How to fix remote: error: GH009: Secrets detected. Push blocked. in Git

GitINTERMEDIATECRITICALUpdated Dec 6, 2025· 164 views

The 'GH009: Secrets detected' error occurs when GitHub's push protection feature detects credentials, API keys, or other sensitive data in your commits. This security feature blocks the push to prevent accidental exposure of secrets in your repository.

Quick fix

Fixes GH009

remote: error: GH009: Secrets detected. Push blocked.
remote:
remote: —— GitHub Personal Access Token ——————————————
remote:  locations:
remote:    - commit: 8728dbe67
remote:      path: config/settings.js:15
remote:
remote: —— AWS Access Key ID ——————————————————————
remote:  locations:
remote:    - commit: a4f2c9e12
remote:      path: .env:3
  1. Identify the detected secret and its location
  2. Revoke the exposed secret immediately
  3. Remove the secret from the latest commit
  4. Remove secrets from earlier commits using interactive rebase
git — error
remote: error: GH009: Secrets detected. Push blocked.
remote:
remote: —— GitHub Personal Access Token ——————————————
remote: locations:
remote: - commit: 8728dbe67
remote: path: config/settings.js:15
remote:
remote: —— AWS Access Key ID ——————————————————————
remote: locations:
remote: - commit: a4f2c9e12
remote: path: .env:3
remote: error: GH009: Secrets detected. Push blocked.

What this error means

This error indicates that GitHub's secret scanning push protection has detected what appears to be sensitive information in one or more of your commits. GitHub automatically scans pushes for known secret patterns such as API keys, tokens, passwords, and private keys to prevent accidental credential exposure. When push protection is enabled (which is the default for public repositories and can be enabled for private repositories), GitHub analyzes the content of your commits before accepting the push. If it detects patterns matching known secret formats from supported providers, it blocks the push entirely. The error message typically includes: - The type of secret detected (e.g., "GitHub Personal Access Token", "AWS Access Key ID") - The commit hash(es) containing the secret - The file path and line number where the secret was found This is a critical security feature because secrets accidentally pushed to a repository (especially public ones) can be harvested by malicious actors within minutes. Even if you quickly delete the file, the secret remains in Git history and can be extracted.

Typical symptoms

  • Git push fails with 'GH009: Secrets detected' error
  • Error message shows 'push declined due to a detected secret'
  • Push blocked with message listing detected secret type and location
  • CI/CD pipeline fails at push step with secret detection error
  • Error shows specific commit hashes and file paths containing secrets
  • Push rejected even after removing the secret from the latest commit
  • Multiple secrets detected across different commits
  • False positive blocking push for test credentials or example values

Common causes

  1. Accidentally committed a real API key, token, or password
  2. Committed a configuration file containing credentials
  3. Included a .env file or secrets file in the commit
  4. Private key or certificate file added to the repository
  5. Hardcoded credentials in source code
  6. Test credentials that match real secret patterns
  7. Copied example code containing placeholder secrets that look real
  8. Secret exists in an older commit in the push history
  9. Merged a branch that contained secrets
  10. Restored a file from history that contained secrets

How to fix "remote: error: GH009: Secrets detected. Push blocked."

1Identify the detected secret and its location

When GitHub blocks your push, it provides detailed information about what was detected. Examine the error message carefully:

bash
remote: error: GH009: Secrets detected. Push blocked.
remote:
remote: —— GitHub Personal Access Token ——————————————
remote:  locations:
remote:    - commit: 8728dbe67
remote:      path: config/settings.js:15
remote:
remote: —— AWS Access Key ID ——————————————————————
remote:  locations:
remote:    - commit: a4f2c9e12
remote:      path: .env:3

Note down:
1. Secret type - What kind of credential was detected
2. Commit hash - Which commit(s) contain the secret
3. File path and line - Exact location of the secret

If the secret is in the most recent commit, remediation is straightforward. If it's in an earlier commit, you'll need to rewrite history.

2Revoke the exposed secret immediately

Before fixing your Git history, revoke or rotate the exposed credential. Even though the push was blocked, treat any secret that reached this point as potentially compromised.

