Find the key in the history, then stop the next one at commit
Task
Commit a fake cloud key to a repository, remove it in a later commit, and show that it is still recoverable from the history. Then write a pre-commit check that refuses the next one, and record the response order the lesson gives: revoke and rotate first, clean up afterwards.
Steps
- Create the repository with
git init /tmp/secrets-lab, then give it a local identity:git -C /tmp/secrets-lab config user.email lab@example.comandgit -C /tmp/secrets-lab config user.name lab. - Add
app.confcontaining a fake key in the shape of a cloud access key ID --AKIAfollowed by sixteen capital letters or digits, for exampleaws_key=AKIALABFAKE000000042-- and commit it the way a developer in a hurry would. - Remove the line in a second commit with the message
remove key, and confirm the current files no longer contain it. - Now search the history:
git -C /tmp/secrets-lab log -p | grep -E 'AKIA[0-9A-Z]{16}'. The key is still there -- and in a shared repository it would also be in every clone made in between. - Write a pre-commit hook at
/tmp/secrets-lab/.git/hooks/pre-commitand make it executable. It inspects the staged changes (git diff --cached) and exits non-zero, printing which rule matched, if they contain a string matchingAKIA[0-9A-Z]{16}or a line that begins-----BEGINand containsPRIVATE KEY. - Try to commit a second fake key and confirm the commit is refused.
- Write
/tmp/secrets.md: the response order had the key been real (revoke and rotate it, check the provider's logs for any use, then clean the history), why removing it in a later commit was not enough, and where the value should live instead.
Verify
git -C /tmp/secrets-lab log -p | grep -cE "AKIA[0-9A-Z]{16}"
git -C /tmp/secrets-lab grep -cE "AKIA[0-9A-Z]{16}" HEAD || echo "0 in the current tree"
python3 - <<'PY'
import pathlib,subprocess
repo='/tmp/secrets-lab'
def git(*a):
return subprocess.run(['git','-C',repo]+list(a),capture_output=True,text=True)
before=git('rev-parse','HEAD').stdout.strip()
probe=pathlib.Path(repo,'probe.conf')
probe.write_text('key=AKIA'+'PROBE00000000099'+'\n')
git('add','probe.conf')
c=git('commit','-m','probe')
after=git('rev-parse','HEAD').stdout.strip()
if after!=before:
git('reset','--soft','HEAD~1')
git('reset','-q','probe.conf')
probe.unlink()
print('commit exit code with a key staged:',c.returncode)
assert c.returncode!=0 and after==before, 'the hook let a recognisable key be committed'
print('the pre-commit hook refused the key')
PY
grep -ciE "revoke|rotate" /tmp/secrets.md
The first must be non-zero: the key survives in the history even though the second commit removed it. The second must find nothing in the current tree. The assertion stages a fresh fake key, tries to commit it, and requires your hook to refuse -- then undoes its own change, so the repository is left exactly as you had it. If the commit went through, the hook is not executable or its pattern does not match.
Notes
Committing is only one of three places to scan. Repository platforms can refuse a push that contains a recognised secret, and a scan of the whole history finds what was committed before either check existed. None of the three makes a leaked key safe -- only revoking it does, which is why rotation comes before cleanup and why the lasting fix is a vault the application reads at runtime.
This is an independent study companion for CompTIA Security+ SY0-801 and is not produced by or endorsed by CompTIA.