| Tested on | Kali GNU/Linux Rolling 2026.2 (kali-rolling) |
|---|---|
| Package | trufflehog 3.97.1gitgrep |
| Lab environment | Pentest lab |
| Cert prep | CompTIA PenTest+ PT0-003 — Domain 2 (Reconnaissance and Enumeration) |
| Privilege | Normal user for scans; sudo to install TruffleHog under /usr/local/bin |
| Scope | Local Git secret scanning with TruffleHog git and filesystem modes, git log -S history review, and grep fallbacks on a disposable lab repository. Does not cover live GitHub or GitLab API mining, CI pipeline setup, or post-finding credential abuse. |
| Related guides | Web content discovery SMTP and SMB enumeration |
Developers paste cloud keys into config files, commit .env by mistake, and assume a later redaction removes the risk. TruffleHog helps you scan a Git repository for secrets and find secrets in Git history even when the latest tree looks clean. In penetration testing, that workflow is part of secrets enumeration — this walkthrough focuses on TruffleHog Git scanning on Kali.
Every secret in the examples below is fake lab data. Scan only repositories and paths covered by your rules of engagement.
What is secret scanning in Git?
Git secret scanning hunts for API keys, cloud credentials, database connection strings, private keys, and password dumps left in source trees or commit history. The question that drives most searches is simple: “I deleted the key from the latest commit — does it still exist somewhere in the repository?”
Common leak locations:
- Environment files —
.env,.env.local, deployment YAML with inline passwords - Application config — JSON or XML blocks with
aws_access_key_idand partner API tokens - Git history — secrets removed from HEAD but still reachable through older commits and object storage
- Private keys —
id_rsa, deploy keys, and PEM material committed beside application code - Password dumps —
passwords.txt, CSV exports, and notes folders copied into the repo - Cloud and SaaS keys — AWS, Azure, GCP, Stripe, GitHub, Slack, and generic bearer tokens
Passive OSINT for pentesters can surface public GitHub leaks. This lesson focuses on active scanning after you legally possess a repository clone or filesystem export from the client.
TruffleHog quick start
Point TruffleHog git mode at a local repository clone. Replace the path with your authorized checkout:
export REPO=/path/to/your/repo
trufflehog git "file://${REPO}" --no-verification🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷
Found unverified result 🐷🔑❓
Detector Type: Postgres
Decoder Type: PLAIN
Raw result: postgres://admin:SuperSecretLabPass99@db.internal:5432
Commit: 9a1ac9852086fad25676a8aaf6cf76cc7254fc79
Email: Lab User <lab@example.com>
File: config.json
Line: 2
Timestamp: 2026-08-31 05:57:33 +0000The Commit and File lines tie the finding to an older revision — exactly what you need when HEAD shows REDACTED but history still contains the database URL.
TruffleHog labels each match verified or unverified:
- Verified — TruffleHog contacted a provider or service and confirmed the credential works.
- Unverified — the value matched a detector, but validity was not confirmed.
- False positive / test fixture — the string resembles a secret but does not represent usable access.
Verification can make outbound requests to provider APIs, so confirm that traffic is permitted by the RoE before you enable it on client data.
The controlled lab repository below reproduces this output on Kali. Install TruffleHog first, then build $REPO under /tmp/secrets-lab/leaky-app.
Install TruffleHog on Kali Linux
Kali does not always ship TruffleHog in the default repositories. Use the official installation script from the Truffle Security project — it downloads the latest matching binary for your platform:
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/binConfirm the build you installed:
trufflehog --versiontrufflehog 3.97.1The script installs the newest release by default. To pin a specific version, append the tag: sh -s -- -b /usr/local/bin v3.97.1.
Prefer a manual install? Download the Linux amd64 archive from the TruffleHog releases page, extract trufflehog, and move it to /usr/local/bin with sudo.
Build a leaky Git lab repository
Real engagements start from a client zip export or git clone. Here you simulate a common mistake: a database URL committed in config.json, then redacted in HEAD while the original commit remains in history.
| Variable | Purpose |
|---|---|
LAB |
Scratch directory (/tmp/secrets-lab) |
REPO |
Git root (/tmp/secrets-lab/leaky-app) |
Export the paths once per shell session:
export LAB=/tmp/secrets-lab
export REPO="${LAB}/leaky-app"Create the parent directory:
mkdir -p "$LAB"Initialize the repository and set a local Git identity:
mkdir -p "$REPO" && cd "$REPO"Turn the directory into a Git repository:
git initSet a local commit identity so history looks realistic:
git config user.email "lab@example.com"Set the display name Git attaches to commits:
git config user.name "Lab User"Add a config file with a Postgres connection string in the first commit:
printf '%s\n' '{' ' "database_url": "postgres://admin:SuperSecretLabPass99@db.internal:5432/app"' '}' > config.jsonStage and commit the first revision:
git add config.jsonRecord the commit that introduces the database URL:
git commit -m "add config with database url"Redact the URL in the working tree and add token-shaped strings plus a password list:
printf '%s\n' '{' ' "database_url": "REDACTED"' '}' > config.jsonAdd a .env file with SaaS token placeholders:
printf '%s\n' 'GITHUB_TOKEN=ghp_abcdefghijklmnopqrstuvwxyz1234567890ABCD' 'STRIPE_KEY=sk_test_4eC39HqLyjWDarjtT1zdp7dc' > .envCreate a notes folder for the password dump:
mkdir -p notesWrite colon-separated credentials into notes/passwords.txt:
printf '%s\n' 'admin:Winter2024!' 'deploy:changeme123' 'backup:qwertyuiop' > notes/passwords.txtCommit the second revision:
git add . && git commit -m "redact database url; add env and notes"Review the history you just created:
git log --onelined74eb67 redact database url; add env and notes
9a1ac98 add config with database urlconfig.json at HEAD shows REDACTED, but the first commit still stores the full connection string for TruffleHog to find.
Choose TruffleHog git mode or filesystem mode
| Mode | What it is for |
|---|---|
trufflehog git |
Git repositories and commit history; returns commit metadata |
trufflehog filesystem |
Directories and files; may encounter loose Git objects under .git |
trufflehog github |
GitHub repositories and organizations (requires tokens; out of scope here) |
When you need to find secrets in Git history, prefer trufflehog git. TruffleHog filesystem mode scans files beneath the path you supply. If .git is included, it may also read loose objects under .git/objects, but that is not the same as a full Git-aware history walk — packed objects and complete history semantics differ.
Current TruffleHog releases clone local repositories into a temporary directory before scanning to reduce risk from malicious local Git configuration (CVE-2025-41390). Only use --trust-local-git-config when you trust the repository.
Scan Git history with TruffleHog git mode
Git mode walks commits and attributes each finding to the revision that introduced it:
trufflehog git "file://${REPO}" --no-verificationFound unverified result 🐷🔑❓
Detector Type: Postgres
Decoder Type: PLAIN
Raw result: postgres://admin:SuperSecretLabPass99@db.internal:5432
Commit: 9a1ac9852086fad25676a8aaf6cf76cc7254fc79
Email: Lab User <lab@example.com>
File: config.json
Line: 2
Timestamp: 2026-08-31 05:57:33 +0000Record the commit, file path, line, and timestamp as supporting evidence for the finding. The commit hash points at the introducing revision even though HEAD shows REDACTED — the core “deleted from the tree but still in history” scenario.
To scan commits for secrets introduced after a known baseline — a recent branch, retest window, or release tag — limit the history range:
trufflehog git "file://${REPO}" --since-commit HEAD~1 --no-verificationOn this lab fixture, that pass reports no Postgres findings because the database URL was introduced in the parent commit, not in the latest revision alone. Scan the full repository when you need complete history coverage; use --since-commit when you only want changes after a specific commit.
Export JSON when you need machine-readable output for reporting pipelines:
trufflehog git "file://${REPO}" --no-verification --json 2>/dev/null | grep '^{' | head -1The useful fields are condensed below — the live command emits one long JSON line per finding:
{
"DetectorName": "Postgres",
"Verified": false,
"SourceMetadata": {
"Data": {
"Git": {
"commit": "9a1ac9852086fad25676a8aaf6cf76cc7254fc79",
"file": "config.json",
"line": 2
}
}
}
}"Verified": false is expected for lab fixtures and offline hosts.
Scan the working tree with TruffleHog filesystem mode
Filesystem mode is useful when you have a directory export without an intact .git directory, or when you want to scan config trees outside version control:
trufflehog filesystem "$REPO" --no-verificationFound unverified result 🐷🔑❓
Detector Type: Postgres
Decoder Type: PLAIN
Raw result: postgres://admin:SuperSecretLabPass99@db.internal:5432
File: /tmp/secrets-lab/leaky-app/.git/objects/e7/665e282904bd0bd7094d5fe070d2cdbb120dd9
Line: 1TruffleHog surfaced the connection string inside a loose Git object, not in the current config.json working tree. Prefer git mode when you need commit metadata and a deliberate history walk.
Find deleted secrets still stored in Git history
When HEAD shows REDACTED, older commits may still expose live material. git log -S (“pickaxe”) lists commits where the number of occurrences of a string changed — useful for locating when a secret was introduced or removed:
git -C "$REPO" log -S 'SuperSecretLabPass99' --all --onelined74eb67 redact database url; add env and notes
9a1ac98 add config with database urlBoth commits appear because the database URL was added in the first revision and removed in the second.
Inspect the revision that still contains the connection string:
git -C "$REPO" show 9a1ac98:config.json{
"database_url": "postgres://admin:SuperSecretLabPass99@db.internal:5432/app"
}TruffleHog git mode already reported the same material from config.json in commit 9a1ac98. Pickaxe queries give you a second, Git-native path when you need to prove exactly when a value entered or left the tree.
Find API keys and credentials with grep
Recursive grep is a useful fallback for organization-specific naming conventions or quick lab checks, but handcrafted regexes should not replace maintained TruffleHog detectors. Token formats change over time; use grep to find API keys in Git working trees as a lead, not as a substitute for scanner detectors.
Search for a few common indicators in the lab tree:
grep -RInE 'ghp_|sk_(live|test)_' "$REPO"/tmp/secrets-lab/leaky-app/.env:1:GITHUB_TOKEN=ghp_abcdefghijklmnopqrstuvwxyz1234567890ABCD
/tmp/secrets-lab/leaky-app/.env:2:STRIPE_KEY=sk_test_4eC39HqLyjWDarjtT1zdp7dcGitHub- and Stripe-shaped strings appear in .env at HEAD even though the historical Postgres URL lives only in an older commit. Combine both approaches on engagements, but treat grep hits as leads until you confirm scope.
Search password-style dumps separately:
grep -RInE '^[a-zA-Z0-9_.-]+:[^[:space:]]+$' "$REPO/notes"/tmp/secrets-lab/leaky-app/notes/passwords.txt:1:admin:Winter2024!
/tmp/secrets-lab/leaky-app/notes/passwords.txt:2:deploy:changeme123
/tmp/secrets-lab/leaky-app/notes/passwords.txt:3:backup:qwertyuiopColon-separated user:password lines are weak credential stores and frequent lateral movement seeds.
Validate and report findings
Treat every TruffleHog and grep hit as unconfirmed until you record context and scope:
| Step | Action |
|---|---|
| Triage | Note detector name, path, commit hash, and line number |
| Redact | Replace live secret bytes with placeholders in your report |
| Verify | On authorized repos with outbound access, rerun with --results=verified or --results=verified,unknown |
| Impact | Map the secret to services — cloud consoles, payment APIs, databases |
| Handoff | Deliver through the RoE secure channel; recommend rotation before history rewrite |
For the highest-confidence output, use --results=verified. When you also want potentially valid secrets that TruffleHog cannot verify automatically, add unknown:
trufflehog git "file://${REPO}" --results=verified,unknownOn the lab fixture, that pass still surfaces the unverified Postgres match. A --results=verified-only run returns nothing here — expected for synthetic strings.
If a real secret was committed, revoke or rotate it first. Rewriting Git history removes the exposed value from normal repository history but does not make a credential safe again — existing clones, forks, caches, logs, or earlier copies may still contain it.
For preventive scanning before secrets reach repository history, use TruffleHog in pre-commit or CI workflows — a separate DevSecOps topic outside this enumeration lesson.
Git secret scanning troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
command not found: trufflehog |
Binary not on PATH | Rerun the install script with -b /usr/local/bin |
Zero findings on obvious .env |
Scanning wrong directory | Export REPO to the Git root that contains .git |
--results=verified prints nothing |
No live credentials in scope | Expected for lab fixtures; try --results=verified,unknown |
--since-commit misses an old leak |
Secret introduced before the baseline commit | Run a full git scan or move the baseline earlier |
| Duplicate Postgres lines in filesystem mode | Same secret in file and loose object | Deduplicate in notes; prefer git mode for commit attribution |
| Huge JSON noise on CI runners | Entire workspace scanned | Point TruffleHog at the repository root only |
Summary
Git secret scanning answers whether credentials survive after someone “removed” them from the latest commit. You installed TruffleHog 3.97.1 on Kali, ran git mode against a disposable repository, and watched it find secrets in Git history — a Postgres URL in config.json at commit 9a1ac98 while HEAD showed REDACTED. Filesystem mode and grep supplemented the scan; git log -S confirmed when the value was introduced and removed.
The main pitfall is trusting a single tool pass. Pair TruffleHog with pickaxe history review and manual triage, then redact before you write the report. Rotate confirmed live credentials before recommending history rewrites.
On authorized client work, run git mode against each in-scope Git repository and filesystem mode against non-Git source or config exports. Use --results=verified,unknown when the network allows verification. For broader passive collection before you possess a clone, continue with OSINT tools for pentesters.
References
- TruffleHog — GitHub repository
- TruffleHog installation script
- Git pickaxe documentation (
git log -S)
Frequently Asked Questions
What is secrets enumeration in penetration testing?
Secrets enumeration hunts for API keys, cloud credentials, database URLs, private keys, and password dumps left in source trees, configuration files, or Git history. Git secret scanning with TruffleHog is one active technique you use after you legally possess a repository clone.
What does TruffleHog detect?
TruffleHog ships hundreds of detectors for cloud provider keys, SaaS tokens, database connection strings, and private keys. It can verify some findings against provider APIs when outbound access is allowed. Offline lab scans still surface unverified matches you must validate manually.
Should I use git mode or filesystem mode?
Use trufflehog git when you need commit-aware history scanning and metadata such as author and timestamp. Use trufflehog filesystem for directory exports or config trees. When .git is present and history matters, git mode is the recommended path.
What is the difference between verified and unverified results?
Verified means TruffleHog confirmed the credential against a provider or service. Unverified means the value matched a detector but was not confirmed. Use --results=verified for the strictest filter, or --results=verified,unknown when you also want matches that cannot be verified automatically.
Why did TruffleHog miss a token that grep found in the lab?
Synthetic test strings may match grep patterns but fail TruffleHog detector heuristics. Combine TruffleHog with git log -S and pattern searches, then treat every hit as a lead until you confirm scope and impact.
Is scanning client Git repositories legal?
Repository and filesystem scanning belongs inside written authorization for the engagement. Cloning public repos without permission, exfiltrating secrets you find, or using discovered credentials outside the test scope violates most rules of engagement and many laws.

