Three weeks ago, a teammate on a side project pushed a .env file with a real Stripe key in it. The key was rotated within the hour, but the commit sat in history for 41 minutes — long enough that I lost a week of sleep over it.
That was the moment I stopped treating "we should scan for secrets" as a someday item. It also kicked off a two-month search for a scanner I actually liked, which turned out to be harder than it should be, because the three tools I kept coming back to — Gitleaks, TruffleHog, and my own small project DotGuard — all solve slightly different versions of the same problem.
Choosing a secret scanner by vibe is a bad idea. So I did what any reasonable developer would do: I ran all three against the same 50 repositories and wrote down exactly what happened.
This is the full breakdown, including the numbers that embarrassed me.
TL;DR: TruffleHog found everything (and verified what was live), Gitleaks was the balanced all-rounder, and DotGuard was the fastest with the cleanest report — on my test set, at least. Read on for the misses, the false positives, and the verdict.
The test setup
I assembled 50 repositories:
- 20 public OSS projects — clean, no secrets. The control group.
- 20 "realistic" repos — built from stripped copies of apps I've worked on, with fake keys that look real.
-
10 trap repos — where I planted 60 secrets across 7 formats:
- AWS access key + secret pairs
- GitHub personal access tokens
- RSA private keys
- Postgres and MongoDB connection strings
- Slack webhook URLs
- Hardcoded JWT signing secrets
- Generic hex API keys (32 and 64 chars)
Placement mattered as much as format. I hid the secrets in .env, .env.production, a committed .env.bak, a base64-encoded string inside a deploy script, a Dockerfile build argument, a YAML config file — and, the important ones, in git history: secrets that existed in an old commit and were "fixed" by a later one.
Each tool ran with its default config where a sane default existed: Gitleaks with its default ruleset, TruffleHog in verification mode, DotGuard with dotguard scan. I ran each tool three times and took the median.
The three contestants
Gitleaks is the de-facto standard. A single Go binary, regex plus entropy heuristics, one of the largest community-maintained rulesets in the space, and git-history scanning out of the box. If your security team asks "what are you using for secrets?", Gitleaks is the answer that ends the conversation.
TruffleHog plays a different game. It still patterns, but its signature feature is verification: it takes candidate secrets and checks them against provider APIs to see if they're live. That turns "this looks like an AWS key" into "this is an AWS key and it works right now." TruffleHog is also the tool people reach for on cloud asset discovery — hunting for credentials scattered across infrastructure, not just in git.
DotGuard is my project (@wuchunjie/dotguard), and it holds one very specific opinion: env file leaks are the most common, most preventable, and most under-scanned class of secret exposure, and most scanners treat .env files as just another file. DotGuard is an npm package — one install, zero config to start, JSON-native output — designed to run on every push in under a minute.
Full disclosure before the numbers: I built DotGuard. Read its results with that in mind. The Gitleaks and TruffleHog results are me running them as a user, not a maintainer.
Detection: what did they actually find?
Median run across all 50 repos:
| Tool | Secrets found (of 60 planted) | False positives | Total runtime |
|---|---|---|---|
| Gitleaks | 57 | 9 | 4m 32s |
| TruffleHog | 60 | 2 | 11m 08s |
| DotGuard | 58 | 0 | 47s |
Now the fine print, because the fine print is where the real differences live.
Gitleaks missed two. A base64-encoded AWS secret buried in a deploy script (its rules don't decode base64 payloads by default), and a MongoDB connection string with an unusual auth mechanism that landed just below its entropy threshold. Both are edge cases, and both are exactly the kind of thing community PRs fix over time — that ruleset is years of accumulated leaks.
TruffleHog found all 60. But "found" needs a footnote: 41 of the 60 came back as verified (I'd set up test endpoints where possible, and the GitHub/AWS-style tokens passed provider format validation), while 19 were reported as unverified candidates — mostly the hex keys, which have no live endpoint to check against. In verification mode, TruffleHog's real value is that 41/19 split, not the raw 60.
DotGuard found 58 and reported zero false positives on this set. The two misses: the base64-encoded AWS secret (the same blind spot, since patched — the current release decodes base64 layers before matching) and one 64-char hex key sitting next to a wall of random test fixtures, where the local entropy context pushed the confidence score just under threshold.
The false-positive column is where the tools diverge most. Gitleaks flagged 9 strings that were not secrets: UUIDs in test fixtures, a 32-char hash in a changelog, a couple of base64 image headers. TruffleHog flagged 2. DotGuard flagged none — which is partly design (its scoring weighs format, location, and entropy together instead of entropy alone) and partly luck (my fixtures happen to sit below its tuned threshold). To be fair: on a repo stuffed with random hex, DotGuard will flag more. The zero is a property of this dataset, not a universal claim.
Git history: where leaks actually live
The 12 secrets I planted in git history — present in an old commit, deleted in a later one — are the real reason you scan history instead of just the working tree. A rotated key in HEAD means nothing if the old value is still sitting in history, pullable with git log -p.
All three can scan history, but the ergonomics differ:
- Gitleaks walks all git objects by default. Thorough, and the main reason its runtime is what it is.
-
TruffleHog targets commits since a reference (
--since-commit) and pairs the findings with verification. -
DotGuard scans full history with
dotguard scan --history, and it was the only one of the three that collapsed the same secret appearing in multiple commits into a single finding withfirst_seen/last_seenranges. That's how you want to read a report — not as four identical alarms.
If your threat model is "someone committed a key two years ago," all three will find it. If you also want the report to be readable, the dedup behavior matters as much as the raw detection count.
Speed: 47 seconds versus 11 minutes
Median runtimes across the 50 repos:
- DotGuard: 47s
- Gitleaks: 4m 32s
- TruffleHog: 11m 08s
The gap with TruffleHog is structural: verification means network round-trips to provider APIs, and that's the feature. You're paying for the certainty of "this key works." On a large monorepo, an 11-minute gate is real, and most teams end up running TruffleHog nightly rather than per-push.
Gitleaks' time is mostly spent walking git history across 50 repos. Perfectly fine.
DotGuard's comes from three things: a single-pass walker that reads each file once, no per-provider API calls, and parallelism across files. The design goal was "runs on every push, on every repo, in the time it takes to order a coffee." Forty-seven seconds on 50 repos leaves plenty of headroom for a 500-repo monorepo in a CI lane.
CI integration
Gitleaks has first-class support: an official GitHub Action, a one-line step, and it ships in most pre-commit hook bundles.
TruffleHog also has an official action, and verification mode works well in CI when you pass the provider scopes you care about.
DotGuard is one YAML block:
- name: Scan for secrets
run: |
npm install -g @wuchunjie/dotguard
dotguard scan --ci --json ./
The --ci flag exits non-zero on any finding and writes a machine-readable JSON summary to the workflow log, so you can pipe it into whatever reporting you already have. No config file required for a first run; there is one if you want custom rules or paths to ignore.
So which one should you use?
Straight answers, no hedging:
- Gitleaks — the safe default. If you need a scanner your security reviewer, your auditor, and your CI all already recognize, this is it. Battle-hardened, huge community, active maintenance.
- TruffleHog — when you need verification instead of pattern matching. Live credential discovery, cloud audits, "prove this key is real" — nothing else in this test does that.
- DotGuard — when env file leaks are your top risk, you want a sub-minute gate on every push, and you'd rather read JSON than parse a text report. It's the youngest tool here with the smallest ruleset, and it's my project. The table above is one dataset, run by its author. Use it if the shape fits your workflow — and judge it on your repos, not mine.
A pragmatic setup that costs nothing: Gitleaks in pre-commit for the fast local path, DotGuard in CI on every push for the env and history sweep, and TruffleHog nightly with verification on the repos that touch production credentials. Three tools, three jobs, none of them running when they don't need to.
Trying DotGuard
npm install -g @wuchunjie/dotguard
# scan the current repo (working tree + git history)
dotguard scan --history .
# CI mode: JSON output, non-zero exit on findings
dotguard scan --ci --json .
If you run it against a repo and find a secret it should have caught — or a string it shouldn't have flagged — that's a bug report with a payoff: I read every issue, and fixes usually land within the week. That's the entire maintenance model of a small open-source tool. It's also why I'd want your test results, not just my test results.
Package page: https://www.npmjs.com/package/@wuchunjie/dotguard
The part no scanner can benchmark
One last thing, because it matters: none of the three tools catches a secret that never made it into the repo in the first place — the Slack message, the screenshot, the copy-paste into a support ticket. Gitleaks, TruffleHog, and DotGuard all answer "what's in git?" They don't answer "what got out?"
The 41 minutes my Stripe key lived in history were preventable with any of these three. The fix was never a better scanner. It's running one, on every push, starting today.
Top comments (0)