# Image layers are scanned for hardcoded credentials and secrets

- **Pattern:** `ab-001632` (`infrastructure-hardening.container-image-security.no-secrets-in-layers`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001632
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001632)

## Why it matters

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.

## Severity rationale

Critical because credentials baked into image layers are exfiltrated by anyone with registry read access — no runtime exploit required.

## Remediation

Use multi-stage builds to ensure secrets never land in the final image layer. In `Dockerfile`:

```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.

## Detection

- **ID:** `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:

  ```dockerfile
  # 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.

## External references

- cwe CWE-540
- owasp A02
- nist SC-28
- external CIS-Docker-4.10 — https://www.cisecurity.org/benchmark/docker

Taxons: cryptography-and-secrets, supply-chain

HTML version: https://auditbuffet.com/patterns/ab-001632
