Secrets Accidentally Committed in YAML Files and the Cleanup
What to do when a token lands in a values or config YAML: removing it from the file leaves it in history, gitleaks finds it, and rotation is the real fix.

It happens on a normal Tuesday. Someone adds a values.yaml for a deploy, pastes in a token to get it working, and commits. The next commit removes the token. The file now looks clean, the pull request is approved, and the secret is sitting in the repository history for anyone with read access, for as long as the repository exists. YAML files are common carriers, because they are where credentials get wired into tools: Helm values, CI variables, Compose environments, Ansible vars. This post reproduces the leak, shows what a scanner finds, and gives the order of steps for the cleanup. It belongs to the cluster on YAML security pitfalls, and the prevention side is keeping secrets out of YAML config files.
Reproducing it
A first commit with a values file that holds a token, a second commit that replaces the value with a placeholder:
# values.yaml, first commit
replicas: 2
database:
host: db.internal
github:
token: ghp_<36 random characters># values.yaml, second commit
github:
token: ${GITHUB_TOKEN}Look at the repository after the second commit. The working tree is clean: grep -c ghp_ values.yaml finds nothing. Most people stop there. History tells a different story. We repeated the experiment with an AWS-style key to make the history visible in the output:
--- working tree now:
0
--- history still has it:
318507e remove secrets
- access_key_id: AKIAIOSFODNN7EXAMPLE
+ access_key_id: ${AWS_ACCESS_KEY_ID}
14f4283 add values
+ access_key_id: AKIAIOSFODNN7EXAMPLEgit log -p --all -S AKIAIOSFODNN7EXAMPLE lists every commit where the number of occurrences of that string changed, which is the commit that added it and the commit that removed it, each showing the line. The secret is one command away for anyone who can clone. Branches, tags, forks and pull request refs all carry it, and a hosting platform may keep it in caches and in the diff views of old pull requests even after the branch is deleted.
What a scanner sees
Scanners exist for this, and they make a distinction that matters: they scan the working tree, the history or both. We ran gitleaks on the two-commit repository with the random GitHub-style token:
INF 2 commits scanned.
WRN leaks found: 1for the git history, and for the current files:
INF scanned ~75 bytes (75 bytes) in 1.8ms
INF no leaks foundOne leak in history, none in the files. If your CI scans only the checkout, as many simple setups do, it reports a clean repository for a repository with a live credential in it. A scan on pull requests should cover the commits being added, not only the resulting tree, and a periodic scan of the full history catches what slipped in earlier. The tool's --redact option masks the secret in its output, so the report itself does not become a second leak.
A second detail from the test: our first attempt used the AWS documentation's example key and secret, and gitleaks reported nothing. The tool allowlists values that are known to be examples, since flagging the AWS documentation would make every tutorial a finding. The consequence is that you cannot test a scanner with a documentation sample. Use a freshly generated random string in the format the rule expects, as we did for the second run, and keep it fake.
The order of the cleanup
The instinct is to remove the secret from history first. That is the wrong first step, because history rewriting is slow, partial and cannot reach clones that already exist. Work in this order.
Revoke or rotate the secret. Treat it as compromised from the moment it was pushed. A rotated secret makes everything after this step less urgent. For a database password, change it and update the services; for a cloud key, deactivate it and issue a new one; for a token, revoke it in the provider's settings.
Check for use. Look at the provider's audit log from the time of the commit to now. Was the credential used from an unfamiliar address? The answer decides whether this is a cleanup or an incident.
Replace the value in the file with a reference to a secret store or an environment variable, so the new secret never enters the repository. Commit that.
Remove it from history if you must, with a rewriting tool such as
git filter-repoor BFG Repo-Cleaner, then force-push all branches and tags. Tell collaborators to re-clone rather than merge. Ask the hosting provider to purge cached views and pull request refs; most have a process.Search elsewhere. The same file may have been pasted into a chat, a ticket, a wiki, a CI log or a container image layer. A scanner that handles those sources, or a manual search of the places your team shares text, finds the copies history rewriting cannot.
Steps four and five are cosmetic when step one is done, which is why step one comes first. Rewriting history is worth doing for hygiene and for compliance, and it never substitutes for rotation: anyone who fetched the repository in the window holds the old value.
Why YAML is a frequent carrier
Several habits in YAML-heavy work make leaks likely.
Values files are copied. A Helm
values.yamlfrom a working install gets committed as an example, secrets included.Compose files hold environment blocks.
environment: DB_PASSWORD: hunter2is the quickest way to get a container to start, and it stays.CI variables are tempting. Pasting a token into a workflow
env:to debug a failing job, then forgetting.Ansible vars and inventories often contain passwords, and vaulting them is an extra step.
**Kubernetes
Secretmanifests** hold base64 values, which are encoding and not protection; anyone can decode them.
The common fix is the pattern from the other post: the file names where the secret lives and does not contain it. A ${GITHUB_TOKEN} placeholder, a reference to a secret manager, a Kubernetes Secret created out of band, or an encrypted file such as SOPS-encrypted YAML. For encrypted files, keep the key out of the repository too.
Prevention in the workflow
Three layers cost little and stop most accidents.
A pre-commit scan runs the scanner on staged changes and blocks the commit, so the secret never reaches history. Building a pre-commit check for YAML files shows how hooks are wired; a secret scanner hook is another entry in the same file.
A CI scan on pull requests, covering the commits and not only the tree, catches what bypassed the hook.
A push-time check on the server, where the platform offers secret scanning with push protection, which refuses a push containing a recognized token. Where available, turn it on.
Pair these with a habit: when you need a credential in a file to get something working, use a throwaway one, and make the file the example it should be, with fake values in the shape of real ones.
When history rewriting is not the answer
The repository is public or widely forked. Copies exist in places you cannot reach. Rotation is the only control that works.
The secret is already in use elsewhere. Rotation must be coordinated, because changing it breaks the services that use it. Plan the cutover and do it, instead of postponing because it is inconvenient.
Many people have the repository. A force-push breaks every clone. Do it with warning, or accept the copy in history and rely on rotation.
The credential is long-lived and widely scoped. A one-time cleanup does not fix a design that makes such credentials necessary. Move to short-lived tokens.
The scanner is documented at the gitleaks repository, and the history rewriting tool at git-filter-repo. GitHub describes secret scanning and push protection in its documentation. A quick check that a YAML file parses before you commit it: the YAML formatter.


