TruffleHog does something no pattern matcher can: it takes a candidate credential and asks the provider whether it works.
That single capability changes the shape of the output. A regex scanner produces a list of strings that look like credentials. TruffleHog produces a list separated into ones that are live and ones that are not — which is the only distinction that determines what you do next.
The short answer
Section titled “The short answer”{/* A remote repository */}trufflehog git https://github.com/OWNER/REPO
{/* A local repository — note the file:// scheme */}trufflehog git file:///path/to/repo
{/* A directory, ignoring Git */}trufflehog filesystem /path/to/directoryFilter the output by verification status:
trufflehog git file:///path/to/repo --results=verified,unknownAnd in CI, make a finding fail the job:
trufflehog git file://. --results=verified --fail--fail exits with code 183 when results are found — a distinctive value chosen so it cannot be
confused with an ordinary tool error.
Verification is the whole point
Section titled “Verification is the whole point”Every other scanner in this cluster answers “does this look like a credential?” TruffleHog answers “is this credential real?”
It does so with detectors — over seven hundred of them — each of which knows both a credential’s shape and how to test it against its provider’s API. A candidate AWS key gets an authenticated call to AWS; a candidate GitHub token gets a call to GitHub.
The results are classified:
| Status | Meaning | What it means for you |
|---|---|---|
| verified | The provider confirmed the credential works | An incident. Rotate now |
| unverified | Detected, and verification said no | Probably dead or fake. Still worth a look |
| unknown | Verification could not be completed — an error, a network failure | Treat as verified until you know better |
| filtered_unverified | Unverified and would have been filtered out | Visible only if you ask for it |
The default output is verified and unknown, which is a sensible default: it shows you what is live plus what could not be checked, and hides the noise.
What a finding looks like
Section titled “What a finding looks like”Real output, scanning a test repository containing a randomly generated, syntactically valid but
entirely fake GitHub token. The Raw result value has been replaced here — the tool prints the
credential in full, and publishing a well-formed token string is exactly what
push protection is designed to stop:
Detector Type: GithubDecoder Type: PLAINRaw result: ghp_REPLACED_FOR_PUBLICATION_SEE_NOTE_BELOWRotation_guide: https://howtorotate.com/docs/tutorials/github/Version: 2Commit: ef62a6fa2d5d53bb80b9957019ce7eafdb356b11Email: T <t@e.com>File: secrets.cfgLine: 1Repository: file:///path/to/repoTimestamp: 2026-09-01 07:01:01 +0000Several things in that block are worth noticing.
Raw result prints the secret. TruffleHog does not redact by default. That is deliberate — you
usually need the value to identify which credential it is — and it means the output is a file full of
credentials. Do not attach it to a ticket, and be careful where CI logs go.
Rotation_guide links to instructions for revoking that specific credential type. A small touch
that removes a real obstacle: knowing a key leaked and not knowing where to revoke it is a common
reason remediation stalls.
Decoder Type: PLAIN means the value was found as-is. TruffleHog also decodes base64 and other
encodings and re-scans the result, so this field tells you whether the secret was hiding.
Commit, file, line and author locate it precisely, which is what you need to scope a history rewrite.
Critically: this finding did not appear in the default output. The token is fake, verification
failed, and it was classified unverified. Only asking for unverified results surfaced it:
trufflehog git file://$PWD/repo --results=verified,unknown,unverifiedThat is the tool working correctly, and it is the single most important behaviour to understand before using it — the default view is deliberately a short list of things that are real.
The summary line
Section titled “The summary line”Every run ends with a structured summary:
finished scanning {"chunks": 4, "bytes": 233, "verified_secrets": 0,"unverified_secrets": 0, "scan_duration": "317.403202ms","trufflehog_version": "3.97.1", "verification_caching": {...}}verified_secrets is the number that matters. verification_caching reports how many verification
calls were avoided by caching — relevant on a large scan, where the same credential appearing in
fifty commits should not produce fifty API calls.
JSON output, for anything automated
Section titled “JSON output, for anything automated”Human-readable output is fine for a person reading a terminal. Anything else — a CI gate, a report, a
dashboard — should use --json, which emits one JSON object per finding.
The fields on each object, verified against version 3.97.1:
SourceMetadata SourceID SourceType SourceNameDetectorType DetectorName DetectorDescription DecoderNameVerified VerificationFromCacheRaw RawV2 RedactedExtraData StructuredData SecretPartsThree of those do most of the work:
Verified is a boolean, and it is the field every automation should branch on.
Redacted carries a partially-masked version of the value, which is what belongs in a report,
a notification or anywhere a human might read it. Raw is the full credential — useful for
identifying which key this is, and dangerous everywhere else.
DetectorName identifies the provider, which is how you route the finding to whoever owns that
system. VerificationFromCache tells you whether the verdict came from a cached verification rather
than a fresh call — worth knowing when a finding’s status looks stale.
A minimal gate that fails on live credentials without printing any of them:
trufflehog git file://. --results=verified --json --no-update > findings.jsoncount=$(jq -s 'length' findings.json)if [ "$count" -gt 0 ]; then jq -r '"\(.DetectorName): \(.Redacted)"' findings.json echo "::error::${count} verified credential(s) found" exit 1fiNote that it prints Redacted, never Raw. A gate that leaks the credentials it is protecting into
a log everyone can read has made the situation worse, and this is the most common way that happens.
Sources
Section titled “Sources”TruffleHog scans more than repositories, and the non-Git sources are frequently where credentials actually sit.
| Command | Scans |
|---|---|
trufflehog git | A Git repository, local or remote, including history |
trufflehog github | Repositories, organisations and users on GitHub |
trufflehog filesystem | Files and directories |
trufflehog docker | Container images, layer by layer |
trufflehog s3 | Object storage buckets |
docker is the one worth adopting deliberately. Credentials baked into an image at build time are
invisible to every Git-based scanner, survive in published layers, and are readable by anyone who can
pull the image. See Preventing .env commits for how
they get there.
Scanning a range
Section titled “Scanning a range”A full history scan on every pull request is slow and reports findings the author did not introduce. Scope it to the commits under review:
trufflehog git file://. --since-commit "${BASE_SHA}" --branch "${HEAD_REF}"What it doesScans only the commits between two revisions.
Why we run itA pull request check should report what this change introduced, not what the repository has always contained.
Expected resultFindings only from commits in the range, and a much shorter scan.
Pair it with a scheduled full scan. The pull request check keeps new credentials out; the scheduled scan covers everything the range-limited check never looked at.
CI integration
Section titled “CI integration”The official action:
name: secret scan
on: pull_request: schedule: - cron: "0 5 * * *"
jobs: trufflehog: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: fetch-depth: 0
- uses: trufflesecurity/trufflehog@v3 with: extra_args: --results=verified,unknownfetch-depth: 0 is required. A shallow clone has no history, so a history scan finds nothing and the
check passes — a green tick that means nothing was looked at.
Running the binary directly gives you version control over the scanner itself:
- name: Install TruffleHog run: | curl -sSL "https://github.com/trufflesecurity/trufflehog/releases/download/v${VERSION}/trufflehog_${VERSION}_linux_amd64.tar.gz" \ | tar -xz trufflehog sudo mv trufflehog /usr/local/bin/ env: VERSION: 3.97.1
- name: Scan run: trufflehog git file://. --results=verified --fail --no-update--no-update stops the binary checking for a newer version at run time, which you want when the
version is pinned deliberately.
Scoping a large scan
Section titled “Scoping a large scan”Full-history verification on a large repository is slow, and most of the cost is verification calls rather than reading. Four flags control the scope.
| Flag | Effect |
|---|---|
--since-commit | Start from a commit rather than the beginning of history |
--branch | Scan one branch instead of everything reachable |
--max-depth | Limit how many commits back to go |
--bare | Scan a bare repository, useful for a mirror clone |
And two that control which detectors run at all:
{/* Only the providers you actually use */}trufflehog git file://. --include-detectors="github,aws,gcp,slack"
{/* Or everything except a noisy one */}trufflehog git file://. --exclude-detectors="uri"--include-detectors is worth considering for a blocking check. Running seven hundred detectors when
your organisation uses six providers spends most of the scan on formats you will never issue, and the
long tail of generic detectors is where unverified noise comes from.
The trade-off is real: excluding a detector means never finding that credential type, including one that arrives because somebody adopted a new service without telling you. The usual resolution is a narrow detector set for the fast pull request gate and the full set for the scheduled scan.
Archives and containers
Section titled “Archives and containers”TruffleHog traverses archives, controlled by --archive-max-depth, --archive-max-size and
--archive-timeout. That matters because a credential inside a .jar, a .tar.gz or a build
artefact is invisible to a scanner that treats those files as opaque.
The container source is the same idea applied to images:
trufflehog docker --image=ghcr.io/YOUR_ORG/YOUR_IMAGE:TAGThis scans each layer, which finds credentials baked in at build time — a category no Git-based
scanner can see, because the credential never entered the repository. It got in through a build
context, a RUN step that fetched configuration, or a copied .env.
If you publish container images, this scan is worth running against a published tag rather than only against a locally built one. What you build and what you published can differ.
Tuning the noise
Section titled “Tuning the noise”Three flags matter when the output is too long.
--results is the primary control. Start with verified alone for a blocking check, and widen to
verified,unknown for a scheduled scan.
--filter-entropy filters unverified results below a Shannon entropy threshold, which removes
low-randomness matches — the class that is usually a constant or an identifier rather than a
credential.
--filter-unverified outputs only the first unverified result per chunk, which collapses the case
where the same non-credential matches repeatedly.
The order to reach for them: fix the --results filter first, because it is a statement about what
you care about. Reach for entropy filtering only when a genuinely noisy unverified stream is worth
keeping rather than discarding.
GitLeaks or TruffleHog?
Section titled “GitLeaks or TruffleHog?”Neither is universally better. They fail differently, which is the argument for running both.
| GitLeaks | TruffleHog | |
|---|---|---|
| Mechanism | Regex rules plus entropy | Detectors plus live verification |
| Key question | “Does this look like a credential?” | “Is this credential working?” |
| Speed | Fast, entirely local | Slower — verification makes network calls |
| Offline | Yes | Verification requires network access |
| False positives | Higher | Much lower when filtered to verified |
| False negatives | Formats with no rule | Providers with no detector |
| Custom formats | Straightforward TOML rules | More involved |
| Output safety | --redact built in | Prints raw secrets by default |
| Non-Git sources | Files and stdin | Containers, object storage, GitHub organisations |
A split that works well in practice:
GitLeaks in the pull request gate and the pre-commit hook. Fast, offline, no network calls, easy to extend with your organisation’s own token format.
TruffleHog on a schedule, and for incident work. When there is a finding and you need to know whether it is live, verification answers in seconds what a person would spend an afternoon establishing.
The redundancy is the point. A credential format GitLeaks has a rule for and TruffleHog has no detector for is caught by one; a base64-encoded credential TruffleHog decodes and verifies is caught by the other.
Verification during an incident
Section titled “Verification during an incident”The situation where TruffleHog earns its place decisively is triage after a finding, and it is worth walking through because the workflow is different from routine scanning.
You have an alert — from GitHub secret scanning, from GitLeaks, or from somebody noticing. The urgent question is not “where is it” but “does it still work?”, because the answer determines whether this is a rotation or a cleanup.
-
Scan the affected repository including unverified results, so you see everything rather than just the live set:
Terminal window trufflehog git file://. --results=verified,unknown,unverified --no-update --json > findings.jsonUse
--json, and write to a file rather than to a terminal somebody is sharing. -
Extract the verified findings. Those are the incidents:
Terminal window jq -r 'select(.Verified == true) | .DetectorName' findings.json | sort | uniq -c -
Treat
unknownas verified. A credential whose verification errored is not a credential you have cleared. -
Rotate everything in categories 2 and 3, following rotating exposed credentials.
-
Re-scan after rotation. Previously verified findings should now come back unverified. That is the confirmation that the rotation took effect — a better check than reading a settings page.
Step 5 is the one that turns this from a scanner into a verification tool. Nothing else in this cluster will tell you, positively, that the credential you rotated is now dead.
Common mistakes
Section titled “Common mistakes”Assuming the default output is everything. Unverified results are hidden by default. A fake or revoked credential in your repository will not appear unless you ask.
Treating unknown as safe. It means verification failed to complete — a network error, a
rate limit, a provider outage. It is closer to verified than to unverified.
Letting raw output reach a CI log. The value is printed unredacted, and the log is visible and retained.
Shallow clones in CI. No history to scan, and a passing check.
Using @main because the README does. It is a moving reference executing in a job with your
credentials.
Running verification against an environment that alerts on unfamiliar authentication. Tell the people who watch those alerts before you scan.
Using it as the only scanner. No detector means no finding, and detectors cover the providers somebody implemented.
Mental model
Section titled “Mental model”TruffleHog is a detector plus a phone call. The detector finds candidates the way any scanner does; the phone call to the provider is what turns “this looks like a credential” into “this credential works” — and that is the fact that determines whether you have an incident.
What you learned
Section titled “What you learned”- Verification distinguishes live credentials from dead strings, which is the triage split that matters
- Results are classified verified, unverified, unknown and filtered_unverified; the default shows verified and unknown
unknownmeans verification could not complete and should be treated as live- Verification authenticates with the candidate credential, which the provider sees and may log
- Output prints the raw secret by default, so CI logs need care
--failexits with code 183, distinct from ordinary tool errorsfile://is required for a local repository path- It scans containers, object storage and whole GitHub organisations, not only Git
- The project’s README shows
@main; pin to a tag or SHA instead - GitLeaks and TruffleHog fail differently, which is why running both is not redundant
Exercise
Section titled “Exercise”Use a disposable repository and a randomly generated fake token.
-
Commit a file containing a
ghp_prefix followed by 36 random alphanumeric characters. -
Run
trufflehog git file://$PWD --no-update. Predict: does it report anything? -
Run it again with
--results=verified,unknown,unverified. Predict: what changes, and why? -
Read the finding. Note that
Raw resultshows the full value and that a rotation guide is linked. -
Run with
--no-verification. Predict: how does the classification change, and how does the scan duration compare? -
Add
--failand checkecho $?. Predict: what exit code? -
Scan the same repository with GitLeaks. Predict: do the two agree on this finding? The difference is the lesson.
-
Delete the repository.
Related lessons
Section titled “Related lessons”The secrets management checklist and least-privilege token guide are in the Professional Toolkit.