Secrets in Public ECR Images: Hunting What Should Never Ship
Container images are zip files with habits. Build args, env layers, and leftover /.aws directories survive in history even after a later layer "deletes" them. When those images are public on Amazon ECR Public—or accidentally world-pullable on a private registry—the secret walks out with docker pull.
Confirm pull access first
For ECR Public, anyone can pull. For private ECR, try unauthenticated pull and a low-privilege identity. AccessDenied ends that path. A successful pull of an image tagged prod, release, or a customer-facing app name is your green light to inspect layers offline.
Stay scoped: only images clearly belonging to the target. Random public gallery browsing is not a bounty. Match image names to the company's GitHub or Helm charts so triage does not argue ownership.
Record the digest, not just the floating tag. latest moves; digests pin your evidence.
Where secrets hide
Unpack or use a local scanner against the image you pulled. High-yield locations:
- Environment variables in config JSON
/app/.env,credentials.json, Terraform files, kubeconfigs- Shell history and CI scripts copied into the image
- Deleted files still present in earlier layers
- NPM, PyPI, or Docker Hub tokens in build caches
I care less about a placeholder API_KEY=changeme and more about a live cloud key, private package token, or database URL that authenticates somewhere. Squashed images reduce history risk; multi-stage builds that copy the builder's entire filesystem recreate it. Helm charts that reference the same digest as the public gallery make ownership arguments easy in the report.
Private registries with a repository policy of Principal: "*" plus ecr:BatchGetImage are functionally public even when the console still says "private." Treat that policy as enough reason to pull and inspect.
Validate without becoming the incident
If you find an AWS access key, call sts:GetCallerIdentity only. If you find a SaaS token, use the vendor's "whoami" or a harmless metadata endpoint. Never create resources, never exfiltrate customer data, never push to the registry.
Document layer digests, file paths, and a redacted secret fingerprint (last four characters). That is enough for rotation. If the key is already disabled, say so—still a process failure, usually lower severity.
Related misconfigs
- Mutable
latesttags that let a compromised push overwrite production pulls - Repository policies granting
ecr:GetDownloadUrlForLayerto* - CI that builds from a Dockerfile with
COPY . .including local secrets - Debug images published beside production with richer env files
Those can be separate findings or context for why the secret appeared.
Fix guidance
Rebuild without secrets; inject at runtime via task roles or Secrets Manager. Use multi-stage builds, .dockerignore, and careful layer design. Enable tag immutability for release tags. Scan images in CI and fail on high-entropy credentials. Rotate everything that lived in a public layer—history is forever until the image is gone and the credential is dead.
Public ECR is a gift for recon. Treat pulled secrets as broken build hygiene with cloud consequences, and keep validation minimal.