TT Lab
Get started
Learn Learning paths Courses

Git in Practice

A Token Got Into History — And It Still Prints

Continue in TT Lab

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

  1. 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.
  2. Take a colleague's copy /root/gitx6/peer in advance, and leave the current state in notes/before.txt.
  3. Rewrite all refs with git filter-branch.
  4. Write in notes/original.txt why the objects still remain, and strip away those references.
  5. Expire the reflog and really delete with gc --prune=now, then leave the size in notes/after.txt.
  6. Leave in notes/rewrite.txt the fact that every hash has changed.
  7. Make /root/gitx6/peer follow the new history, and write in notes/collab.txt whether the secret remains in that copy.
  8. Summarize the incident response order in notes/report.md.

Notes

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.