GitHub Secret Scanning: How to Find and Prevent Leaked Credentials
GitHub's secret scanning automatically detects leaked API keys and tokens. Learn how to enable it, configure push protection, scan historical commits, and build pre-commit hooks to stop secrets before they're pushed.
GitHub automatically scans every repository for exposed secrets — API keys, tokens, connection strings, and private keys — and notifies both you and the service provider. Despite this, thousands of new credentials are leaked to GitHub every day.
This guide covers GitHub's built-in secret scanning, how to stop secrets before they're pushed, and how to audit your entire repository history.
GitHub's Built-In Secret Scanning
GitHub Secret Scanning detects secrets from 200+ service providers — AWS access keys, Stripe keys, GitHub PATs, Twilio auth tokens, Google API keys, Slack tokens, and many more.
For Public Repositories
Secret scanning is enabled by default on all public repositories. When a secret is detected:
- GitHub sends you an alert in the Security tab → Secret scanning alerts
- GitHub notifies the service provider (they may auto-revoke the secret)
There is no way to disable secret scanning on public repositories. This is by design — leaked secrets are a public safety concern.
For Private Repositories (GitHub Advanced Security)
Secret scanning for private repos requires GitHub Advanced Security (GHAS), available with:
- GitHub Enterprise
- GitHub Team/Enterprise plans (check your plan)
- Open source repositories (free)
Enabling Secret Scanning
For an organization:
- Organization Settings → Code security and analysis
- Enable "Secret scanning" → "Enable all"
For a specific repository:
- Repository Settings → Security & analysis
- Enable "Secret scanning"
Push Protection
Push protection is the most valuable secret scanning feature — it blocks pushes that contain detected secrets before they reach GitHub, rather than alerting after the fact.
Enable Push Protection
Organization level:
Organization Settings → Code security and analysis → Push protection → Enable all
Or via API:
curl -X PATCH \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/orgs/YOUR_ORG/secret-scanning/push-protection-enable
What Happens When Push Protection Blocks a Push
The developer sees an error:
remote: error: GH013: Repository rule violations found for refs/heads/main.
remote:
remote: - GITHUB PUSH PROTECTION
remote: —————————————————————————————————————
remote: Resolve the following secrets before pushing:
remote:
remote: (?) To push, remove secret from commit(s) or follow this URL to allow the secret:
remote:
remote: stripe_secret_key:
remote: commit: abc123
remote: secret: sk_live_...
The developer must either:
- Remove the secret from the commit and push clean code
- Bypass push protection with a justification (creates an audit log entry)
Bypass Logging
When developers bypass push protection, GitHub logs:
- Who bypassed
- What secret was bypassed
- The justification provided
- Timestamp
This gives security teams visibility into bypasses for follow-up.
Custom Secret Patterns
Beyond the 200+ built-in patterns, you can define custom patterns for your internal credentials:
- Organization/Repository Settings → Code security → Secret scanning → Custom patterns
- Click "New pattern"
- Define a regex pattern
Example: custom internal API token format INT-[A-Za-z0-9]{32}:
INT-[A-Za-z0-9]{32}
Test your pattern against sample secrets before enabling to minimize false positives.
Scanning Historical Commits
Push protection only stops new secrets. For historical scanning:
gitleaks
# Install
brew install gitleaks
# Scan entire repo history
gitleaks detect --source . --verbose
# Scan only staged files (for pre-commit)
gitleaks protect --staged --verbose
# Generate a report
gitleaks detect --source . --report-format json --report-path leaks.json
truffleHog
# Install
pip install trufflehog3
# or
brew install trufflesecurity/trufflehog/trufflehog
# Scan git history (verified findings only)
trufflehog git file://. --only-verified
# Scan a GitHub repo
trufflehog github --repo https://github.com/your-org/your-repo --only-verified
# Scan entire org
trufflehog github --org your-org --only-verified --token $GITHUB_TOKEN
The --only-verified flag attempts to validate each found secret against the service's API. This dramatically reduces false positives — you only see credentials that are actually valid.
Pre-Commit Hooks
Pre-commit hooks scan staged files before the commit is created. This is the earliest possible intervention.
Option 1: gitleaks pre-commit hook
# Install pre-commit framework
pip install pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
- id: gitleaks
# Install hooks
pre-commit install
# Now every commit is scanned automatically
git commit -m "feat: add payment flow"
# If secrets found: commit is blocked with details
Option 2: Simple git hook
# .git/hooks/pre-commit (make executable: chmod +x)
#!/bin/bash
# Check for common secret patterns
patterns=(
'sk_live_[a-zA-Z0-9]+' # Stripe live key
'AKIA[A-Z0-9]{16}' # AWS access key
'ghp_[A-Za-z0-9]{36}' # GitHub PAT
'xoxb-[0-9]+-[0-9]+-[a-zA-Z0-9]+' # Slack bot token
'AIza[0-9A-Za-z_-]{35}' # Google API key
)
secrets_found=0
for pattern in "${patterns[@]}"; do
matches=$(git diff --cached --unified=0 | grep '+' | grep -E "$pattern")
if [ -n "$matches" ]; then
echo "⚠️ Potential secret detected:"
echo "$matches"
secrets_found=1
fi
done
if [ $secrets_found -eq 1 ]; then
echo "Commit blocked. Remove secrets or add to .gitignore"
exit 1
fi
.gitignore Patterns for Common Secret Files
# Environment files
.env
.env.local
.env.development
.env.staging
.env.production
.env.*.local
*.env
# Credential files
credentials.json
service-account.json
*.pem
*.key
*.p12
*.pfx
# Cloud provider configs
.aws/credentials
.gcloud/
.azure/
# Secret management
.vault-token
vault-token
# IDE/editor sensitive configs
*.secret
*secret*
*password*
*credential*
Responding to a Detected Secret
When GitHub alerts you to a detected secret:
- Revoke immediately — Don't investigate first; revoke the credential before anything else
- Rotate — Issue a new credential and update all systems using the old one
- Audit — Check service logs for unauthorized usage during the exposure window
- Remove from history — Use
git-filter-repoto purge the secret from all branches - Close the alert — Mark as "revoked" in GitHub's secret scanning alerts
Removing from Git history
pip install git-filter-repo
# Remove a specific file from all history
git filter-repo --path path/to/secrets.env --invert-paths
# Replace a specific string throughout history
git filter-repo --replace-text <(echo 'sk_live_abc123==>REMOVED_KEY')
# Force push all branches
git push --force --all
git push --force --tags
# Ask GitHub to remove cached views (contact support or wait)
Secret Scanning in CI/CD
Add scanning to your CI pipeline as an additional layer:
# GitHub Actions
name: Secret Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for thorough scanning
- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} # Required for org-level