🎧 Listen to this article: English
🌍 Read this in your language: हिंदी · தமிழ் · తెలుగు · ಕನ್ನಡ · മലയാളം · ଓଡ଼ିଆ · 日本語 · 中文
Someone asked me a boring question this week: "where is that production config file?" Twenty minutes later I was looking at a live cloud access key sitting in a Git repository, readable by everyone on the team and by anyone who had ever cloned it.
Today is September 29, 2026, and this keeps happening for a very current reason. Teams are moving fast to bolt AI features onto existing products, and every new integration brings another key: a model API key, a cloud service account, a storage credential. Those keys have to live somewhere, and the path of least resistance is the application's config file. If that file is already tracked in Git, the secret is published the moment someone commits.
The point of this post is not "do not commit secrets." Everyone knows that. The point is what to do when you discover one, because the obvious instinct is wrong.
It started with a boring question
I was looking at a .NET application. The config file in question was appsettings.Production.json, which is the standard place a .NET app keeps its production settings. The question was simply which copy was authoritative, because the file exists in three different places at once:
- in the source tree, in the repository
- inside the running container, at the deployed path
- rewritten at deploy time by the build pipeline, which injects the database connection string
That third one matters. Because the pipeline overwrites some values at deploy, it is easy to assume it overwrites all of them. It does not. Whatever the pipeline does not inject is used exactly as committed.
The first thing to check is whether the file is tracked
This is a one-line check and it is worth running on any config file you are unsure about:
git ls-files --error-unmatch path/to/appsettings.Production.json
If that command succeeds, the file is tracked, which means it is in the repository and in its history. In my case it succeeded, and the .gitignore had no rule for config files at all.
Inside were roughly a dozen secrets: a database connection string, several passwords, web push notification keys, an AI model API key, and a cloud access key pair.
Why "we will just delete it" does not work
Here is the part that surprises people. Removing a secret from a file and committing that change does not remove the secret.
Git stores history, not just the current state. The old commit still exists. Anyone can run:
git log --oneline -- path/to/appsettings.Production.json
git show <old-commit>:path/to/appsettings.Production.json
and read the original values. Every clone of the repository carries that full history. So a developer who cloned the project last year still has the secret on their laptop today, even after you "remove" it.
You can rewrite history with tools built for that, but it requires a force push, it breaks every existing clone, and critically it does nothing about copies people already have. History rewriting is cleanup. It is not remediation.
The only action that actually revokes exposure is changing the secret itself.
Work out who really has it
Before deciding how urgent this is, count the holders honestly. In a typical small team the list is longer than it looks:
- every current developer with repository access
- every former developer or contractor who ever cloned it, whose laptop you no longer control
- build agent working directories and their caches
- server and virtual machine backups and snapshots
- any mirror of the repository
That last group is easy to forget. Backups are designed to be durable, which is exactly the wrong property for a leaked credential.
Find out what the key can actually do
Severity is not about the fact of a leak. It is about privilege. A read-only key for one storage bucket is a very different problem from an administrative key.
The user account here was named as though it were a storage-only account. It was not. Checking its actual permissions told a different story:
aws iam list-attached-user-policies --user-name app-s3
aws iam list-user-policies --user-name app-s3
aws iam list-groups-for-user --user-name app-s3
It had full storage access across every bucket, membership in a group granting full control of the compute service running production, and permission to send email as the company domain. A name implying narrow scope, with broad power behind it.
Then, the question that decides your timeline:
aws iam get-access-key-last-used --access-key-id AKIAEXAMPLEKEYID
It had been used that same morning. So the key was not a forgotten leftover. Something in production depended on it, which meant deleting it immediately would have caused an outage.
Rotate, do not just revoke
That last detail changes the plan. A live, in-use credential needs a rollover, not a sudden revoke. Here is the sequence that closes the hole without breaking production.
Step 1: Confirm the key is live and find its blast radius
Run the permission and last-used checks above. Decide urgency from what the key can do, not from how it is named.
Step 2: Create a second key alongside the first
Most cloud providers allow two active access keys per user precisely so you can roll over safely.
aws iam create-access-key --user-name app-s3
Do not touch the old key yet.
Step 3: Put the new key somewhere that is not the repository
Move it into whatever secret store your pipeline already uses. Most teams already have one and simply are not using it consistently. Reference it from the build so the value is injected at deploy time and never written into source.
Step 4: Deploy and verify
Redeploy, then confirm the functions that used the key still work. In my case that meant checking outbound email and file storage. Verify before you revoke, never after.
Step 5: Deactivate the old key
Deactivate rather than delete first, because deactivation is instantly reversible if you missed a consumer.
aws iam update-access-key --user-name app-s3 --access-key-id AKIAEXAMPLEKEYID --status Inactive
Watch for errors for a few days, then delete it.
Step 6: Only now, clean the repository
git rm --cached path/to/appsettings.Production.json
echo "appsettings.Production.json" >> .gitignore
This step is last on purpose. It prevents the next leak. It does not fix this one.
Step 7: Tighten the permissions while you are here
If an account named for storage had control of your production compute, fix that too. Rotating a key that is over-privileged just gives you a fresh over-privileged key.
Conclusion
The instinct when you find a secret in Git is to delete it and feel better. That instinct produces a clean-looking repository and an unchanged risk. History is permanent, clones are everywhere, and backups are built to keep things forever. The only thing that genuinely helps is replacing the credential, and doing it in an order that does not take production down on the way. Check whether it is live, measure what it can do, roll a new key in, verify, then turn the old one off. Cleaning the repository is the last step, not the first.
Merits
- The detection check is a single command and works on any repository
- Rotation is a permanent fix, unlike file deletion or history rewriting
- Rolling over with a second key means no downtime for live systems
- Auditing permissions during rotation often reveals over-privileged accounts
- Moving secrets into an existing secret store usually requires no new tooling
- The whole process is reversible until the final delete
Demerits
- Rotation requires finding every consumer of the key, which is rarely documented
- You cannot undo the original exposure, only limit what it is worth
- History rewriting breaks existing clones and is disruptive for the team
- Secrets already copied to laptops and backups are outside your control
- Cleanup competes for time with feature work and is easy to postpone
- Some older systems make credential rotation genuinely awkward
Caution
This article is educational and describes a general approach rather than a prescription for your environment. Every command, file path, account name and key identifier here is a placeholder and must be replaced with values from your own systems. Rotating credentials can interrupt live services if a consumer is missed, so verify each step in a non-production environment first, confirm what depends on a key before deactivating it, and check the current documentation for your cloud provider and tooling before relying on anything written here.
Frequently asked questions
- Does deleting a secret from a Git repository remove it? — No. The value stays in the commit history and in every existing clone, so anyone with the repository can still read it.
- Is rewriting Git history enough to fix a leaked secret? — No. It cleans the repository but does nothing about copies people already cloned, so the credential must still be rotated.
- How do I check whether a config file is tracked by Git? — Run
git ls-files --error-unmatch <path>. If it succeeds, the file is tracked and its contents are in history. - Should I delete a leaked key immediately? — Only if it is unused. If something in production depends on it, create a replacement first, deploy, verify, then deactivate the old one.
- How do I know if a leaked cloud key is still being used? — Most providers expose a last-used timestamp for access keys, which tells you whether a consumer still depends on it.
- Where should application secrets live instead? — In a secret store or your build system's credential store, injected at deploy time so the value never appears in source control.
- Why did the deployment pipeline not protect the secret? — Pipelines often inject only some settings, such as database connection strings, and leave every other value exactly as committed.
- How can I stop this happening again? — Add config files to
.gitignore, enable automated secret scanning on your repositories, and review permissions so leaked keys are worth as little as possible.
Tags
#Security #DevOps #Git #SecretsManagement #CloudSecurity #IAM #KeyRotation #DevSecOps #ConfigManagement #InfrastructureAsCode
Incident Response: First Hour
A calm, evidence-preserving checklist for establishing control, bounding impact, communicating clearly, and containing an incident safely.
Free. No spam — unsubscribe in one click.


Responses
Sign in to leave a response.