PRACTITIONER GUIDE | APPSEC
Practitioner GuideUpdated 11 min read

GitHub Secret Scanning and Push Protection: How to Deploy and Tune It So Developers Are Not Blocked by False Positives

200+
secret types supported by GitHub's built-in secret scanning patterns, including AWS keys, Azure credentials, Google Cloud, Stripe, Slack, and dozens more third-party services
Push protection
blocks the push before the commit enters the repository -- retroactive scanning after the fact is less valuable because the secret may already be visible in the commit history
Custom patterns
let organizations define regex patterns for internal credential formats (internal API keys, VPN certificates, proprietary tokens) that GitHub's default patterns do not cover
Bypass
push protection can be bypassed by individual developers -- track bypass events and require security team review for bypass reasons that indicate intentional credential storage

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

GitHub Advanced Security (GHAS) secret scanning with push protection is one of the highest-impact developer security controls available because it prevents the credential leakage that enables a substantial portion of cloud compromise incidents. The common deployment mistake is enabling it at the organization level without any custom patterns or bypass governance, leading to either high false-positive rates (from overly aggressive patterns) or gaps in coverage (from relying only on built-in patterns that do not cover internal credential formats).

Enabling Secret Scanning and Push Protection at Org Level

Secret scanning and push protection require GitHub Advanced Security licenses (included in GitHub Enterprise Cloud/Server). Enable at the organization level to apply to all repositories:

Via GitHub API:

# Enable secret scanning for all repos in an org
curl -X PATCH \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  https://api.github.com/orgs/your-org \
  -d '{"secret_scanning_enabled_for_new_repositories": true}'

# Enable push protection for all repos in an org
curl -X PATCH \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  https://api.github.com/orgs/your-org \
  -d '{"secret_scanning_push_protection_enabled_for_new_repositories": true}'

For existing repositories, use the GitHub REST API to enable on each:

# Enable on a specific repository
curl -X PATCH \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  https://api.github.com/repos/your-org/your-repo \
  -d '{"security_and_analysis": {"secret_scanning": {"status": "enabled"}, "secret_scanning_push_protection": {"status": "enabled"}}}'

Or via GitHub CLI for bulk enablement:

# Enable on all repos in an org using gh CLI
gh api /orgs/your-org/repos --paginate | \
  jq '.[].name' -r | \
  while read repo; do
    gh api --method PATCH /repos/your-org/$repo \
      -f 'security_and_analysis[secret_scanning][status]=enabled' \
      -f 'security_and_analysis[secret_scanning_push_protection][status]=enabled'
    echo "Enabled: $repo"
  done

Creating Custom Patterns for Internal Credential Formats

Built-in patterns cover major third-party services but not internal systems. Create custom patterns for:

  • Internal API gateway tokens
  • Service-to-service authentication tokens
  • Internal PKI certificate patterns
  • Database connection strings with internal domain patterns

Create a custom pattern via the GitHub API:

# Create an org-level custom pattern
curl -X POST \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  https://api.github.com/orgs/your-org/secret-scanning/patterns \
  -d '{
    "name": "Internal API Key",
    "pattern": "INTERNAL-[A-Z0-9]{32}",
    "secret_type": "internal_api_key",
    "keywords": ["INTERNAL-"],
    "description": "Internal API gateway authentication key"
  }'

