A Token Got Into History — And It Still Prints
Goal
You actually lift a secret file and a large file out of the history. And you also confirm what remains even after lifting them out.
Why it matters
Git is a content-addressed store. Every file becomes a blob object, and as long as the trees and commits pointing to that blob remain, the object stays alive. So git rm means only "gone from now on", not "it never happened". If you do not know this difference when a secret has gone in, you delete it, commit, feel safe, and keep using the token. This lab shows that difference in command output and builds the habit of the order in which you really remove it (rewrite → remove backup references → expire the reflog → gc). The last step is the conclusion — since copies that have already spread keep it as it is, for a secret revoking comes before cleaning.
Steps
- Create at
/root/gitx6/repoa history that mixes a token file and a 900 KB chunk, and include even the commit that deletes it from the working tree. - Take a colleague's copy
/root/gitx6/peerin advance, and leave the current state innotes/before.txt. - Rewrite all refs with
git filter-branch. - Write in
notes/original.txtwhy the objects still remain, and strip away those references. - Expire the reflog and really delete with
gc --prune=now, then leave the size innotes/after.txt. - Leave in
notes/rewrite.txtthe fact that every hash has changed. - Make
/root/gitx6/peerfollow the new history, and write innotes/collab.txtwhether the secret remains in that copy. - Summarize the incident response order in
notes/report.md.
Notes
- Make
conf/prod.envwith the two lines below. They are all synthesized fake values, and later steps check whether the blob with this content has disappeared, so do not change a single character. They areLABHUB_FAKE_TOKEN=deadbeefdeadbeefdeadbeefdeadbeefandDB_PASSWORD=not-a-real-password-1234. - Fix the commit times with
GIT_AUTHOR_DATEandGIT_COMMITTER_DATE. git filter-branchshows a warning and waits 10 seconds. If you putFILTER_BRANCH_SQUELCH_WARNING=1in front, it is skipped.- Common mistake: stopping at step 3 and thinking you are done. As long as
refs/original/remains, not a single object disappears. - Common mistake: deleting
peerand cloning again in step 7. This lab is about seeing what remains in a copy that has already spread, so you must make the existing copy follow along as it is.
Build a history mixing a token and a large chunk
Create at /root/gitx6/repo a history that mixes a token file and a 900 KB chunk, and include even the commit that deletes it from the working tree.
Stack five commits — the app file, conf/prod.env, assets/blob.bin, an app edit, and then the git rm conf/prod.env commit. In the middle, also branch off a side branch and attach an annotated tag v1. To see later that the rewrite touches all refs, you need branches and tags. The content of conf/prod.env must be exactly the two lines written in ## 참고 of the instructions — because later steps check by content whether that blob has disappeared.
Even though you deleted it, the original text comes out
Take a colleague's copy /root/gitx6/peer in advance, and leave the current state in notes/before.txt.
First take the pre-rewrite copy with git clone /root/gitx6/repo /root/gitx6/peer (you will use it in step 7). Then measure the size with git count-objects -vH, see whether the two paths appear in git rev-list --objects --all, and put into the file even that the token's original text comes out as it is with git cat-file -p <커밋>:conf/prod.env. You must also write down the current HEAD hash so that you can compare it in step 6.
Rewrite all refs
Rewrite all refs with git filter-branch.
Give --index-filter the command git rm -r --cached --ignore-unmatch <경로들>, and use --prune-empty, --tag-name-filter cat and -- --all together. If you give only one branch, side and v1 remain holding on to the old commits. Skip the 10-second warning with FILTER_BRANCH_SQUELCH_WARNING=1.
Not a single thing has disappeared yet
Write in notes/original.txt why the objects still remain, and strip away those references.
Even though the rewrite is finished, the two paths still appear in git rev-list --objects --all. If you look at git for-each-ref refs/original, you can see the reason — filter-branch backed up all the original refs so that you can undo. List them and delete them one by one with git update-ref -d.
Empty the reflog and really delete
Expire the reflog and really delete with gc --prune=now, then leave the size in notes/after.txt.
The reflog is also a reference — empty it with git reflog expire --expire=now --expire-unreachable=now --all. Then run git gc --prune=now. Without --prune=now, deletion happens only after the default grace period. Write the git count-objects -vH before and after side by side so that it shows how much the size shrank.
Every hash has changed
Leave in notes/rewrite.txt the fact that every hash has changed.
Look for the old HEAD hash you wrote down in step 2 in the current repository with git cat-file -e — it is not there. A commit hash is calculated including its parent and tree, so if you fix one thing at the start of the history, everything after it becomes different. Also write which commit the tag v1 points to now.
It is still there in the copy that was already taken
Make /root/gitx6/peer follow the new history, and write in notes/collab.txt whether the secret remains in that copy.
In peer, if you run git fetch, origin/main changes to the new history. Make it follow along with git reset --hard origin/main. Then look for the secret blob in that copy with git cat-file -e. The conclusion that comes out here is the point of this lab — write what you must do first because of that.
Organize it as an incident response order
Summarize the incident response order in notes/report.md.
Write it in a numbered order. What is first is the most important, and also add one line each on why the four cleanup steps (rewrite, remove backup references, expire the reflog, gc) are in that order. Do not forget what must be announced to the team.