12.8M
Secrets exposed in public GitHub repositories in 2023: the most recent full-year count available (GitGuardian)
< 1 hour
Median time for bots to discover and abuse a leaked AWS access key after it appears in a public repo
0
Days a secret is 'safe' after being committed: automated scanners find new commits within minutes
100%
Of secrets committed to a public repo that should be considered permanently compromised and rotated

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

The mistake is easy: a developer copies a working .env file into a project directory, adds it to a commit along with other files, and pushes. The API key is now in the repository's git history: permanently accessible to anyone with read access, even after the file is deleted and a new commit is pushed.

Automated scanners operated by credential harvesters monitor GitHub for new commits containing patterns matching API keys, database URLs, and private keys. The median time between a credential appearing in a public repository and its first abuse attempt is under one hour. For private repositories, the risk is lower but not zero: former employees, compromised developer machines, and repository permission misconfigurations all create exposure.

Step 1: Scan Your Current Codebase

Before looking at history, scan what is currently in your repository. Several tools exist; TruffleHog has the best detection rate for real secrets versus false positives.

TruffleHog (recommended for initial scan):

# Install
pip install trufflehog

# Scan a GitHub repository (remote, no clone required)
trufflehog github --repo https://github.com/yourorg/yourrepo

# Scan a local repository including git history
trufflehog git file://path/to/local/repo --json

TruffleHog uses entropy analysis and pattern matching to find high-confidence secrets. It includes detectors for AWS keys, GitHub tokens, Stripe keys, Twilio credentials, and hundreds of other specific secret formats.

Gitleaks (fast, deterministic rule-based scanner):

# Install via Homebrew
brew install gitleaks

# Scan the current directory
gitleaks detect --source . --report-format json --report-path gitleaks-report.json

# Scan including git history
gitleaks detect --source . --log-opts="--all"

Gitleaks uses a rule file with regex patterns for common secret formats. It is faster than TruffleHog for large repositories and produces machine-readable output suitable for CI/CD integration.

GitHub Secret Scanning (built-in, free for public repos, requires GitHub Advanced Security for private): For repositories hosted on GitHub, enable Secret Scanning under Settings > Security & analysis > Secret scanning > Enable. GitHub automatically scans all commits for patterns matching 200+ secret types and alerts the repository owner.

Step 2: Scan Git History

Current code scanning misses secrets that were committed and later deleted. Git history scanning finds these.

TruffleHog history scan (recommended):

# Scan full git history
trufflehog git file://. --since-commit HEAD --json | jq '.'

# Scan between two commits
trufflehog git file://. --since-commit abc1234 --branch main

Gitleaks log scan:

# Scan all branches and all history
gitleaks detect --source . --log-opts="--all --full-history"

git log + grep (manual, no tool required):

