Secrets baked into Docker image layers are permanently readable by anyone with registry pull access — including future employees, CI systems, and compromised tokens (OWASP A02, CWE-540). Docker layer history exposes every ARG, ENV, and RUN instruction even after a subsequent RUN unset — the value persists in the lower layer. NIST 800-53 SC-28 requires protection of information at rest; credentials in registry layers violate that control and are disclosed with every docker history command. This is one of the most common paths to credential exposure in containerized environments.
Critical because credentials baked into image layers are exfiltrated by anyone with registry read access — no runtime exploit required.
Use multi-stage builds to ensure secrets never land in the final image layer. In Dockerfile:
# Build stage — secret is accessible here
FROM node:20 AS builder
ARG PRIVATE_TOKEN
RUN git clone https://${PRIVATE_TOKEN}@github.com/org/private-repo.git /src
RUN cd /src && npm ci && npm run build
# Runtime stage — secret never copied
FROM node:20-alpine
COPY --from=builder /src/dist /app
CMD ["node", "/app/index.js"]
For runtime secrets, inject via Kubernetes Secrets (secretKeyRef) or a secrets manager (Vault, AWS Secrets Manager). Never use ENV SECRET=value in the final stage — use docker secret or runtime injection instead.
ID: infrastructure-hardening.container-image-security.no-secrets-in-layers
Severity: critical
What to look for: Enumerate every RUN, COPY, ADD, ARG, and ENV instruction across all Dockerfiles. For each instruction, classify whether it handles sensitive data (API keys, passwords, private keys, tokens, database credentials). Count the total instructions that reference or embed secrets.
Pass criteria: No Dockerfile instruction contains hardcoded secrets, API keys, passwords, or private keys — 0 of the total instructions reference embedded credentials. All sensitive data is injected at runtime via environment variables, Kubernetes Secrets, or a secrets manager rather than baked into image layers. Multi-stage builds are used to exclude at least 100% of build-time secrets from the final image. Report: "Scanned X instructions across Y Dockerfiles, 0 secrets found in layers."
Fail criteria: Any hardcoded secret, API key, password, private key, or database credentials found in Dockerfile layers. Secrets visible in .dockerignore but not excluded, or stored in image layers.
Skip (N/A) when: The project does not have a Dockerfile.
Detail on fail: Quote the offending instruction. Example: "Dockerfile RUN command contains git clone with hardcoded PAT: 'git clone https://token:abc123@github.com/...'" or "AWS credentials passed as ARG without being excluded from final layer"
Remediation: Never hardcode secrets in Dockerfiles. Use multi-stage builds to exclude secrets from the final image:
# Build stage with secrets
FROM node:20 AS builder
ARG PRIVATE_TOKEN
RUN git clone https://${PRIVATE_TOKEN}@github.com/org/repo.git
# Final stage — no secrets
FROM node:20
COPY --from=builder /app /app
# PRIVATE_TOKEN is not in this stage
At runtime, inject secrets via Kubernetes Secrets or environment variables — never bake them into the image.