Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXV · Secrets in GitMentalModel

The secret leak fallacy — why deleting the file does not delete the secret

Advanced⏱ ~22 mingitgitleaks

What you'll learn

  • Explain why removing a file from the working tree does not remove it from history
  • Identify the four places a secret can persist after a deleted-file commit
  • Distinguish the forensic-cleanup task from the rotation task and explain why cleanup alone is insufficient
  • Recognise the false reassurance produced by a clean subsequent commit

Prerequisites

Practice

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

An engineer commits a .env file with a production API key, runs git rm .env two minutes later, and pushes the deletion. The working tree is clean. Six months later, secret-scanning fires on a public mirror: the key was indexed between the original commit and the deletion. The “deletion” never deleted anything.

The mental-model error

The error is treating a commit as a mutable file. A Git commit is a content-addressed immutable object identified by a SHA. Recording a deletion is a new commit pointing to a tree without the file. The original commit and blob still exist; the hash still resolves to the same bytes.

flowchart LR
    A["commit A: contains .env with API_KEY"] --> B["commit B: removes .env"]
    B --> C["HEAD: clean working tree"]
    A --> D["blob: still contains API_KEY"]
    A --> E["pack file: still contains API_KEY"]
    A --> F["reflog: still references commit A"]
    A --> G["forks and clones: already received"]

The “clean HEAD” is a property of the most recent commit, not of the repository. The repository holds every commit ever made; the deletion commit is a new node pointing away from the leak.

The four places a secret hides

After a deletion commit, the secret persists in:

  • Original commit and blob. Immutable, reachable from the reflog and any pre-deletion tag.
  • Pack file. Unreachable objects are kept for gc.pruneExpire (default 14 days) and linger in backups or forge caches.
  • Reflog. Every clone retains the old commit reference until the reflog expires.
  • Forks, clones, mirrors. Every git clone between the leak and the deletion downloaded the full history. The forge cannot reach clones owned by other users.

The four places are not independent. Cleanup that addresses one does not address the others.

Why cleanup is forensic, not preventive

Cleanup rewrites history to reduce the surface for future discovery; it does not affect any secret already discovered. Rotation reissues the credential and revokes the old one; rotation makes the secret stop working. The order is forced: rotate first, then rewrite history, then audit consumers (CI logs, error reporters, backups, deploy artifacts). The alternative — cleanup without rotation — leaves a working credential and a team that believes the incident is closed.

Production discipline

  1. A committed secret is a compromised secret. The credential is in every bot, backup, and prior clone. The deletion commit is a record of intent, not a revocation.
  2. Rotation is the fix; history rewrite is hygiene. The credential is the access; the access is what the attacker uses.
  3. Detection is the only preventive measure that matters. Prevention by design is the last lesson in this part.

Cross-course references

  • Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) covers rotation patterns.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) covers vault posture.
  • Terraform for Production Sysadmins - Parts IX-XII (State) cover committed .tf and out-of-band state.

Quiz

Knowledge check · 4 questions

  1. Q1. Why is a secret still compromised after `git rm .env` and a deletion commit?

  2. Q2. Cleaning history with git filter-repo is not sufficient to neutralise a leaked secret.

  3. Q3. Name the four places a secret persists after a deletion commit, and pick the one least under the team's control.

  4. Q4. An engineer committed and pushed a .env file with a production database password, then deleted it. Decide the response order.

    T0: engineer commits .env with DATABASE_URL. T0+1m: engineer commits deletion. T0+1h: team learns from a secret-scanning alert. The team has 30 forks, the CI ran three jobs against the leaked commit, and a backup snapshot from T0+15m exists on S3. The password is in use by three production services.

Passing score: 75%. Answers are checked in this browser.