# Search all commit diffs for patterns matching common secret formats
git log --all --full-history -p | grep -E '(api[_-]?key|secret|password|token|private[_-]?key)\s*[:=]\s*["'\''`]?[A-Za-z0-9+/]{20,}'

The grep approach is less precise than dedicated tools but requires no installation and works on any git repository. False positive rate is higher: treat every match as a candidate requiring manual review.

Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Step 3: Remove Secrets From History

Critical decision first: If the repository is public and the secret appeared in any commit, assume the secret is permanently compromised. Rotate it immediately regardless of whether you successfully remove it from history. History removal prevents future discovery; it does not undo past exposure.

git-filter-repo (recommended: replaces deprecated git filter-branch):

# Install
pip install git-filter-repo

# Remove a specific file from all history
git-filter-repo --path path/to/.env --invert-paths

# Replace a specific secret string everywhere in history
git-filter-repo --replace-text expressions.txt
# expressions.txt contents:
# literal:AKIAIOSFODNN7EXAMPLE==>REMOVED_AWS_KEY

After git-filter-repo:

# Force push the rewritten history (requires repo admin rights)
git push origin --force --all
git push origin --force --tags

Warning about force push: Force pushing rewrites remote history. All collaborators must re-clone or reset their local repositories: their existing local copies still contain the secret in history. Communicate the force push to all contributors before executing.

GitHub-specific: GitHub retains cached versions of deleted content for approximately 90 days through their CDN. After force pushing, contact GitHub Support to request immediate deletion of cached commits containing the secret.

Step 4: Prevent Future Leaks With Pre-Commit Hooks

Prevention is cheaper than remediation. Two approaches stop secrets before they reach the remote repository.

pre-commit framework with gitleaks:

# Install pre-commit
pip install pre-commit

Create .pre-commit-config.yaml in the repository root:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.1
    hooks:
      - id: gitleaks

Install the hook:

pre-commit install

Now git commit runs gitleaks before the commit is created: any detected secret causes the commit to be rejected with an explanation of what was found.

GitHub native secret scanning push protection (requires GitHub Advanced Security): GitHub's push protection blocks pushes to the remote repository if the pushed commits contain detected secrets. Unlike pre-commit hooks (which run locally and can be bypassed with --no-verify), push protection runs server-side and cannot be bypassed without explicit override with justification.

Enable under Repository Settings > Code security and analysis > Push protection > Enable.

For the .env file problem specifically: Ensure .env is in .gitignore before developers create it:

# Add to .gitignore
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore
git add .gitignore && git commit -m "Add .env to gitignore"

A .env.example file with placeholder values (not real credentials) communicates required environment variables without committing actual secrets.

The bottom line

Any secret that reaches a public GitHub repository should be treated as permanently compromised and rotated immediately: history removal prevents future discovery but does not undo past exposure. Scan current code with TruffleHog or Gitleaks, scan git history with the same tools using full-history flags, remove discovered secrets from history using git-filter-repo and force push, then install pre-commit hooks and GitHub push protection to prevent future leaks. Rotation before removal; removal before announcement.

Frequently asked questions

Is a secret safe after I delete it from GitHub?

No. Deleting a file from a GitHub repository does not remove it from git history: it remains accessible to anyone who can view the repository's commit history, including cloners and forkers. You must rewrite git history using git-filter-repo and force push to remove a secret from history, and even then anyone who cloned the repository before deletion still has it.

How do I scan my GitHub repository for leaked secrets?

Use TruffleHog ('trufflehog git file://path/to/repo --json') to scan both current code and full git history. Enable GitHub Secret Scanning in repository settings for continuous monitoring. For prevention, install a pre-commit hook using gitleaks that rejects commits containing detected secrets before they are created.

How do I rotate a secret that was committed to a public GitHub repository?

Rotate the secret immediately by revoking it in the issuing service and generating a new one: this is the only step that removes the security exposure. Deleting the commit or making the repository private does not remove the secret from GitHub's history, from any forks, or from web crawlers that may have already indexed it. After rotation: remove the secret from all current files and history using git filter-repo (not git filter-branch, which is deprecated), push the rewritten history with --force, and notify all repository collaborators to reclone. Assume the original secret was accessed; check the service's audit log for unauthorized use.

What is the most common type of secret accidentally committed to GitHub?

API keys and access tokens are the most common: AWS access keys, GitHub personal access tokens, Stripe API keys, and cloud service credentials. .env files committed to repositories are the most common vector, followed by hardcoded credentials in configuration files and connection strings in database migration scripts. Less obvious but equally dangerous: private SSH keys, certificate private keys, and OAuth client secrets embedded in application configuration. TruffleHog and Gitleaks detect all of these categories using regex patterns and entropy analysis.

How do I prevent secrets from being committed to GitHub in the future?

Three complementary controls: (1) Pre-commit hooks using gitleaks ('gitleaks protect --staged --redact') reject commits containing secrets before they are created. Install via pre-commit framework for consistency across the team. (2) GitHub Secret Scanning with push protection (available on public repos for free, on private repos with GitHub Advanced Security) blocks pushes that contain known secret patterns. (3) Developer education: .env files should always be in .gitignore, secrets should never be in application code or config files, and environment variables or secrets managers (AWS Secrets Manager, HashiCorp Vault, Doppler) should be used for all credentials.

What is the correct process for revoking a secret that was committed to a public GitHub repository and how quickly must you act?

Treat a secret committed to a public repository as fully compromised from the moment of the push -- not from when you discover it. GitHub's Secret Scanning notifies credential providers (AWS, Stripe, GitHub itself, and 100+ others) automatically when matching patterns are pushed to public repos. AWS typically revokes exposed credentials within minutes of detection via this automated pipeline. Your required actions: revoke and rotate the credential immediately, do not wait for GitHub to confirm detection. For AWS: deactivate the access key in IAM, create a new key, update all systems using the old key, then delete the old key. Review CloudTrail logs for the entire period between the commit timestamp and revocation for any API calls using that key -- assume the key was used maliciously if any gap exists. For the git history: removing the secret from git history (git filter-branch or BFG Repo Cleaner) prevents future exposure but does not undo current exposure -- the secret was already public. Do not delay revocation to perform git history cleanup. After revocation and rotation, perform git history cleanup and update any forks or clones that may have pulled the compromised commit.

Sources & references

  1. GitHub: Secret Scanning Documentation
  2. TruffleHog: Open Source Secret Scanner
  3. git-filter-repo: History Rewriting Tool

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.