Secrets Scanning

Find credentials, API keys, and tokens accidentally committed to your repos — both in the current code and the full git history. Validates each finding against the issuing provider to confirm it's still live.

What Secrets Scanning catches

Pattern-matched credentials in source files (any extension), config files (.env, .yaml, .json), notebooks (.ipynb), and the full git history including deleted files and rewritten branches. Coverage spans 140+ provider signatures.

Providers detected

AWS (access keys, IAM tokens)
GCP (service account JSON, API keys)
Azure (storage, AD, KeyVault)
Stripe (sk_live + rk_live + webhook secrets)
GitHub PATs + OAuth tokens
GitLab tokens
Bitbucket app passwords
OpenAI / Anthropic / Gemini
HuggingFace
Slack bot tokens + webhooks
Discord webhooks
Twilio + SendGrid
Cloudflare API tokens
DigitalOcean PATs
DataDog + NewRelic
Snowflake JWTs
DB connection strings (postgres / mysql / mongo)
Generic JWT / PEM private keys / SSH keys
PGP private key blocks

How a scan works

  1. Repo clone. Sectora pulls a shallow clone for current-tree scanning and a full clone (with all branches + tags) when history scanning is enabled.
  2. Pattern match. 140+ provider regexes run on every file (current tree) and every blob ever committed (history scan). Patterns are tuned for low FP — they look for the actual key format, not just secret=.
  3. Entropy check. For generic patterns (e.g. "long random string near the word 'password'"), Shannon entropy filters out structured strings (UUIDs, hashes) that aren't real credentials.
  4. Validation. Every matched credential is tested against its provider — does this AWS key still work? Does this Stripe sk_live still authorize? Live keys get severity Critical; revoked keys get severity Medium (still useful to know they leaked, less urgent).
  5. FP suppression. Test fixtures and documentation get their own allowlist (more below).

Live validation

Validation logic per provider:

  • AWSGetCallerIdentity via STS (read-only, lists no resources).
  • StripeGET /v1/balance (returns 200 if key is valid + which account).
  • GitHubGET /user with the token.
  • OpenAI / Anthropic / Gemini — list-models endpoint.
  • Slackauth.test endpoint.

All validation calls are observed by the provider — your security team should expect a single "Sectora validation probe" access log entry per finding. We use a clearly-identifiable User-Agent so this isn't mistaken for an attack.

Git history scanning

Enabled by default. Walks every commit on every branch and every tag, hashing each blob once for deduplication. Catches secrets that were committed and later removed — these are still valid because git never actually deletes anything by default. The blob is still in the repo's pack files; anyone who clones can recover it.

History scanning roughly doubles scan time on a typical repo and 10× on monorepos with long histories. Disable per-scan if speed matters more than completeness (e.g. PR gate); leave on for nightly + ad-hoc scans.

False positive suppression

Three layers, applied in this order:

  1. Built-in test-fixture allowlist. Patterns in *_test.*, tests/, __tests__/, fixtures/, examples/, *.example.*, *.template.* are auto-downgraded to Info severity. These are usually placeholder keys.
  2. Known-fake patterns. Stripe's public test key sk_test_4eC39HqLyjWDarjtT1zdp7dc and similar provider-published example keys are auto-suppressed.
  3. Your allowlist. Settings → Secrets → Allowlist accepts per-finding suppression (by SHA-256 of the matched secret + file) or per-pattern suppression. Use the per-finding form when the secret is intentionally committed (e.g. a public-by-design publishable key) — narrower scope, less risk of suppressing a real one later.

Remediation flow

When a live secret is found, the recommended sequence:

  1. Rotate first. Generate a new credential at the provider; deploy it to your environment via Secret Manager. The leaked one is compromised whether or not anyone has used it yet.
  2. Revoke the old one. Most providers (AWS, Stripe, GitHub) have a one-click revoke. Some (legacy on-prem) require restarting the issuing service.
  3. Audit logs at the provider. AWS CloudTrail, Stripe Events, GitHub audit log — look for any usage from IPs you don't recognize between the commit time and your rotation.
  4. Then think about git history. Removing the file in a new commit is NOT enough — the blob is still in pack files. To fully purge: git filter-repo --invert-paths --path leaked-file.env, force-push, and have every collaborator re-clone. Most teams don't bother once the credential is rotated + revoked, since the leaked credential no longer works.

CI/CD integration

# .github/workflows/sectora-secrets.yml
# Pre-merge gate: block any PR that introduces a new secret.
name: Sectora Secrets
on: pull_request

jobs:
  secrets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0    # full history for accurate diff
      - name: Run secrets scan + block on any new finding
        env:
          SECTORA_API_KEY: ${{ secrets.SECTORA_API_KEY }}
        run: |
          SCAN_ID=$(curl -sf -X POST https://api.sectora.io/api/v1/secrets/scans \
            -H "Authorization: Bearer $SECTORA_API_KEY" \
            -d '{
              "target": "${{ github.repository }}",
              "branch": "${{ github.head_ref }}",
              "scan_history": false,
              "fail_on_validated_only": true
            }' | jq -r .data.id)

          # ZERO new validated secrets allowed
          curl -sf -H "Authorization: Bearer $SECTORA_API_KEY" \
            "https://api.sectora.io/api/v1/secrets/scans/$SCAN_ID/diff?baseline=main" \
            | jq -e '.new | length == 0'

fail_on_validated_only: trueignores pattern-only matches that didn't validate live — keeps the PR gate from blocking on placeholder keys in newly-added test files. Daily scheduled scans should leave this off to catch placeholders too.

Pre-commit hook (catch before commit, not after)

For developers who want to catch secrets before they ever leave the machine, Sectora ships a pre-commit hook that runs the same patterns locally:

# .pre-commit-config.yaml
- repo: https://github.com/sectora/secrets-precommit
  rev: v1
  hooks:
    - id: sectora-secrets-staged

Runs offline (patterns only, no live validation) — fast enough for every commit (<200ms on typical changes). Validation happens later in CI.

Troubleshooting

"Scan reports a leak I rotated already"

Validation status is captured at scan time. The leaked credential is still IN the git history even if you've rotated; the validation may have shown it as live before rotation. Re-run the scan to refresh the validation status — it'll downgrade to Medium (was-live-now-revoked) which is the accurate state.

"Some keys aren't being detected"

  • For custom internal key formats, add a regex via Settings → Secrets → Custom patterns.
  • Generic high-entropy strings need surrounding context (e.g. password=, token:) to fire — bare strings without context are too FP-prone.
  • Binary files are NOT scanned (would FP-storm on compressed/encrypted data); check the file is text.

"Validation calls are appearing in my AWS CloudTrail"

Expected. Each finding triggers one read-only validation call (e.g. sts:GetCallerIdentity). User-Agent is Sectora-Secrets-Validator/1.0. If you want to skip validation entirely (pattern-only scanning), set validate_findings: false on the scan options. Severity falls back to a constant-Medium for everything since liveness is unknown.

What's next

  • SAST — secrets in code can also indicate broader misuse patterns; SAST catches the surrounding flow.
  • SCA — dependency vulnerabilities; complement to leaked-secret coverage.
  • Integrations — auto-ticket leaked secrets into Jira/Linear/GitHub Issues.
  • PR Gates — block PRs that introduce new secrets.