~$ skillshelf
← OverTheWire Bandit

Bandit 28 → 29: the file was redacted, the history wasn't

banditlinuxgit

Last level, git was only the transport — one commit, one file, password sitting in the working tree. This is the same setup with the puzzle switched on. The clone works first try, the README is right there, and the password field has been replaced with xxxxxxxxxx. That looks like the level is broken. It’s the level working.

the goal

There is a git repository at ssh://bandit28-git@localhost/home/bandit28-git/repo via the port 2220. The password for the user bandit28-git is the same as for the user bandit28. From your local machine (not the OverTheWire machine!), clone the repository and find the password for the next level.

Same wording as 27 → 28 apart from the username — the page tells you nothing about what’s different. localhost is still your own machine, so the host swap from last level still applies.

the approach

One file comes down, README.md, with the password line blanked:

## credentials

- username: bandit29
- password: xxxxxxxxxx

The first theory worth ruling out is .gitignore, and it’s worth ruling out explicitly: .gitignore decides which untracked files get added to the repository. It has no power to blank a line inside a file that’s already tracked, and anything that arrived in the clone is tracked by definition. Whatever emptied that field, it wasn’t ignore rules.

dead end: grep

The obvious search doesn’t work:

grep -rniH -- password .

Two reasons. The working files genuinely don’t contain it. And with nothing excluded it walks .git as well — git’s object store is zlib-compressed, so a plain-text search across it finds nothing useful and prints binary noise for a long time. Mine put close to 2 GB into a terminal log before I killed it.

That’s the real lesson of the dead end. grep reads files. The clone brought down more than files — the working directory is one view of the repository, and the whole thing was already on disk. The tool has to read the repository, not the view.

dead end: git-log

git help -a is one flat alphabetical dump and hard to scan. git help everyday groups commands by what you’re trying to do, and the standalone-developer list has exactly one line matching “see what happened”:

git-log(1) to see what happened.

Typed literally, that fails:

git-log
zsh: command not found: git-log

The hyphen is a man-page naming convention — git-log(1) is the manual page for the log subcommand, and (1) is the man section number, not something you type. You run it as two words.

the history

git log
commit 83d77407b76c9f86ac4e691a47618641c9d527ba (HEAD -> master, origin/master, origin/HEAD)
    fix info leak

commit 13bbc4d2414ffe0439b8ee4f5e5c2949780cf4b3
    add missing data

commit f3334fbccbf9446a6af88a3c71021c2f57163322
    initial commit of README.md

Three commits, and the messages give the whole game away. Someone added the data, then “fixed” the leak. The fix is the newest commit — the one I’m looking at in the working tree — and the thing it removed is still recorded in the commit that removed it.

git show with no argument shows HEAD, so it doesn’t even need the id:

git show
    fix info leak

 - username: bandit29
-- password: <redacted>
+- password: xxxxxxxxxx

The - line is the password. The + line is the xxxxxxxxxx from the working tree. A diff shows both sides, so the commit that deleted the secret is also the commit that displays it.

the takeaway

A clone is the whole repository, not the current files. Deleting a secret in a later commit doesn’t remove the secret — it removes it from the working tree while leaving it permanently recorded in the history, and anyone who can clone the repo can read it back. That’s not a Bandit trick; it’s the single most common way credentials leak out of real repositories. It’s also why the answer to a committed secret is rotate the credential, not push a follow-up commit. The follow-up commit is what tells people where to look.

Two smaller bricks:

Commit messages are a map. fix info leak isn’t a description, it’s an arrow. Read the log before running anything else — the history is searchable prose written by whoever made the change, and people describe what they did far more honestly than they realise.

Man pages name subcommands with a hyphen; you type them with a space. git-log(1) in documentation means you run git log. The (1) is the man section. Same for every git-* page you’ll ever read.