Reverting the commit didn't remove the secret
Someone committed a secret at work and removed it in the next commit. Git still remembered it, so I built Redflag.
Someone at work committed secrets to one of our repositories.
They caught the mistake and followed it with another commit that removed them. The latest version of the code was clean, so it looked like the problem had been fixed.
It hadn’t.
The original commit was still in Git history, with the secrets sitting exactly where they’d first been added. Removing them from the next version of the file didn’t remove them from the repository.
The secrets had to be rotated. There was no clever way around that. But I kept thinking about catching the mistake earlier, before there was anything to rotate.
That became Redflag.
Catching both sides of the mistake
I wanted Redflag to work in two places.
The first was before a commit. It can install a pre-commit hook and scan code while the mistake is still local:
redflag install-hook The second was after the fact. A hook only helps on machines where it has been installed, and it can’t do anything about secrets already buried in an old branch. I wanted to be able to point the same tool at a repository and make it inspect the history too.
# Scan the files as they are now
redflag scan .
# Scan those files and the repository history
redflag scan . --git-history That also made periodic scans possible. Run it in CI, or on a schedule, and check again for things that got past the hook.
Rust, because I was learning Rust
I was already trying to learn Rust. There was so much hype around the language at the time, and I wanted to get past small exercises and use it on a real problem.
A command-line scanner was a good fit. It involved files, regular expressions, Git objects, terminal output, configuration, and enough awkward edge cases to force me to learn more than the happy path.
I used the git2 crate, which wraps libgit2, for reading repositories.
Scanning the current folder was fairly straightforward. Git history was the interesting part. A repository isn’t just a stack of old folders. Commits branch and merge, the same content can appear more than once, and scanning every commit in a large repository can take a while.
Redflag ended up with controls for which branches to inspect, how far back to go, and which dates to include. When it finds something in history, it keeps the commit hash, author, and date with the finding. Otherwise you know a secret existed at some point but still have to dig through Git to find it yourself.
The false-positive problem
Walking Git history took work, but false positives were the hardest part.
Redflag looks for known secret formats with regular expressions. It also uses Shannon entropy to catch long, random-looking strings that don’t match one of those formats.
The problem is that source trees are full of long, random-looking strings that aren’t secrets.
Hashes, generated files, test fixtures, minified code, and example tokens can all set a scanner off. Make the rules too strict and you miss things. Make them too loose and the tool spends all day shouting at people about files they already know are safe.
Once that happens, people stop reading the output. At that point the scanner may as well not be there.
I added different exclusion policies so a repository didn’t have to choose between scanning everything and ignoring a path completely:
[entropy]
enabled = true
threshold = 3.8
min_length = 24
[[exclusions]]
pattern = "**/node_modules/**"
policy = "Ignore"
[[exclusions]]
pattern = "**/test-fixtures/**"
policy = "ScanButAllow"
[[exclusions]]
pattern = "docs/examples/**"
policy = "ScanButWarn" Some paths can be skipped. Some can still produce a warning. Others can be scanned without failing the whole run. It isn’t exciting work, but it is the difference between a scanner people can leave switched on and one they remove after a week.
One more check before the push
Redflag isn’t going to make it impossible to commit a secret. Hooks can be skipped, patterns can miss things, and anything that has already been exposed still needs to be rotated.
What it can do is put another check between a pasted key and the repository history.
If it catches the mistake while it is still sitting on someone’s laptop, fixing it means editing a file. That is a much better problem than rotating credentials and cleaning up a repository after the push.