API Security and Container Security
REST and GraphQL API vulnerabilities, JWT attacks, OAuth misconfigurations, Docker container escape, Kubernetes RBAC, and supply chain security.
Modern applications are built from APIs and containers. A REST API that exposes user data is just as valuable a target as an HTTPS web page. A container with a misconfigured security context is just as dangerous as a misconfigured server. The attack surface has changed; the attacker's objectives haven't.
This module covers the OWASP API Security Top 10, the specific mechanics of JWT attacks and OAuth misconfiguration, how Docker containers can be escaped via privileged mode or volume mounts, Kubernetes RBAC and how its misconfigurations become privilege escalation paths, and supply chain security for container images — because an attacker who poisons your base image owns everything built on top of it.
OWASP API Security Top 10
The OWASP API Security Top 10 (2023) covers the most common and impactful API-specific vulnerabilities. Many overlap with the Web Application Top 10 but have API-specific nuances in how they manifest and how they're detected.
| ID | Name | Core Issue | Example |
|---|---|---|---|
| API1 | Broken Object Level Authorization | Access other users' objects by changing ID | GET /api/orders/1042 → returns another user's order |
| API2 | Broken Authentication | Weak or missing authentication mechanisms | API keys in URL, no token rotation, weak JWTs |
| API3 | Broken Object Property Level Authorization | Read/write sensitive fields not intended for caller | PATCH /users/me returns/sets isAdmin field |
| API4 | Unrestricted Resource Consumption | No rate limiting, quotas, or size limits | Upload 10GB files, call expensive endpoint 10K times/sec |
| API5 | Broken Function Level Authorization | Access admin-only functions as regular user | GET /admin/users works without admin role |
| API6 | Unrestricted Access to Sensitive Business Flows | Abuse legitimate API flows at scale | Bot buys limited inventory in milliseconds |
| API7 | Server Side Request Forgery | Server fetches attacker-controlled URLs | webhook URL → 169.254.169.254 → cloud metadata |
| API8 | Security Misconfiguration | Exposed debug endpoints, verbose errors, CORS * | GET /api/debug/config returns env vars with secrets |
| API9 | Improper Inventory Management | Forgotten old API versions, shadow APIs | v1 API still accessible, bypasses v2 security controls |
| API10 | Unsafe Consumption of APIs | Trust third-party API responses blindly | Parse third-party data → SQL injection via trusted data |
API security testing checklist: Authentication: [ ] Can I access the API without a token? [ ] Does the token expire? What happens with an expired token? [ ] Can I reuse a token after logout? [ ] Is the token in the URL (visible in logs)? Should be in Authorization header. Authorization (BOLA / IDOR): [ ] Create two test accounts (A and B) [ ] Log in as A, capture resource IDs (invoice IDs, order IDs, document IDs) [ ] Log in as B, try to access A's resource IDs [ ] Succeeds? → IDOR/BOLA Mass Assignment (API3): [ ] PATCH or PUT your own resource [ ] Add extra fields in the body: role, isAdmin, accountBalance, verifiedEmail [ ] Check if any extra fields are accepted or reflected in response Rate Limiting (API4): [ ] Call the same endpoint 100 times in 10 seconds [ ] Does it return 429 (Too Many Requests)? No → missing rate limiting [ ] Check: POST /auth/login (brute force path), POST /api/search (resource exhaustion) Function Level Authorization (API5): [ ] Find admin endpoints in API docs, mobile app, JS source [ ] Call them as a regular user [ ] Common: /api/admin/*, /api/internal/*, /api/v1/debug/* CORS: [ ] Check: Origin: https://evil.com header [ ] If response includes Access-Control-Allow-Origin: https://evil.com → CORS misconfiguration [ ] If Access-Control-Allow-Origin: * + credentials → critical finding
JWT Attacks — Exploiting Token Vulnerabilities
JSON Web Tokens (JWTs) are the dominant authentication mechanism for REST APIs. A JWT consists of three base64-encoded parts: header (algorithm + type), payload (claims), and signature. The server verifies the signature; if valid, it trusts the claims. Several well-known attacks exploit weaknesses in JWT implementation.
JWT structure:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwicm9sZSI6InVzZXIiLCJleHAiOjE3MDA1ODUwMDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Decoded:
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "1234567890", "name": "Alice", "role": "user", "exp": 1700585000}
Signature: HMAC-SHA256(base64(header) + "." + base64(payload), secret_key)
Attack 1: Algorithm None (CVE-2015-9235)
Modify header: {"alg": "none", "typ": "JWT"}
Modify payload: {"sub": "1", "name": "Admin", "role": "admin", "exp": 9999999999}
Remove signature (just keep the trailing dot)
Some libraries accept alg:none as valid and skip signature verification!
Result: Forged token accepted as admin
Fix: Explicitly whitelist allowed algorithms; reject "none" and "None"
Attack 2: Algorithm Confusion (RS256 → HS256)
Server signs with RSA private key (RS256 algorithm)
Server public key is often accessible (e.g., /api/auth/keys.json)
Attack: Change alg from RS256 to HS256
Sign the token using the PUBLIC KEY as the HMAC secret
Vulnerable libraries: When verifying, they check "alg in token is HS256"
→ use the configured "key" (which is the RSA public key)
→ verify as HMAC-SHA256 with public key as secret
→ Attacker also computed the same HMAC → verification passes!
Fix: Verify algorithm matches what the server expects, not what the token claims
Attack 3: Weak Secret (brute force)
HS256/HS384/HS512 JWTs signed with a weak secret can be cracked offline
hashcat -a 0 -m 16500 jwt.txt wordlist.txt
# jwt.txt format: eyJhbG...(full JWT)
# Common weak secrets: "secret", "password", "key", "jwt-secret", app name
Fix: Use secrets.token_hex(32) (256 bits) minimum for HMAC key
Attack 4: JWK Injection
JWT header can include "jku" (JWK URL) or "jwk" (inline JWK)
Attacker hosts their own JWK Set at a controlled URL
If library fetches and trusts this URL without validation:
→ Attacker's own public key is used to verify
→ Attacker signs token with their private key → verification passes
Fix: Never use "jku" or "jwk" from the token header; use server-configured key onlyOAuth 2.0 Misconfigurations
OAuth 2.0 is the standard for delegated authorisation — allowing a third-party application to access a user's resources on another service without sharing the user's password. The complexity of the protocol creates multiple misconfigurations that attackers exploit.
OAuth Authorization Code Flow (correct usage):
1. App redirects: https://auth.example.com/authorize?
response_type=code
&client_id=app123
&redirect_uri=https://myapp.com/callback
&state=random_csrf_token ← Anti-CSRF
&scope=email,profile
2. User authenticates at auth server
3. Auth server redirects to: https://myapp.com/callback?
code=AUTH_CODE_HERE
&state=random_csrf_token ← Verify this matches what you sent
4. App exchanges code for tokens (server-side, not in browser):
POST /token
{code: "AUTH_CODE_HERE", client_id: "app123", client_secret: "SECRET"}
→ {access_token: "...", refresh_token: "..."}
5. App uses access_token to call APIs on behalf of user
Common OAuth attacks:
Attack 1: Redirect URI manipulation
Legitimate redirect_uri: https://myapp.com/callback
Attacker-controlled redirect_uri: https://evil.com/callback
Victim authorises → auth code sent to https://evil.com/callback
Attacker exchanges code → gets access token → accesses victim's account
Fix: Exact string matching for redirect_uri (no wildcards, no regex)
Register allowed redirect URIs server-side; reject any mismatch
Attack 2: Missing state parameter (CSRF)
State: random value generated per-request, verified on callback
Without state:
Attacker starts OAuth flow for their own account
Pauses before step 3 — has a valid auth code for their account
Tricks victim into visiting the callback URL with attacker's code
App associates attacker's account with victim's session
Attacker now controls victim's app account
Fix: Always generate and verify state parameter
Attack 3: Token leakage via Referer header
Implicit flow (deprecated) returns token in URL fragment: #access_token=...
If page includes any third-party resources (analytics, CDN, fonts)
The Referer header includes the full URL — token leaked to third party
Fix: Use Authorization Code flow (not implicit); never put tokens in URLs
Attack 4: Open redirect on redirect_uri
redirect_uri=https://myapp.com/logout?next=https://evil.com
Auth code in URL → next= redirects to evil.com → code captured
Fix: Validate redirect_uri against exact allowlist; no open redirects in appGraphQL Security
GraphQL APIs present security challenges not found in REST: introspection exposes the full schema (a map of every type, field, and operation), query depth is unlimited by default (deeply nested queries can exhaust resources), and batching allows multiple operations in a single request (amplifying brute force attacks).
GraphQL-specific attack vectors:
1. Introspection — Schema discovery
All GraphQL implementations enable introspection by default
Query: {__schema{types{name fields{name type{name}}}}}
Returns: complete schema of every type, field, and mutation
Attacker uses introspection to:
- Discover hidden fields (isAdmin, internalId, creditCardNumber)
- Find mutations that modify sensitive data
- Build targeted queries without documentation
Fix: Disable introspection in production
# Apollo Server:
new ApolloServer({ introspection: process.env.NODE_ENV !== 'production' })
2. Query depth attacks
Deeply nested query exhausts server resources:
{ users { friends { friends { friends { friends { id email } } } } } }
100 levels deep → exponential database queries
Fix: Depth limiting middleware
# graphql-depth-limit (npm):
const depthLimit = require('graphql-depth-limit')
createYoga({ validationRules: [depthLimit(5)] })
3. Batching / brute force via aliases
GraphQL allows multiple operations with aliases in one request:
mutation {
a1: login(username: "admin", password: "password1") { token }
a2: login(username: "admin", password: "password2") { token }
a3: login(username: "admin", password: "password3") { token }
... (100 variations in one HTTP request)
}
Bypasses rate limiting that counts HTTP requests, not GraphQL operations
Fix: Rate limit at operation level, not HTTP request level
Limit number of operations per request (max 10)
4. Information disclosure via error messages
Invalid query returns detailed error:
"Cannot query field 'secret' on type 'User'. Did you mean 'secretToken'?"
→ Reveals internal field name
Fix: Custom error handler that masks internal details in production
5. Authorization bypass via field-level access
Query: { users { id email internalNotes creditBalance } }
Application checks: "Can user access /users?" → Yes
Doesn't check: "Can user access internalNotes or creditBalance?" → No
Fix: Field-level authorization; every resolver checks authorization
(not just the root query resolver)Docker Container Security
Containers provide isolation via Linux namespaces and cgroups — but this isolation has limits. Several common Docker configurations weaken or eliminate container isolation, allowing a compromised container process to escape to the host system.
Container escape techniques:
1. Privileged container (--privileged flag)
Container has all Linux capabilities + access to all devices
Effectively root on the host with namespace separation only
Escape technique:
# Inside privileged container:
ls /dev/sda # Host disk visible
mkdir /mnt/host
mount /dev/sda1 /mnt/host # Mount host filesystem
chroot /mnt/host # chroot into host OS
# Now have full root filesystem access to host
Detection: docker inspect <id> | jq '.[].HostConfig.Privileged'
Fix: Never use --privileged in production; use specific capabilities instead
2. Docker socket mount (-v /var/run/docker.sock:/var/run/docker.sock)
Docker socket = administrative API to Docker daemon
Process inside container can control the Docker daemon
Escape:
# Inside container with socket mounted:
docker run -v /:/host --rm -it ubuntu chroot /host bash
# Launched a NEW container mounting the HOST filesystem
# chroot into it → root on host
Fix: Never mount Docker socket in containers
Use Docker-in-Docker (DinD) for CI/CD that needs Docker access
3. Sensitive host directory mounts
-v /:/host # Entire host filesystem
-v /etc:/etc # Host /etc (modify shadow, crontab, sudoers)
-v /proc:/host-proc # Host /proc (process info)
Fix: Mount only specific directories needed; use read-only where possible
-v /app/data:/data:ro (read-only mount)
4. Capability abuse (CAP_SYS_ADMIN, CAP_NET_ADMIN)
CAP_SYS_ADMIN: Near-root capability; allows mounting, namespaces, syscalls
CAP_NET_ADMIN: Change network interfaces, routing, raw packets
CAP_SYS_ADMIN escape:
# Mount a new tmpfs with cgroup release_agent
unshare -UrmC bash
mount -t cgroup -o rdma cgroup /tmp/cgroup
echo 1 > /tmp/cgroup/notify_on_release
echo /cmd > /tmp/cgroup/release_agent # /cmd will execute on host
echo "#!/bin/sh; id > /output" > /cmd # Host-side command
# Trigger cgroup release → host executes /cmd as root
Fix: Drop all capabilities, add only what's needed:
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginxMinimal-privilege Dockerfile: # BAD — runs as root by default FROM node:20 WORKDIR /app COPY . . RUN npm install CMD ["node", "server.js"] # GOOD — minimal privilege, non-root user, read-only filesystem FROM node:20-slim AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production FROM node:20-slim # Create non-root user RUN groupadd -r appuser && useradd -r -g appuser appuser WORKDIR /app # Copy only built artifacts COPY --from=builder --chown=appuser:appuser /app/node_modules ./node_modules COPY --chown=appuser:appuser src/ ./src # Drop all root privileges USER appuser # Read-only root filesystem (mount writable volumes for needed paths) # docker run --read-only -v /tmp:/tmp app EXPOSE 3000 CMD ["node", "src/server.js"] # Run with additional Docker security flags: docker run \ --read-only \ # Immutable filesystem --tmpfs /tmp \ # Writable /tmp in RAM --cap-drop ALL \ # No Linux capabilities --cap-add NET_BIND_SERVICE \ # Only if binding port < 1024 --no-new-privileges \ # Prevent privilege escalation via SUID --security-opt=no-new-privileges \ -u 1001:1001 \ # Non-root UID:GID myapp:latest
Kubernetes RBAC and Security
Kubernetes RBAC (Role-Based Access Control) controls what identities (Service Accounts, users, groups) can do in the cluster. Misconfigured RBAC is one of the most common Kubernetes security findings because the default service account has too many permissions, and "quick fix" role bindings often grant cluster-admin.
Kubernetes RBAC concepts:
Role vs ClusterRole:
Role: Namespaced — grants permissions within one namespace
ClusterRole: Cluster-wide — grants permissions across all namespaces
RoleBinding vs ClusterRoleBinding:
RoleBinding: Binds a Role/ClusterRole to principals in one namespace
ClusterRoleBinding: Binds a ClusterRole to principals cluster-wide
Dangerous RBAC configurations:
1. cluster-admin bound to default service account:
ClusterRoleBinding that binds cluster-admin to:
system:serviceaccounts (all service accounts in all namespaces)
system:authenticated (any authenticated user)
→ Any compromised pod has full cluster control
2. Wildcard permissions:
rules:
- apiGroups: ["*"] ← all API groups
resources: ["*"] ← all resources
verbs: ["*"] ← all verbs (get, list, create, delete, update)
→ Effectively cluster-admin
3. pods/exec permission:
rules:
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
→ Can exec into any pod in namespace → steal other pods' secrets/service account tokens
4. secrets verb:
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
→ Can read ALL secrets in namespace, including other services' credentials
5. Role escalation via bind/escalate:
verb "bind" on ClusterRole/Role objects → can grant yourself any role
verb "escalate" → can create roles with permissions you don't currently have# Kubernetes RBAC audit commands
# List all ClusterRoleBindings — who has cluster-wide access?
kubectl get clusterrolebindings -o json | \
jq '.items[] | {name: .metadata.name, subjects: .subjects, role: .roleRef.name}'
# Find all roles with dangerous permissions:
kubectl get roles,clusterroles -A -o json | \
jq '[.items[] | select(.rules[]?.resources[]? == "*" or .rules[]?.verbs[]? == "*")]'
# What can the default service account do?
kubectl auth can-i --list --as=system:serviceaccount:default:default
# What can a specific service account do?
kubectl auth can-i --list --as=system:serviceaccount:prod:backend-sa -n prod
# Check if we can exec into pods (lateral movement vector):
kubectl auth can-i create pods/exec -n production
# Use Kube-Bench to run CIS Kubernetes Benchmark:
# docker run --rm -v $(pwd):/host aquasec/kube-bench:latest --json > results.json
# Audit default service account tokens (should be auto-mounted=false):
kubectl get pods -A -o json | \
jq '.items[] | select(.spec.automountServiceAccountToken != false) |
{name: .metadata.name, namespace: .metadata.namespace}'# Kubernetes Pod Security Standards
Privileged policy (least secure — avoid):
Allows all container capabilities; essentially unrestricted
Baseline policy (default for most workloads):
Disallows: privileged containers, host PID/IPC/network namespaces, hostPath volumes
Allows: most capabilities needed for normal applications
Restricted policy (most secure):
Everything in Baseline PLUS:
- Non-root user required (runAsNonRoot: true)
- Seccomp profile required (RuntimeDefault or Localhost)
- All capabilities dropped (ALL)
- No privilege escalation (allowPrivilegeEscalation: false)
Apply PSA labels to namespaces:
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
Example secure Pod spec:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
limits:
memory: "128Mi"
cpu: "100m"Container Image Supply Chain Security
The container image supply chain is the sequence of steps that produces a container image: base image selection, dependency installation, build steps, and registry storage. A compromise at any step affects every deployment built from that image. SolarWinds-style supply chain attacks now target container build pipelines because they provide leverage over thousands of deployed instances from a single compromise.
Supply chain attack vectors:
1. Malicious base image:
FROM someuser/node-alpine:latest
Base image from untrusted registry contains backdoor
All builds inherit the backdoor
Real example: typosquatting (node-alepin, nod-alpine) on Docker Hub
Fix:
- Use official images (docker.io/library/node, not third-party)
- Pin to digest, not tag: FROM node@sha256:abc123...
(tag is mutable; digest is cryptographic hash of image layers)
- Use distroless images (Google): minimal OS, reduced attack surface
FROM gcr.io/distroless/nodejs20-debian12
2. Dependency confusion / typosquatting:
npm install left-pad → attacker publishes malicious left-pad
pip install requets → typosquat of requests
Fix:
- Use package-lock.json / poetry.lock / requirements.txt with pinned hashes
- Private registry mirror for critical dependencies
- Verify checksums: pip install --require-hashes
3. Compromised build pipeline:
CI/CD system with access to registry credentials
Attacker injects malicious build step
Fix:
- Isolated build environments (no persistent credentials)
- OIDC-based registry auth (short-lived tokens)
- Build provenance: SLSA framework
4. Outdated base images with CVEs:
Most breaches don't need supply chain attacks
Simply use old base image with known CVE
Fix:
- Scan images: trivy, Grype, Snyk, Amazon ECR scanning
- Rebuild base images weekly
- Alert on critical CVEs in deployed images
Image signing with Cosign (sigstore):
# Sign image after build:
cosign sign --key cosign.key registry.io/myapp:v1.0.0
# Verify before pulling:
cosign verify --key cosign.pub registry.io/myapp:v1.0.0
# Kubernetes policy enforcement (Kyverno or OPA Gatekeeper):
# Reject unsigned images from running in production namespace# Trivy — container image vulnerability scanning # Scan a local image: trivy image myapp:latest # Output: 2024-01-15T10:30:00.000Z INFO Scanning image... 2024-01-15T10:30:02.000Z INFO Detected OS: debian 12.4 2024-01-15T10:30:02.000Z INFO Detecting Debian vulnerabilities... myapp:latest (debian 12.4) ===================================================== Total: 47 (UNKNOWN: 0, LOW: 28, MEDIUM: 12, HIGH: 5, CRITICAL: 2) CRITICAL: CVE-2023-4911 libglibc Glibc 2.37 buffer overflow (Looney Tunables) CVE-2023-44487 nghttp2 HTTP/2 Rapid Reset DoS HIGH: CVE-2023-5156 libssl3 OpenSSL use-after-free ... # Scan and fail build on CRITICAL findings (CI/CD integration): trivy image --exit-code 1 --severity CRITICAL,HIGH myapp:latest # Scan a Dockerfile for misconfigurations: trivy config ./Dockerfile # Scan running Kubernetes cluster: trivy k8s --report all cluster
Interview Questions
The attack: the attacker modifies the token header to change "alg": "RS256" to "alg": "HS256". Then they sign the modified payload using the RSA public key as the HMAC-SHA256 secret. When a vulnerable library verifies this token, it reads "alg": "HS256" from the header, takes its configured verification key (which is the RSA public key), and computes HMAC-SHA256 with that key as the secret. Since the attacker used the same public key to create their signature, the verification passes. The attacker has effectively forged a token — they can set any claims they want (role: admin, user_id: 1).
The fix is to verify that the algorithm in the incoming token matches the algorithm the server expects, rather than accepting the algorithm from the token itself. Libraries should be configured with: "this endpoint only accepts RS256, reject anything else." Never let the token dictate what algorithm is used for verification.
The fix: never use --privileged in production containers. Instead, identify the specific Linux capabilities the container actually needs (e.g., NET_BIND_SERVICE to bind to port 80, or AUDIT_WRITE for audit logging) and grant only those with --cap-drop ALL --cap-add NET_BIND_SERVICE. No application should need --privileged. For containers that legitimately need more host access (monitoring agents, security tools), use a stricter alternative like userns-remap to map the container's root to a non-root UID on the host, or use pod security policies/standards to enforce restrictions at the Kubernetes level.
ClusterRoleBindings are far more dangerous when misconfigured. If a ClusterRole with wildcard permissions (resources: ["*"], verbs: ["*"]) is bound via a ClusterRoleBinding, the bound principal has full control of the entire Kubernetes cluster — equivalent to cluster-admin. If that same role were bound via a RoleBinding in a single namespace, the blast radius is limited to that namespace.
The most dangerous misconfiguration in practice: binding cluster-admin to system:serviceaccounts (all service accounts in all namespaces) or to system:authenticated (any authenticated user). This means any compromised pod — regardless of what it's supposed to do — has cluster-admin access and can read all secrets, exec into any pod, create privileged pods to escape to nodes, or delete all workloads.
In the registry stage: sign images with Cosign so you can verify provenance at deploy time. Store images in a private registry, not Docker Hub public. Enable automatic vulnerability scanning on push (AWS ECR, GCR, and Azure ACR all offer this). Set registry policies that reject images with Critical CVEs. In the deploy stage: use a policy engine (Kyverno or OPA Gatekeeper) in Kubernetes that rejects unsigned images or images with recent high-severity CVEs. Enforce pod security standards (restricted profile) to prevent privileged containers from running. Regularly rebuild base images to incorporate security patches — a weekly automated rebuild pipeline ensures you're not running with months-old base images.
Look for: HTTP 200 responses returning Account A's data when authenticated as Account B (confirmed IDOR), or HTTP 500 errors (suggests the request reached the object but caused an error — still worth investigating). HTTP 403 or 404 is the correct response. Also test with IDs that shouldn't exist — some applications leak information by returning different error messages for "object exists but you can't access it" versus "object doesn't exist." Finally, test with enumerable IDs: if IDs are sequential integers (1, 2, 3...), try IDs near your own to find recent records from other users. If IDs are UUIDs, the attack surface is smaller but still test edge cases.
Error Library — Common Mistakes
🎯 Key Takeaways
- ✓BOLA (API1) — Broken Object Level Authorization — is the most common and impactful API vulnerability. Test with two accounts: can Account B access Account A's resources by substituting object IDs?
- ✓JWT algorithm confusion attacks change "alg": "RS256" to "alg": "HS256" in the header, then sign with the public key as the HMAC secret. Fix: specify expected algorithm explicitly in the verification call; never derive it from the token.
- ✓OAuth redirect_uri must be validated with exact string matching. Any mismatch should reject the authorization. Wildcards or substring matching enable authorization code theft to attacker-controlled redirect URIs.
- ✓Docker --privileged containers can mount host block devices and chroot into the host filesystem. Never use --privileged; grant specific capabilities with --cap-drop ALL --cap-add [specific capability].
- ✓Mounting the Docker socket (/var/run/docker.sock) into a container grants it full control of the host's Docker daemon — equivalent to root on the host. Use kaniko or BuildKit for CI/CD image builds instead.
- ✓Kubernetes ClusterRoleBindings with wildcard permissions or binding to system:serviceaccounts are the most dangerous RBAC misconfigurations — they give every compromised pod cluster-admin access.
- ✓Most pods don't need Kubernetes API access. Set automountServiceAccountToken: false and use pod security standards (restricted profile) by default. Add permissions only when explicitly needed.
- ✓GraphQL introspection should be disabled in production — it provides attackers a complete map of every type, field, and mutation. Rate limit at the operation level, not just HTTP request level.
- ✓Container image supply chain security: pin base images to SHA256 digests (not floating tags), scan with Trivy on every build, sign with Cosign, and enforce signature verification at deploy time via Kyverno.
- ✓Kubernetes pod security standards (restricted profile) enforce non-root user, dropped capabilities, no privilege escalation, seccomp profile, and read-only root filesystem. Apply to all namespaces; exceptions require explicit justification.
In Module 20, you learn to write code that is secure by design: input validation patterns, output encoding, parameterised queries, secrets management in code, dependency management, security testing in CI/CD, and threat modelling as a development practice. This module bridges development and security — making security the developer's job, not an afterthought.
Continue to Module 20 →Discussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.