Try it: find a secret in git history, then remove it properly

Reading · 20 min · Module 6, lesson 3 of 530 min left in this module

Module 6 · SecretsLesson 3 of 5

Goal: Find a key that's gone from the latest files but still in history, rotate it first, then rewrite the history and prove it's gone.

You need Git set up as in lesson 2.5.1, a terminal, and Homebrew or Python 3 to install one tool. Nothing is pushed.

About the sample

learn-leaky-config is a tiny Node app with three commits on main: Initial app, Connect payments, which hard-codes a key in config.js, and Read the key from the environment, which fixes the code. The key, demo-not-a-real-key-0000, is fake and works nowhere.

You don't need Node; you never run the app. git log -p shows its output through a pager: press q to leave it.

  1. Get the sample

    git clone https://github.com/computesphere-samples/learn-leaky-config.git
    cd learn-leaky-config
    git log --oneline
    

    You should seeThree commits, newest first: Read the key from the environment, Connect payments, Initial app.

  2. Search the latest files

    git grep demo-not-a-real-key
    cat config.js
    

    git grep searches the files in the latest commit. By this check, the repository looks clean.

    You should seeNothing printed. config.js reads process.env.PAYMENT_API_KEY.

  3. Search the history

    git log -p -S "demo-not-a-real-key-0000"
    

    -S lists every commit that added or removed that text, and -p shows the change. Anyone who clones the repository can run this.

    Check yourself

    The latest code reads the key from the environment. Why is the key still exposed?

    You should seeTwo commits: Read the key from the environment removes the key (a - line) and Connect payments adds it (a + line).

  4. Rotate the key first

    With a real key, you'd do this before touching history: create a new key at the provider, store it as a secret in your app's settings and redeploy, then revoke the old key. From then on, every copy of the old key is useless.

    You should seeNothing to run: this key is fake. With a real one, the old key would now be revoked.

  5. Install git-filter-repo

    With Homebrew:

    brew install git-filter-repo
    git filter-repo --version
    

    You should seeA short version code, such as a40bce548d2c, instead of an error.

  6. Rewrite the history

    Create a file outside the repository saying what to replace, then run the rewrite:

    echo 'demo-not-a-real-key-0000==>REMOVED' > ../replacements.txt
    git filter-repo --replace-text ../replacements.txt
    

    In PowerShell, create the file with Set-Content ..\replacements.txt 'demo-not-a-real-key-0000==>REMOVED'.

    Every commit is rewritten with REMOVED in place of the key. Removing origin is deliberate: it stops you pushing rewritten history by accident.

    You should seeA notice that the origin remote was removed, then Completely finished.

  7. Prove it's gone

    git log -p -S "demo-not-a-real-key-0000"
    git show HEAD~1:config.js
    git log --oneline
    

    Then delete ../replacements.txt: it contains the key.

    You should seeThe search prints nothing, and the old config.js now says REMOVED. The last two commit IDs differ from step 1.

Why rotation came first

Your copy is clean, but nobody else's is. Everyone who cloned before the rewrite still has the old commits, and so do forks and any system that cached the repository.

Publishing the fix means a force-push, which replaces the branch on the server. Everyone else must then clone again or reset. Don't push here; the sample stays leaky for the next learner.

A key that was revoked first is harmless in all those copies. That's why cleaning history is tidying up, and rotation is the fix.

If filter-repo refuses to run

It prints Refusing to destructively overwrite repo history since this does not look like a fresh clone when the repository has changes since it was cloned. That's a safety check. Delete the folder, clone again, and start from step 1.