Pattern design principles for low false-positive rates:

  1. Require a keyword prefix: Patterns without a keyword anchor (like [A-Za-z0-9]{40}) match too broadly. Require a distinguishing prefix: apikey_, sk_live_, INTERNAL-, etc.
  2. Specify minimum entropy: High-entropy random strings are more likely to be real secrets. Use character class ranges to enforce entropy: [A-Za-z0-9!@#$%]{32,64}
  3. Test against false positive samples before deployment: GitHub allows dry-run testing of custom patterns against a sample of recent commits before activating them
  4. Use the test functionality: Settings > Security > Code security and analysis > Custom patterns > [pattern] > Test -- paste sample content and verify the pattern matches real secrets but not example values in documentation
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.

Bypass Request Governance

Push protection can be bypassed by developers when they have a legitimate reason (test credentials, example values, already-rotated secrets). Track and review bypass events:

# List all push protection bypass events in an org via API
curl -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "Accept: application/vnd.github+json" \
  "https://api.github.com/orgs/your-org/secret-scanning/alerts?state=open&resolution=used_in_tests" \
  | jq '.[] | {repo: .repository.full_name, secret_type: .secret_type, created_at: .created_at, resolution: .resolution}'

Bypass reasons developers can select:

  • It's used in tests: The secret is a test/example value with no real access
  • It's a false positive: The pattern matched something that is not actually a secret
  • I'll fix it later: The developer acknowledges it is a real secret but will rotate it

"I'll fix it later" is the high-risk bypass reason. Configure a workflow that:

  1. Sends an immediate Slack/Teams notification to the security team when this reason is selected
  2. Creates a JIRA ticket with a 48-hour SLA for the developer to confirm the secret has been rotated
  3. Follows up after 48 hours if the secret has not been rotated and the alert remains open

Configure this via GitHub Actions on the secret_scanning_alert webhook event:

# .github/workflows/secret-alert-webhook.yml
on:
  secret_scanning_alert:
    types: [created]
jobs:
  notify-security:
    runs-on: ubuntu-latest
    steps:
      - name: Notify on push protection bypass
        if: github.event.alert.push_protection_bypassed == true
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
            -H 'Content-type: application/json' \
            --data '{"text":"Push protection bypassed: ${{ github.event.alert.secret_type }} in ${{ github.event.repository.full_name }}"}'

Alert Triage and Remediation Workflow

Secret scanning generates alerts for secrets found in repository content. Triage alerts by severity:

Immediate response (within 1 hour):

  • AWS access keys, Azure client secrets, GCP service account keys
  • Database connection strings with production credentials
  • Active OAuth tokens for production applications

For each high-severity alert:

  1. Verify the secret is still active (check the provider's API for key validity)
  2. Revoke the secret immediately at the source (AWS IAM, Azure portal, GCP IAM)
  3. Generate a replacement credential
  4. Update the application using the credential (via Secrets Manager or environment variable injection)
  5. Remove the secret from git history (if it was committed, git history rewrite is required)

Removing a secret from git history:

# Use BFG Repo Cleaner (faster than git filter-branch)
bfg --replace-text secrets-to-remove.txt your-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force --all
git push --force --tags

Note: git history rewrite requires force push and affects all forks. Notify all contributors to re-clone after history rewrite.

Lower priority (within 48 hours):

  • Test credentials with no production access
  • Internal system credentials for dev/staging environments
  • Credentials that are already rotated

For lower-priority alerts, verify the credential is no longer active and close the alert with the appropriate resolution.

Measuring Program Effectiveness

Track key metrics to demonstrate secret scanning program value and identify gaps:

# Count open secret scanning alerts by secret type
curl -H "Authorization: Bearer $GITHUB_TOKEN" \
  "https://api.github.com/orgs/your-org/secret-scanning/alerts?state=open&per_page=100" \
  | jq 'group_by(.secret_type) | map({type: .[0].secret_type, count: length}) | sort_by(.count) | reverse'

# Count alerts resolved in the last 30 days
curl -H "Authorization: Bearer $GITHUB_TOKEN" \
  "https://api.github.com/orgs/your-org/secret-scanning/alerts?state=resolved&per_page=100" \
  | jq '[.[] | select(.resolved_at > "'$(date -v-30d +%Y-%m-%d)'")] | length'

# Track bypass rate (bypasses as % of push protection blocks)
curl -H "Authorization: Bearer $GITHUB_TOKEN" \
  "https://api.github.com/orgs/your-org/secret-scanning/alerts?state=open" \
  | jq '[.[] | select(.push_protection_bypassed == true)] | length'

Monthly security metrics to report:

  • Total alerts generated vs. resolved
  • Mean time to remediation (MTTR) for critical secret types
  • Push protection block rate (pushes blocked / total push protection evaluations)
  • Bypass rate by reason category
  • Custom pattern coverage vs. internal credential format inventory

The bottom line

GitHub secret scanning with push protection is most effective when enabled at the org level, supplemented with custom patterns for internal credential formats, and backed by bypass governance that creates accountability for 'I'll fix it later' bypasses. The alert triage workflow must differentiate between high-severity production credentials (rotate within 1 hour) and lower-priority test/dev credentials. Git history rewrite is required when a real production secret has been committed, not just alert closure.

Frequently asked questions

Does enabling push protection break any existing CI/CD pipelines?

Push protection only affects pushes that contain newly detected secrets. Existing commits already in the repository are not re-evaluated for push protection purposes (they may generate secret scanning alerts, but not push protection blocks). CI/CD pipelines that read secrets from environment variables or secrets management systems (not from committed files) are not affected. The only pipelines that break are those that commit example credentials or test credential files as part of their operation -- these should be refactored to use test credential patterns that are added to the custom pattern allow list.

How do I prevent secret scanning from flagging example credentials in documentation?

Use two approaches: first, use clearly fake credential formats in documentation (e.g., EXAMPLE-123456789012345678 instead of a realistic-looking random string). Second, add a `.github/secret_scanning.yml` file to the repository to exclude specific paths from scanning: `paths-ignore: ['docs/**', 'examples/**']`. Be careful with path exclusions -- they can create gaps where real secrets in 'docs' folders are ignored.

What happens when a developer bypasses push protection and later the secret is used in an attack?

The bypass event is logged in the GitHub audit log and the secret scanning alert records the bypass reason and the developer's identity. This creates an audit trail for post-incident analysis. From a governance perspective, require developers to acknowledge that bypassing push protection for a real credential (not a test value) is a security policy violation. The 'I'll fix it later' bypass reason should trigger an immediate rotation SLA enforced by the security team, not just a developer promise.

Does GitHub secret scanning work on private repositories?

Yes, but it requires GitHub Advanced Security (GHAS) licenses for private repositories. GHAS is included in GitHub Enterprise Cloud and GitHub Enterprise Server. Public repositories get secret scanning (but not push protection) for free. For organizations on GitHub Team or GitHub Free, secret scanning is available only for public repositories. Check your license tier before enabling at the organization level to understand which repositories will be covered.

How do I set up custom patterns to detect organization-specific secrets that GitHub's built-in patterns miss?

In your GitHub organization settings under Code security > Secret scanning, select Custom patterns and add a new pattern. Define the pattern using a regular expression that matches your internal secret format -- for example, a pattern like `INTERNAL-[A-Z0-9]{32}` for an internal API key format. Test the pattern against a sample secret to confirm it matches correctly before saving. GitHub also supports specifying a 'before secret' and 'after secret' context pattern to reduce false positives by requiring that the secret appear in a specific context (e.g., within an assignment statement like `apiKey=`). After saving, the pattern applies to all new pushes and optionally to all historical commits on push-protected branches.

What is the process for remediating a detected secret after GitHub secret scanning fires?

Remediating a detected secret requires three steps: invalidate the exposed credential, remove it from the repository history, and understand the exposure window. First, invalidate immediately by rotating the credential in the issuing system (revoke the API key, rotate the database password, disable the service account). Do not wait to assess the severity before invalidating -- the cost of rotating is low, and the window of exposure starts from the first commit that introduced the secret. Second, remove from Git history: a simple commit that deletes the file or removes the line is insufficient, as the secret remains in Git history. Use git-filter-repo (the recommended tool) or BFG Repo Cleaner to rewrite history, then force-push with coordinated team communication. Third, assess exposure: check the secret's usage logs (API gateway logs, cloud provider audit logs) for any access during the period between initial commit and invalidation. If unauthorized access is detected, treat it as a potential breach and escalate to your incident response process.

Sources & references

  1. GitHub: About secret scanning
  2. GitHub: Enabling secret scanning for your organization
  3. GitHub: Push protection for repositories

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.