Secrets Management

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.

September 30, 20256 min readShipSafer Team

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:

  1. GitHub sends you an alert in the Security tab → Secret scanning alerts
  2. 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:

  1. Organization Settings → Code security and analysis
  2. Enable "Secret scanning" → "Enable all"

For a specific repository:

  1. Repository Settings → Security & analysis
  2. 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:

  1. Organization/Repository Settings → Code security → Secret scanning → Custom patterns
  2. Click "New pattern"
  3. 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:

  1. Revoke immediately — Don't investigate first; revoke the credential before anything else
  2. Rotate — Issue a new credential and update all systems using the old one
  3. Audit — Check service logs for unauthorized usage during the exposure window
  4. Remove from history — Use git-filter-repo to purge the secret from all branches
  5. 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
github
secret-scanning
api-keys
devsecops
security

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.