← Back to writing
Tools & Cheatsheets

Git Recon: Finding Secrets in Git History

Jan 08, 2025
4 min read
lawbyte

Git commit history is a graveyard of secrets that developers accidentally committed and then deleted. “Deleted” in Git doesn’t mean gone — it means hidden. This post covers every technique for extracting secrets from Git repositories, both remote and local.

Why Git Recon Works

When a developer commits an API key, then removes it in the next commit, both commits remain in the history indefinitely. Many developers believe git rm erases data permanently. It doesn’t — the original blob is still there, reachable via the commit hash.

The same applies to:

  • Hardcoded passwords committed to application code
  • Private keys checked in “temporarily”
  • .env files accidentally staged
  • Database connection strings in config files
  • CI/CD secrets embedded in pipeline configs

Automated Secret Scanning

Always run automated tools first — they cover far more patterns than manual search.

trufflehog (best for entropy + regex)

# Install
pip install trufflehog3
# or
brew install trufflehog

# Scan GitHub repo (includes full history)
trufflehog github --repo=https://github.com/target/repo

# Scan local repo
trufflehog git file:///path/to/repo

# Scan specific branch
trufflehog git --branch=main file:///path/to/repo

# Only regex (no entropy)
trufflehog git --no-entropy file:///path/to/repo

# JSON output for parsing
trufflehog git file:///path/to/repo --json

gitleaks

# Install
brew install gitleaks
# or binary from github.com/gitleaks/gitleaks/releases

# Scan repo
gitleaks detect --source /path/to/repo

# Scan with report
gitleaks detect --source /path/to/repo --report-path report.json

# Scan remote
git clone https://github.com/target/repo && gitleaks detect --source repo/

# Check specific commit range
gitleaks detect --source /path/to/repo --log-opts="HEAD~50..HEAD"

# Verbose (show all matches, not just summary)
gitleaks detect --source /path/to/repo -v

git-secrets

# Install
brew install git-secrets

# Scan for AWS secrets
git secrets --scan-history
git secrets --aws-provider

Search all commit diffs for keywords

cd /path/to/cloned/repo

# Search across all commits for keyword
git log -p --all | grep -iE "password|secret|key|token|api_key|auth" -A 3 -B 3

# Search commit messages
git log --all --oneline | grep -iE "password|credential|key|secret|fix|remove|delete"

# Show what was changed in commits mentioning "password"
git log --all --oneline --grep="password"
git show <commit_hash>

Search by file content across history

# Find all commits that touched a specific string
git log -p -S "api_key" --all
git log -p -S "BEGIN RSA PRIVATE KEY" --all
git log -p -S "password" --all

# Regex search (slower but more flexible)
git log -p -G "API_KEY\s*=\s*['\"]" --all

# Search deleted file content
git log --diff-filter=D --summary --all | grep "delete mode"
git show <commit_hash>:<deleted_file_path>

Recover deleted files

# Find the last commit that touched a file
git log --all --full-history -- "path/to/deleted/file.env"

# Restore the file from that commit
git show <commit_hash>:path/to/deleted/file.env

# Or checkout the file at that point
git checkout <commit_hash> -- path/to/deleted/file.env

Stash entries

git stash list
git stash show -p stash@{0} # show stash content
git stash show -p stash@{1}

All branches and tags

# List all remote branches
git branch -r

# Fetch all remote branches
git fetch --all

# Checkout each and search
for branch in $(git branch -r | grep -v HEAD); do
echo "=== $branch ==="
git log -p $branch | grep -iE "password|apikey|secret" -A 2
done

GitHub-Specific Recon

Use GitHub’s code search to find secrets across public repos:

# Search for secrets mentioning your target
"target.com" "api_key"
"target.com" password filename:.env
org:target_org "BEGIN RSA PRIVATE KEY"
"targetcorp" filename:*.pem
"api.target.com" "Authorization: Bearer"

# Find exposed CI/CD secrets
org:target_org filename:.travis.yml
org:target_org filename:.github/workflows SECRET
org:target_org filename:Jenkinsfile password

