Secrets in Git History

A production admin API key, deleted from the latest code, still lives in an old commit. Watch an attacker recover it from git history and use it, then rotate the key first, because scrubbing history cannot recall a secret that was already cloned.

What Is Secrets in Git History?

Secrets in git history is the failure that occurs when a credential is hardcoded into source, later deleted in a new commit, and assumed to be gone, when git has actually retained the value in every earlier commit and in the reflog. Anyone who clones the repository can walk the history and read the secret, even though the latest code looks clean. It is distinct from committing a whole secret file, such as an .env or a private key, where the file itself is the artifact; here the leak is a hardcoded secret literal sitting inside otherwise ordinary source code. This exercise puts you on both sides. As Bob, a financially motivated attacker who scrapes public code hosts, you clone Larkfell's open-source api-gateway repo, list the commit history of the production config even though the current files look clean, find a commit that claims to have removed a hardcoded production API key, show the commit just before it where the live key is still in plain text, and replay that key against Larkfell's admin API to pull the customer list, because the key was never rotated after the cleanup commit. As Alice, a platform engineer at Larkfell who owns the backend repo, you receive a secret-scanning alert by email, confirm the key is gone from HEAD but survives in an earlier commit, and then work the remediation and its order the hard way: you scrub the secret from history with git filter-repo and force-push, replay the old key and watch it still return the customer list, and only then rotate the credential in the developer console, after which the same key finally returns 401. The lesson lands through that twist: rewriting history cannot recall a secret that was already cloned, so rotation is the fix that actually cuts access, and it should come first. You close by keeping the secret out of source with an environment variable and adding a pre-commit secret-scanning hook so a credential can never enter history again. The exercise ends with quiz questions on why a delete commit does not remove a secret, why rotation comes before history rewriting, why git filter-repo differs from a plain deletion, and how to keep a credential out of history in the first place.

What You'll Learn in Secrets in Git History

Secrets in Git History — Training Steps

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. Knowledge check

    You just watched a deleted secret still open a production API. Lock in why.

  9. 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.

  10. 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.