Secrets in Git History
Deleting the line does not delete the secret.
What Is Secrets in Git History?
Deleting a hardcoded credential in a new commit does not remove it. Git keeps every version of every file, so the value still sits in the commit before the cleanup. The current code looks clean, which is why these leaks survive for years. You'll find the commit that claims to have removed a production API key, read it from the commit before, and replay it against the live admin API. Then you'll scrub history with git filter-repo, in the wrong order on purpose, and learn why rotation comes first.
What You'll Learn in Secrets in Git History
- Recognize that deleting a hardcoded secret in a new commit does not remove it: git retains the value in every earlier commit and in the reflog, so anyone who clones the repo can read it
- Follow an attacker path from cloning a public repo, to listing a file's commit history, to showing the commit before a removal, to replaying a live API key that was never rotated
- Understand that rewriting history cannot recall a secret that was already cloned or scanned, so rotating the credential is the only step that actually invalidates the leaked value
- Prioritize credential rotation as the primary fix and apply history rewriting with git filter-repo or BFG and a forced remote update to strip the secret from every commit as cleanup, not as the fix
- Prevent recurrence by keeping secrets out of source with environment variables and adding a pre-commit secret-scanning hook so a credential is blocked before it ever enters the repository's history
Secrets in Git History — Training Steps
-
Size up the target
Today Bob is targeting Larkfell, a developer-platform company that runs a lot of its work in the open. He starts where anyone would, on the public repository, sizing up what the company ships before he looks for a way in. An open-source repo is a gift to someone like Bob: the whole project, every file, and every version of every file, all downloadable.
-
Clone the repo
Bob clones the repository to his own machine so he can search the whole project offline, at his own pace. The clone he pulls down is not just the current files. It is the entire history, which is exactly what he is after.
-
Check the current code
Before digging, Bob does what a careful reviewer would: he opens the current production config to see whether anything obvious was left in it. It is clean. The admin key is read from an environment variable, not written into the file, so nothing is hardcoded here. A casual look would stop right there.
-
Walk the history
A clean file at HEAD says nothing about the past. The key is gone from the current code, but git kept every earlier version of that file. Bob knows this, so he lists the commit history of the config to see what it used to contain, not just what it holds now.
-
The key is still there
Bob shows the commit that added the config, the one right before the cleanup. The removal commit deleted the line from the current code, but this earlier commit still holds it. The production admin API key is sitting in the diff in plain text, exactly as it was before someone tried to take it out.
-
The key still works
A leaked key is only a theory until it opens something. Bob points the recovered key at Larkfell's admin API, carrying it in an Authorization header the way a real client would. The key was never rotated after the cleanup commit, so the value from that old commit is still live.
-
The customer list is open
The server trusted the key and answered. Bob is reading Larkfell's internal customer list from a public API, with no account of his own.
-
Knowledge check
You just watched a deleted secret still open a production API. Lock in why.
-
The alert lands
You own Larkfell's backend repo. Overnight, the secret-scanning service that watches your public repositories fired an alert and emailed it to you. The finding is blunt: a live production API key is present in the public commit history. It is not in the current code, but the scanner walked the history and found it in an earlier commit.
-
Gone from HEAD, alive in history
Your first instinct is to check the current code, and the key really is gone from HEAD. Someone already deleted the line in a later commit. That did not help. You show the earlier commit yourself, and the live key prints right back out. It survives in every commit that still contains it, so deleting the line from the latest version left the credential fully readable to anyone who cloned the repo.
Security Framework Coverage
CWE
- CWE-540 Inclusion of Sensitive Information in Source Code
- CWE-522 Insufficiently Protected Credentials
MITRE ATT&CK
- T1552.001 Unsecured Credentials: Credentials In Files
- T1213.003 Data from Information Repositories: Code Repositories
CIS Controls
- CIS 3 Data Protection
- CIS 16 Application Software Security
NIST CSF
- PR.AT-02 Individuals in specialized roles are provided with awareness and training so that they possess the knowledge and skills to perform relevant tasks with cybersecurity risks in mind
- PR.DS Data Security
- PR.PS Platform Security