$npx -y skills add briiirussell/cybersecurity-skills --skill container-auditAudit container images, Dockerfiles, and Kubernetes manifests for misconfigurations, excessive privileges, exposed secrets, and runtime risks. Use when the user mentions 'container security,' 'Docker security,' 'Dockerfile audit,' 'Kubernetes security,' 'K8s security,' 'pod secur
| 1 | # Container Audit — Docker & Kubernetes Security Review |
| 2 | |
| 3 | Audit container images, Dockerfiles, Helm charts, Kustomize overlays, and Kubernetes manifests for misconfiguration, excessive privilege, exposed secrets, and runtime security gaps. Distinct from `cloud-audit` (cloud-provider IAM and managed services) and `dependency-audit` (package CVEs in the application). This skill is the container/orchestration layer between them. |
| 4 | |
| 5 | ## Scope the Audit |
| 6 | |
| 7 | 1. Inventory the surface — Dockerfiles, base images, registries, Helm charts, K8s manifests, Kustomize overlays, CI build pipelines that produce images |
| 8 | 2. Identify the runtime — vanilla K8s, EKS, GKE, AKS, OpenShift, ECS Fargate, Cloud Run, Fly.io |
| 9 | 3. Identify the network model — service mesh, ingress controller, default-deny vs default-allow |
| 10 | 4. Identify the secret model — K8s Secrets (base64-only), External Secrets Operator, sealed-secrets, Vault, Doppler |
| 11 | |
| 12 | ## Audit Checklist — Dockerfile |
| 13 | |
| 14 | ### Base image & supply chain |
| 15 | |
| 16 | - Pinned by digest, not tag — `FROM node:20@sha256:abc...` not `FROM node:20` (which can move) |
| 17 | - Distroless / minimal where possible — `gcr.io/distroless/nodejs20`, `alpine` (be aware of musl quirks), `chainguard/*` |
| 18 | - Not using `:latest` — non-reproducible builds |
| 19 | - Multi-stage builds discard build-time tooling — `FROM build AS builder` → `FROM runtime` final stage |
| 20 | - Grep for: `FROM .*:latest`, `FROM .*:[0-9]+$` (tag without digest) |
| 21 | |
| 22 | ### Build-time exposure |
| 23 | |
| 24 | - Secrets passed via `--build-arg` end up in image layers visible to anyone who pulls the image — use BuildKit secrets (`--mount=type=secret`) or runtime env vars instead |
| 25 | - `COPY . .` ships everything in the build context — `.dockerignore` should exclude `.git`, `.env`, `node_modules`, `*.pem`, `.aws/`, `.ssh/` |
| 26 | - `ADD <url>` follows redirects and disables checksum verification — prefer `RUN curl ... && sha256sum -c` |
| 27 | - Grep for: `ARG .*KEY`, `ARG .*TOKEN`, `ARG .*SECRET`, `ENV .*=.*[A-Za-z0-9]{32,}`, `ADD http` |
| 28 | |
| 29 | ### Runtime posture |
| 30 | |
| 31 | - Non-root user — `USER 1001` (or any non-zero UID) before `CMD` |
| 32 | - No `chmod 4755` SUID binaries in the final image |
| 33 | - No unnecessary shells / package managers in the final stage — distroless / FROM scratch is the strong default |
| 34 | - `HEALTHCHECK` defined so orchestrator can detect unhealthy containers |
| 35 | - Read-only root filesystem at runtime (set via K8s; verify nothing in the image writes outside `/tmp` or a declared volume) |
| 36 | - Grep for: `USER root` (or absence of any `USER` directive), `chmod 4755`, `apt-get install.*sudo` |
| 37 | |
| 38 | ## Audit Checklist — Kubernetes manifests |
| 39 | |
| 40 | ### Pod security |
| 41 | |
| 42 | - `securityContext.runAsNonRoot: true` and `runAsUser` set to a non-zero UID |
| 43 | - `securityContext.allowPrivilegeEscalation: false` |
| 44 | - `securityContext.readOnlyRootFilesystem: true` with explicit `emptyDir` mounts where the app needs to write |
| 45 | - `securityContext.capabilities.drop: ["ALL"]` then add only what's needed |
| 46 | - `securityContext.privileged` is never `true` in app workloads (Falco, kube-proxy, some CSI drivers are the rare legit exceptions) |
| 47 | - `hostNetwork`, `hostPID`, `hostIPC` all `false` — yes on these is "container can see / talk to the node" |
| 48 | - `hostPath` volumes — every one is a node-escape risk; review case by case |
| 49 | - Grep for: `privileged: true`, `runAsUser: 0`, `hostNetwork: true`, `hostPath:` |
| 50 | |
| 51 | ### Pod Security Standards (PSS) / admission |
| 52 | |
| 53 | - Cluster enforces `restricted` profile via PSS admission, or equivalent via OPA Gatekeeper / Kyverno |
| 54 | - Pod Security Policies (deprecated since 1.21, removed in 1.25) are NOT what's enforcing this — confirm a current admission controller |
| 55 | - No workloads in the `kube-system` namespace running app code |
| 56 | |
| 57 | ### Network |
| 58 | |
| 59 | - `NetworkPolicy` exists for every namespace running app workloads — default-deny ingress AND egress, then allow specific pods |
| 60 | - Missing NetworkPolicy = every pod can talk to every other pod on every port, including kube-apiserver and metadata service |
| 61 | - Service mesh (Istio, Linkerd) mTLS in STRICT mode for sensitive namespaces, not PERMISSIVE |
| 62 | - Ingress controllers terminate TLS properly; backend `tls.crt` / `tls.key` in K8s Secrets rotate |
| 63 | |
| 64 | ### Secrets |
| 65 | |
| 66 | - K8s Secrets are base64-encoded, NOT encrypted — by default they're plain bytes in etcd |
| 67 | - etcd encryption at rest enabled — `--encryption-provider-config` on kube-apiserver |
| 68 | - Workloads consume secrets via projected volumes, not environment variab |