Insufficient Credential Hygiene
Secrets end up somewhere they were never meant to persist — a commit, a log line, an image layer, an environment dump.
If this is new to you
Credentials rarely leak in one dramatic breach. They accumulate. Somebody pastes a token into a build log to debug a failure. A `.env` file gets copied into an image layer. A key is committed and reverted twenty minutes later.
Each of those feels harmless in the moment, and each is permanent unless somebody actively removes it. Git is designed to remember. So are container registries. "We reverted it" is not a fix — the commit is still in history, and anybody who cloned the repository has it.
The only real remedy for an exposed credential is revocation. Deleting the file changes nothing about the copy that already escaped.
What to do about it
Scan your history, not your working tree. A file you deleted is still in every clone; searching the checkout tells you nothing useful.
Never pass a secret as a build argument. Build args are recorded in image metadata, which means `docker history` prints them — the secret is published as a property of the image whether you intended that or not. Use a build secret mount instead, which leaves no layer behind.
Redact at the logger rather than at each call site. If redaction is something every developer has to remember, the first person who forgets is the incident.
Going deeper
The structural answer is to stop having long-lived secrets to leak. Short-lived credentials minted at run time cannot be found in a settings page months later, because they expired the same afternoon.
Where you cannot avoid stored secrets, make rotation a scheduled task rather than an incident response. A secret that has never been rotated has an unknown blast radius, because you do not know who has held it.
Words used on this pagedefinitions
- Image layer
- A container image is a stack of filesystem diffs. A file added in one layer stays in that layer even if a later layer deletes it — which is why removing a copied secret does not remove it.
- Build argument
- A value passed into a build with `--build-arg`. Recorded in image metadata and therefore visible to anyone who can inspect the image.
- Build secret mount
- A mechanism that exposes a secret to one build step without writing it into any layer.
- Revocation
- Invalidating a credential so the leaked copy stops working. The only remedy that actually helps after exposure.
Why this matters
Credentials leak by accumulation rather than by breach. Each individual copy seems harmless: a value pasted into a build log to debug a failure, a `.env` baked into an image layer, a token in a commit that was 'immediately' reverted. Every one of those is permanent unless someone actively removes it, and git and image registries are both designed to remember.
What it looks like in practice
If any of these sound familiar, that is the point — most of them are things somebody did for a sensible reason on a busy afternoon.
- A `.env` committed once and reverted, still readable in history.
- Secrets in image layers, because a `COPY` preceded the `RUN` that removed them.
- Tokens printed by debug logging, or by a step that echoes its environment on failure.
- Credentials passed as build arguments, which are recorded in image metadata.
- Long-lived secrets with no rotation, where the current holder list is unknown.
Check your own pipeline
Questions you can actually answer, rather than a maturity score. If you cannot answer one of them, that is itself the finding — and a more useful one than a number.
Is anything sensitive in git history, not just the working tree?
Deleting a file does not remove it from history. Scan the history, not the checkout.
git log --all --full-history -- .env
Do image layers contain secrets?
Inspect the layer history, including build arguments, which are stored in image metadata whether or not you intended it.
docker history --no-trunc <image>
Would a failed build print a secret?
Read the error paths, not the happy path. Environment dumps on failure are the common leak.
How to fix it
- Keep secrets out of the repository entirely and inject them at runtime, so there is no copy to leak.
- Never pass secrets as build arguments; use build-time secret mounts that leave no layer behind.
- Rotate anything that has ever been exposed, and treat 'we reverted it' as exposure. Revocation is the fix; deletion is not.
- Redact at the logger, not at the call site, so a new call site cannot forget.
Did this get you to an answer?
No text box on purpose — please do not paste production logs anywhere
Risk taxonomy from OWASP Top 10 CI/CD Security Risks (Daniel Krivelevich and Omer Gil, September 2022, CC BY-SA 4.0). Explanations, checks, fixes and definitions written for this site.