GitHub Dorking

site:github.com "target.com" "password"
site:github.com "target.com" "secret_key"
site:github.com "target-internal-hostname" token

GitHub Archive / Grep.app

# grep.app — search across public GitHub
https://grep.app/search?q=target.com+api_key

# sourcegraph
https://sourcegraph.com/search?q=target.com+password

Exposed .git Directory

If a web application has its .git/ directory publicly accessible, you can reconstruct the entire source code.

Detection

curl -s https://target.com/.git/HEAD
# Should return: ref: refs/heads/main
# If it does, the .git directory is exposed!

Dumping with git-dumper

pip install git-dumper
git-dumper https://target.com/.git/ output_dir/

# Then analyze
cd output_dir/
git log --oneline --all
git log -p | grep -iE "password|key|secret"

Manual extraction

# Key files to retrieve
curl https://target.com/.git/HEAD
curl https://target.com/.git/config # remote URLs, credentials
curl https://target.com/.git/COMMIT_EDITMSG # last commit message
curl https://target.com/.git/logs/HEAD # commit log

# Reconstruct from pack files
curl https://target.com/.git/objects/pack/pack-<hash>.idx
curl https://target.com/.git/objects/pack/pack-<hash>.pack

CI/CD Secret Hunting

CI/CD configs frequently contain secrets:

# GitHub Actions
cat .github/workflows/*.yml | grep -iE "secret|password|token|key"
# Look for: ${{ secrets.XXX }} — these are referenced in CI but set in GitHub settings
# Also look for hardcoded fallback values

# Jenkins
cat Jenkinsfile | grep -iE "credentials|secret|password|token"

# Travis CI
cat .travis.yml | grep -iE "secure:|password|token|key"
# Encrypted values look like: secure: "xxxxxx"

# GitLab CI
cat .gitlab-ci.yml | grep -iE "\$[A-Z_]+\|password\|token"

Package Registry Leaks

Old npm/PyPI packages sometimes contain secrets:

# npm audit published packages
npm pack <package_name>
tar -xzf package_name-*.tgz
grep -r "password\|secret\|api_key" package/

# Check package publish history
npm view <package_name> versions --json

Paste Sites and Code Sharing

# Google dorking for paste sites
site:pastebin.com "target.com" "password"
site:gist.github.com "target.com" "api_key"
site:jsfiddle.net "target-internal"
site:codepen.io "target.com" token

Common Findings Cheat Sheet

# AWS
AKIA[0-9A-Z]{16} # AWS Access Key ID
[0-9a-zA-Z/+]{40} # AWS Secret Access Key
arn:aws: # ARN reference

# Generic API keys
[A-Za-z0-9_-]{32,45} # Generic token (high entropy)
sk-[A-Za-z0-9]{48} # OpenAI API key
ghp_[A-Za-z0-9]{36} # GitHub personal access token
xox[baprs]-[0-9a-zA-Z-]+ # Slack token
ya29\.[0-9A-Za-z\-_]+ # Google OAuth token

# Private keys
BEGIN RSA PRIVATE KEY
BEGIN EC PRIVATE KEY
BEGIN OPENSSH PRIVATE KEY
BEGIN PGP PRIVATE KEY

# Database connection strings
mysql://username:password@host
mongodb://user:pass@host
postgresql://user:pass@host
redis://:password@host

After Finding Credentials

  1. Verify — is the credential still valid? Test against the API/service.
  2. Document — screenshot the finding with the commit hash and date.
  3. Assess scope — what does this credential access? What’s the blast radius?
  4. Report immediately if found during a pentest — don’t use the credential beyond verification.
  5. Recommend rotation — the credential should be rotated even after removal from the repo.

Prevention

  • Use pre-commit hooks with gitleaks to block commits containing secrets.
  • Store secrets in a secrets manager (HashiCorp Vault, AWS Secrets Manager, 1Password Secrets Automation).
  • Use git-secrets or detect-secrets in your CI pipeline.
  • Rotate any credential that was ever committed, even if removed.
  • Add .env to .gitignore from day one — not after the first mistake.

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.