For GitHub tokens:
1. Go to [github.com/settings/tokens](https://github.com/settings/tokens)
2. Find and delete the exposed token
3. Generate a new token with the same permissions

For AWS credentials:
1. Go to AWS IAM Console
2. Find the exposed access key
3. Click "Make Inactive" or "Delete"
4. Create new credentials

For other services:
- Rotate the credential in the respective service's dashboard
- Update any applications using the old credential
- Check service logs for unauthorized access

Important: Always revoke first, then fix Git history. The secret may have been logged or cached by GitHub's systems.

3Remove the secret from the latest commit

If the secret was introduced in your most recent commit, you can amend it:

bash
# Remove the secret from your code
# Edit the file and delete or replace the credential

# Stage the changes
git add <file-with-secret>

# Amend the last commit (rewrites history)
git commit --amend --all

# Force push (since history was rewritten)
git push --force-with-lease

If the secret is in a file that shouldn't be tracked:

bash
# Remove from Git but keep locally
git rm --cached .env
git rm --cached config/secrets.json

# Add to .gitignore
echo ".env" >> .gitignore
echo "config/secrets.json" >> .gitignore

# Amend the commit
git add .gitignore
git commit --amend --all

# Push
git push --force-with-lease

Replace hardcoded secrets with environment variables:

javascript
// Before (bad)
const API_KEY = "sk-abc123...";

// After (good)
const API_KEY = process.env.API_KEY;
4Remove secrets from earlier commits using interactive rebase

If the secret is in an older commit, use interactive rebase to edit that specific commit:

bash
# Find which commit introduced the secret
git log --oneline --all -- <path-to-file>

# Start interactive rebase from the commit before the one with the secret
git rebase -i <commit-hash>~1

# In the editor, change 'pick' to 'edit' for the target commit
# Save and close the editor

When Git stops at the commit:

bash
# Remove or fix the secret
# Edit the file to remove the credential

# Stage the fix
git add <file>

# Amend this commit
git commit --amend

# Continue the rebase
git rebase --continue

After completing the rebase:

bash
# Force push the rewritten history
git push --force-with-lease

Note: This rewrites commit hashes for all commits after the edited one. Coordinate with your team if working on a shared branch.

5Use BFG Repo-Cleaner for complex history cleaning

For secrets spread across many commits or deep in history, BFG Repo-Cleaner is faster and easier than interactive rebase:

bash
# Install BFG (requires Java 11+)
# macOS: brew install bfg
# Or download from https://rtyley.github.io/bfg-repo-cleaner/

# Clone a fresh mirror of your repository
git clone --mirror git@github.com:user/repo.git

# Create a file with secrets to remove
echo "sk-abc123secretkey" >> secrets.txt
echo "AKIAIOSFODNN7EXAMPLE" >> secrets.txt

# Run BFG to replace secrets with ***REMOVED***
java -jar bfg.jar --replace-text secrets.txt repo.git

# Or delete entire files containing secrets
java -jar bfg.jar --delete-files .env repo.git

# Clean up and push
cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force

Alternative: git-filter-repo (recommended by Git project):

bash
# Install git-filter-repo
pip install git-filter-repo

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

# Or replace text patterns
git filter-repo --replace-text expressions.txt

After force pushing, all collaborators must reclone or rebase their local copies.

6Handle false positives by bypassing push protection

If the detected "secret" is actually a test value, example placeholder, or false positive, you can bypass push protection:

Option 1: Bypass via the GitHub UI

When the push is blocked, GitHub provides a URL in the error message:

bash
remote: To push, visit:
remote:   https://github.com/user/repo/security/secret-scanning/...

1. Visit the provided URL
2. Select a bypass reason:
- It's used in tests - For test credentials not used in production
- It's a false positive - The pattern matches but isn't a real secret
- I'll fix it later - Real secret you'll remediate soon
3. Complete the bypass within 3 hours

Option 2: Request bypass if you don't have permission

If your organization restricts bypasses, you can request approval:
1. Click the link in the error message
2. Select "Request bypass"
3. Provide justification
4. Wait for an organization admin to approve

Best practice for test secrets:

Instead of bypassing, use obviously fake values that won't trigger detection:

javascript
// Instead of realistic-looking test values
const TEST_KEY = "test-key-12345";

// Use obviously fake placeholders
const TEST_KEY = "REPLACE_WITH_REAL_KEY";
7Prevent future secret commits with pre-commit hooks

Set up local checks to catch secrets before they reach GitHub:

Using git-secrets (AWS tool):

bash
# Install git-secrets
# macOS: brew install git-secrets
# Linux: see https://github.com/awslabs/git-secrets

# Initialize in your repository
cd your-repo
git secrets --install
git secrets --register-aws

# Add custom patterns
git secrets --add 'sk-[a-zA-Z0-9]{48}'  # OpenAI keys
git secrets --add 'ghp_[a-zA-Z0-9]{36}' # GitHub tokens

# Scan existing history
git secrets --scan-history

Using pre-commit with detect-secrets:

yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']
bash
# Install and run
pip install pre-commit detect-secrets
pre-commit install

# Create baseline (for existing false positives)
detect-secrets scan > .secrets.baseline

Using gitleaks:

bash
# Install gitleaks
# macOS: brew install gitleaks

# Scan before committing
gitleaks detect --source . --verbose

# Add as pre-commit hook
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks
8Configure .gitignore to exclude sensitive files

Prevent secrets from being committed by ignoring sensitive files:

gitignore
# .gitignore

# Environment files
.env
.env.local
.env.*.local
.env.development
.env.production

# Secret configuration
secrets.json
secrets.yaml
secrets.yml
config/secrets.*

# Private keys and certificates
*.pem
*.key
*.p12
*.pfx
id_rsa
id_ed25519

# Cloud provider credentials
.aws/credentials
.azure/credentials
gcloud-service-account.json
*-credentials.json

# Editor and IDE secrets
.idea/httpRequests/
.vscode/settings.json

# Docker secrets
docker-compose.override.yml

Use template files for configuration:

bash
# Create a template without real secrets
cp config.json config.json.example

# Edit config.json.example to use placeholders
# Add config.json to .gitignore
echo "config.json" >> .gitignore

# Commit only the example
git add config.json.example .gitignore
git commit -m "Add config template"

For environment variables, use .env.example:

bash
# .env.example (committed)
DATABASE_URL=postgres://user:password@localhost:5432/db
API_KEY=your-api-key-here

# .env (gitignored, real values)
DATABASE_URL=postgres://realuser:realpass@prod.db:5432/proddb
API_KEY=sk-abc123...

Advanced notes

### Understanding GitHub's Secret Scanning Patterns

GitHub secret scanning detects secrets from over 200 service providers including:
- Cloud providers: AWS, Azure, GCP, DigitalOcean
- Payment processors: Stripe, PayPal, Square
- Communication: Twilio, SendGrid, Mailchimp
- Development tools: npm, PyPI, Docker Hub
- Databases: MongoDB, Redis, PostgreSQL connection strings

The detection uses pattern matching for known token formats. For example:
- GitHub tokens: ghp_, gho_, ghu_, ghs_, ghr_ prefixes
- AWS access keys: AKIA prefix with 16 uppercase alphanumeric characters
- OpenAI keys: sk- prefix with 48 alphanumeric characters

### Push Protection vs. Secret Scanning Alerts

GitHub offers two related features:

1. Push Protection (blocks pushes) - Prevents secrets from entering the repository
2. Secret Scanning Alerts (notifies after push) - Scans existing history and alerts on findings

Push protection is more secure but can block legitimate work if false positives occur.

### Organization-Level Configuration

Repository admins and organization owners can configure push protection:

bash
Repository Settings > Code security and analysis > Secret scanning
  ✓ Enable secret scanning
  ✓ Enable push protection

Organizations can also:
- Enable push protection for all repositories
- Restrict who can bypass push protection
- Require review for bypass requests
- Define custom secret patterns

### Working with Custom Secret Patterns

Organizations can define custom patterns for internal credentials:

regex
# Example custom pattern for internal API keys
internal-api-key-[a-f0-9]{32}

Custom patterns may have higher false positive rates than GitHub's built-in patterns.

### Dealing with Secrets in Pull Requests

If a PR contains secrets:
1. The PR cannot be merged until secrets are removed
2. Close the PR without merging
3. Have the contributor rewrite their branch history
4. Open a new PR from the cleaned branch

### History Rewriting Impact

When you force push to remove secrets:
- All commit hashes after the rewritten commit change
- Open PRs may need to be rebased
- CI/CD pipelines referencing specific commits may break
- Collaborators need to rebase or reclone

Communicate with your team before force pushing to shared branches.

### Checking if Your Secret Was Exposed

Even though push protection blocked the push, check for exposure:

bash
# Check GitHub's API for secret scanning alerts
gh api repos/{owner}/{repo}/secret-scanning/alerts

# Check if the secret appears in any public repository
# Use GitHub search: "YOUR_SECRET_HERE" (be careful with this)

### Enterprise Considerations

For GitHub Enterprise:
- Push protection requires GitHub Advanced Security license
- Admins can configure organization-wide policies
- Self-hosted runners may need specific network access for scanning
- Custom patterns can be managed at the enterprise level

Frequently asked questions

What does "GH009" mean?
This error indicates that GitHub's secret scanning push protection has detected what appears to be sensitive information in one or more of your commits. GitHub automatically scans pushes for known secret patterns such as API keys, tokens, passwords, and private keys to prevent accidental credential exposure. When push protection is enabled (which is the default for public repositories and can be enabled for private repositories), GitHub analyzes the content of your commits before accepting the push.
How do I fix "GH009"?
The 'GH009: Secrets detected' error occurs when GitHub's push protection feature detects credentials, API keys, or other sensitive data in your commits. This security feature blocks the push to prevent accidental exposure of secrets in your repository.
What causes "GH009"?
Accidentally committed a real API key, token, or password Committed a configuration file containing credentials Included a .env file or secrets file in the commit
Is the "GH009" error serious?
Yes. This is a critical-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading