Help us shape the future of Go! Take the 2026 Go Developer Survey

secret-scanner

module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2026 License: Apache-2.0

README

secretscan

ci License Go Reference

Catch real secrets. Ignore the noise. Keep every commit moving.

secretscan is a precision-first secret scanner. Alert fatigue is the real failure mode of secret scanning: when developers keep seeing harmless matches, they add broad exclusions or bypass the hook. secretscan keeps broad coverage, cuts unnecessary alerts, and refuses to buy that by quietly missing real secrets. Both directions are measured against gitleaks and detect-secrets on the same corpus.

It scans files, git history, staged changes, outgoing pushes and raw diffs as a single dependency-free Go binary, and its triage command adds the same precision layer on top of gitleaks, trufflehog or any SARIF report.

  • Evidence, not entropy: provider formats are high confidence on their own. Generic matches need several independent signals (credential-named key, randomness, composition, literal, location) and get a confidence level with the reasons. See docs/precision.md.

  • Strict or balanced: strict blocks every finding; balanced blocks high and medium confidence and generic values in test files, and lists only weak evidence for review. Git hooks default to balanced, scans and CI to strict. The trade-off is measured, and a provider token is never downgraded because it sits in tests/.

  • No silent passes: every filtered candidate is counted and listable with its reason (--show-suppressed). Suppressions are narrow, and regression tests prove none of them hides a valid secret next to it.

  • 130+ built-in rules: AWS (incl. Bedrock API keys), Azure (Entra ID secrets, storage keys, SAS, new and legacy Azure DevOps PATs), GCP, GitHub, GitLab, JFrog, Vault, Terraform Cloud, Slack, Stripe, OpenAI, Anthropic, LiteLLM, Grafana, Datadog, private keys, credentialed URLs, JWTs, generic secrets.

  • Custom patterns: via TOML config, --pattern flags or a patterns file.

  • Drop-in gitleaks compatibility: config schema, fingerprints, .gitleaksignore, gitleaks:allow and JSON report shape all carry over.

  • Noise filters with named reasons: a character-level language model and structural checks reject identifiers, words, paths, digests, UUIDs and published sample credentials. See How noise is filtered.

  • Finds hidden secrets: recursively decodes base64, hex and percent-encoding.

  • Safe output: secrets are redacted by default (80% masked), and report files are written with mode 0600. Reports carry a fingerprint of each secret so baselines work without storing it. A plain SHA-256 fingerprint is not anonymous: anyone with the report can confirm a guessed weak password. Set SECRETSCAN_HASH_KEY for keyed hmac-sha256: fingerprints, or share -f stats (counts only). See Secret fingerprints.

  • Optional live verification (--verify) for GitHub, GitLab, Slack, Stripe, OpenAI, Anthropic, Hugging Face, npm, SendGrid, DigitalOcean, Groq and Linear.

  • Fix guidance: every finding says how urgent it is (verified-live means revoke now), how to rotate it at the issuer, how to avoid static secrets (workload identity, OIDC, managed identity), and the replacement code in the file's language. secretscan explain gives efficient loading patterns for env, Vault, AWS, Azure and GCP, and --fix-patch writes a reviewable patch that turns literals into env lookups. See docs/using-secrets.md.

  • Reports in text, JSON, SARIF 2.1.0, CSV and JUnit XML, plus an anonymized stats summary for sharing results.

Install

One command on Linux or macOS (amd64/arm64). It downloads the release binary, verifies it against the release's SHA256SUMS and installs nothing if the check fails:

curl -fsSL https://raw.githubusercontent.com/AISolutionLabs/secret-scanner/main/install.sh | sh
# a specific version, or another directory
curl -fsSL https://raw.githubusercontent.com/AISolutionLabs/secret-scanner/main/install.sh | BINDIR=/usr/local/bin sh -s -- v1.1.0

On Windows (PowerShell), with the same checks:

irm https://raw.githubusercontent.com/AISolutionLabs/secret-scanner/main/install.ps1 | iex

Or with Go 1.24+, or a binary from Releases:

go install github.com/AISolutionLabs/secret-scanner/cmd/secretscan@v1.1.0
make build            # from a checkout -> bin/secretscan

Every release is installed from scratch on Linux, macOS and Windows by CI after it is published (install script, go install at the tag and, on Linux and macOS, the pre-commit hook), with the signature required, and must block a freshly generated token. The installers are also tested on all three systems on every push, including refusing a tampered binary.

Verifying a release. SHA256SUMS is signed by the release workflow with Sigstore (keyless: the signing certificate names this repository's release.yml and the tag), and every binary has a GitHub build-provenance attestation. With cosign installed, install.sh checks the signature automatically (SECRETSCAN_REQUIRE_SIGNATURE=1 makes it mandatory). By hand:

cosign verify-blob SHA256SUMS --bundle SHA256SUMS.sigstore.json \
  --certificate-identity-regexp '^https://github.com/AISolutionLabs/secret-scanner/\.github/workflows/release\.yml@refs/tags/v' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
sha256sum --ignore-missing -c SHA256SUMS
gh attestation verify secretscan-linux-amd64 --repo AISolutionLabs/secret-scanner

Quick start

secretscan dir .                       # scan the working tree
secretscan git                         # scan all git history (all refs)
secretscan git --staged                # what you're about to commit
secretscan git --log-opts "origin/main..HEAD"   # a commit range
git diff | secretscan stdin --diff     # scan only added lines of a diff
cat file | secretscan stdin --label file.env

secretscan install-hook --type both    # pre-commit + pre-push hooks
secretscan triage gitleaks.json        # re-score another scanner's report

Exit codes: 0 clean · 1 secrets found (--exit-code N to change) · 2 usage or config error.

Policies, confidence and the suppression ledger

[CRITICAL·high] github-pat GitHub personal access token  tests/test_gh.py:1:7     <- blocks under both policies
── Needs review (not blocking under the balanced policy) ──
[MEDIUM·low] generic-api-key Generic API key, token or secret  tests/test_cfg.py:2:18
    why:     assigned to a credential-named key; mixed character classes or long value; literal value
             in code or config; in a test/example path (no downgrade for provider formats);
             capped at low: unstructured value in a test/example file
secretscan: 2 secrets found (1 critical, 1 medium) · scanned 3 files (490 B) in 1ms
  policy balanced: 1 blocking, 1 to review · 3 candidates filtered as noise (2 word-like, 1 uuid; --show-suppressed to list)
  • --policy strict|balanced, or policy = "balanced" in .secretscan.toml. Without either, dir/git/stdin scans use strict and installed git hooks use balanced (install-hook --policy strict to change it).
  • Balanced blocks high and medium confidence, and generic values in test/example files. Only weak evidence (low confidence: dictionary-style passwords, values with few supporting signals) is printed under "Needs review" without blocking. On CredData repositories never used for design (2026-10-17), balanced with the default testFixtures = "block" blocks 39.9% of labeled secrets at 90.2% precision; strict blocks everything it reports, 59.2% at 91.5%.
  • A real credential committed as a test fixture is still a credential, so fixtures block by default. A repository whose tests are full of fake credentials can set testFixtures = "review" in .secretscan.toml (or --test-fixtures review) to list them for review instead; balanced then blocks 22.2% of those labeled secrets at 87.4%. Provider-format tokens (ghp_, AKIA, private keys, ...) block in test files under every setting.
  • Keep CI on strict so nothing merges without a decision.
  • Every finding has Confidence (high/medium/low), Evidence and Blocking in JSON. SARIF levels follow the policy (blocking = error).
  • Confidence is a qualitative evidence level, not a probability: measured precision per level is published and is not monotonic on CredData (why).
  • --min-confidence medium hides weaker findings (still counted in the summary).
  • --show-suppressed lists every candidate a filter or allowlist removed: file, line, rule, reason and redacted value.

Triage: a precision layer for other scanners

Keep your existing scanner's rules and cut its noise:

# Preferred: stream the report, so no unredacted copy is written to disk
gitleaks dir . -f json -r - --no-banner | secretscan triage - --policy balanced
# Partially redacted reports work too (prefix matching), with fewer secrets exposed
gitleaks dir . -f json -r - --redact=50 | secretscan triage -
# If you must use a file: keep it outside the repo and delete it afterwards
secretscan triage /tmp/gl.json --delete-input          # also trufflehog JSON lines or any SARIF

Triage needs the reported values to re-check them, so a report file is a copy of your secrets. secretscan warns when it reads an unredacted report or one that other users can read. It reads source only inside --root (default: the current directory), rejects absolute paths, ../ and symlinks that lead outside it, and for history findings reads the file at the reported commit: if that commit is unavailable, the finding stays uncorroborated instead of being checked against today's file.

Each finding comes back as verified, confirmed (secretscan independently finds it), uncorroborated (kept for review) or filtered: <reason> (for example digest-or-commit-hash, word-like, identifier). Filtered findings are listed, never dropped. Values with a provider-specific shape are never filtered by word heuristics, and redacted or unreadable findings are kept. Exit codes follow the policy.

Triage runs in conservative mode by default: a value assigned to a credential-named key (access_token, secretVal, "nextToken":) is never filtered for its shape alone (UUID, identifier-like, word-like), because real credentials take those shapes. Structural filters (digests, truncated or published examples, placeholders, sequences) and values that name a secret (prod-db-secret-id) still apply, and UUIDs under request-style keys (request_token, idempotency_key) are still IDs. --mode standard applies

every filter: fewer findings to review, more real secrets lost. On CredData repositories never used to design it, conservative lost 54 of gitleaks' 1,110 true findings (41 of them AWS WAF ChangeToken values, deliberately not treated as credentials) and standard lost 105.

Git hooks

secretscan install-hook --type pre-commit|pre-push|both writes hooks into git rev-parse --git-path hooks, so core.hooksPath is respected.

  • Hooks use the balanced policy unless you pass --policy or set it in config (generic values in test files block too, unless testFixtures = "review"), and the installer prints what that means. Every blocked finding is explained (why:).
  • pre-commit scans only the staged diff. Typical commits take milliseconds.
  • pre-push reads the refs git passes on stdin and scans only commits that aren't on the remote yet. This catches secrets committed with --no-verify.
  • An existing hook is preserved as <hook>.local and chained after the scan.
  • Hooks fail closed if the binary is missing. Set SECRETSCAN_OPTIONAL=1 to fail open, or SECRETSCAN_SKIP=1 for an explicit, logged one-off bypass. Set SECRETSCAN_BIN to point at a specific binary.

Hooks are a developer convenience and can be bypassed. Enforce with CI or a push proxy (below).

With the pre-commit framework, the repo ships a .pre-commit-hooks.yaml:

repos:
  - repo: https://github.com/AISolutionLabs/secret-scanner
    rev: v1.1.0
    hooks:
      - id: secretscan          # staged changes
      - id: secretscan-push     # outgoing commits (stages: [pre-push])

Custom patterns

There are three ways to add patterns. All are additive to the built-in pack, and a custom rule with the same id as a built-in replaces it.

1. Config file: .secretscan.toml in the scan root is auto-discovered, then .gitleaks.toml. You can also use -c path or $SECRETSCAN_CONFIG. See examples/secretscan.toml.

[[rules]]
id = "acme-service-token"
description = "ACME internal service token"
regex = '''\b(acme_(?:live|test)_[A-Za-z0-9]{32})\b'''
secretGroup = 1                  # capture group holding the secret (default: first group)
keywords = ["acme_live_", "acme_test_"]   # fast prefilter
entropy = 3.5                    # minimum Shannon entropy of the secret
path = '''\.(?:env|ya?ml)$'''    # only these files; path alone = file-name rule
severity = "critical"            # critical | high (default) | medium | low | info
tags = ["internal"]
examples  = ["ACME_TOKEN=acme_live_abcdefghijklmnopqrstuvwxyz012345"]   # self-tests
negatives = ["acme_live_short"]

  [rules.allowlist]
  regexes = ['''acme_test_0{32}''']

Config-level controls:

[extend]
useDefault = false                # use ONLY your rules (default: true)
path = "../org/secretscan.toml"   # inherit an org-wide baseline (chains allowed)
disabledRules = ["jwt"]

2. Flags. These are repeatable. Options follow ;;:

secretscan dir . --pattern 'corp-key=CORP-[A-Z0-9]{16}'
secretscan dir . --pattern 'tenant=(?i)tenant_secret\s*=\s*(\w{16}) ;; group=1 ;; entropy=3 ;; severity=high ;; keywords=tenant'

3. Patterns file. Use --patterns-file patterns.txt, with one pattern per line in the same syntax. # starts a comment.

If a custom rule has no keywords, a literal regex prefix (e.g. CORP-) is used as the prefilter automatically. secretscan validate compiles everything and runs each rule's examples and negatives, which makes it a good CI check for config changes. secretscan rules [--tag aws] [-f json] lists the active rules.

Fixing findings

Each finding carries remediation in every output format (Remediation in JSON, help and properties.remediation in SARIF):

[CRITICAL] github-pat GitHub personal access token  app/settings.py:5:17
    secret:  ghp_C3J2REDACTED
    fix:     rotate · Revoke the token (Settings > Developer settings > Tokens; ...) and review the org audit log.
    better:  In Actions use the built-in GITHUB_TOKEN with least-privilege `permissions:`, and OIDC for cloud access.
    code:    os.environ["GITHUB_TOKEN"]  (secretscan explain github-pat --lang python)
  • --remediation full prints the step list and a complete code pattern for the target store. --remediation none hides guidance.
  • --secret-store env|vault|aws|azure|gcp chooses the store (default from [remediation] store in config).
  • secretscan explain <rule-id> [--lang L|all] [--store S|all] [--env NAME] covers Python, Go, JS/TS, Java, C#, Ruby, PHP, shell, PowerShell, Kubernetes, Terraform, Dockerfile, GitHub Actions, Azure Pipelines, Spring, .NET config and .env.
  • Prefer --fix-patch FILE: it writes a git apply patch that replaces quoted literals in code with env lookups (adding import os / "os" where needed) for you to review. The patch contains the old secrets, so it's created with 0600 permissions and you're warned if it sits inside the repo.
  • --fix applies the same edits in place, only after an interactive confirmation or --yes. Edited Go, Python and JavaScript files are syntax-checked and an edit that would not parse is skipped; other languages are listed as unchecked. Literals nested inside other strings (struct tags, SQL) are never rewritten. Unquoted values (YAML, .env) are listed for manual moves to a secret store.
  • Replacements fail fast with the variable's name when it is missing (Python, JavaScript/TypeScript, Java, C#, Ruby, shell; Go and PHP are flagged). Literals that must stay constants (const, annotations, attributes, case labels) are skipped. Run your tests. Moving a secret out of the code does not un-leak it: rotate every real secret that was committed, since it stays in git history and clones.
  • Verified-live findings sort first and say REVOKE NOW.

Patterns follow the efficiency rules in docs/using-secrets.md: read once or cache with a TTL, share one client, refresh once on 401, keep the last good value if the store is unavailable, prefer mounted files over env vars for rotation, and never fetch per request. Snippets are syntax-checked in the test suite.

# .secretscan.toml: org defaults for guidance
[remediation]
store   = "azure"
runbook = "https://wiki.example.com/platform/secrets-rotation"

Suppressing findings

Mechanism Use for
Inline comment on the line: secretscan:allow (also gitleaks:allow, pragma: allowlist secret, trufflehog:ignore) one-off false positives
# secretscan:allow-next-line on the line above lines you can't append to
.secretscanignore / .gitleaksignore with file:rule:line or commit:file:rule:line fingerprints (printed as ignore:) accepted findings
.secretscanignore with sha256:<hash> a known secret anywhere, robust to line moves
.secretscanignore with rule:<id> or rule:<id>:<file> silencing a rule globally or per file
--baseline-path old.json adopting the scanner on a repo with existing debt (only new leaks fail)
[[allowlists]] / [rules.allowlist] in config structured, reviewable policy
--exclude-path REGEX ad-hoc path skips

--no-inline-ignore disables inline markers, and --no-noise-filters disables the heuristic filters below. Both are useful for audits.

Secret fingerprints

Every finding carries SecretHash (JSON, CSV) and a SARIF partialFingerprints entry, so ignore entries and baselines can match a secret without storing it.

  • Fingerprints are sensitive metadata, not anonymized data. A report identifies where each secret is and how to recognise it again; reports are written with mode 0600 for that reason.
  • Default: plain SHA-256. Portable (a baseline made anywhere matches anywhere) and unchanged since 1.0.0, but hashing likely passwords and comparing recovers weak ones (Welcome1!) quickly. Keep such reports, CI artifacts and baselines restricted to the people who may see the secrets themselves.
  • Keyed: SECRETSCAN_HASH_KEY=<random, 16+ bytes> (or --hash-key-file FILE). Every fingerprint written to a report is hmac-sha256:<hex> and cannot be tested without the key. Keep the key in your CI secret store. Keyed ignore entries (hmac-sha256:<hex> lines in .secretscanignore) and keyed baselines need the same key; without it a keyed baseline still matches by location (file:rule:line), but a moved secret is reported again. Plain sha256: entries keep working either way.

Options

-c, --config FILE            config file
    --pattern SPEC           extra rule (repeatable)
    --patterns-file FILE     file of patterns
    --no-default-rules       skip the built-in pack
    --enable-rule IDS        only these rules
    --disable-rule IDS       skip these rules
    --tag TAGS               only rules with these tags (aws, azure, ai, vcs, ...)
    --exclude-path REGEX     skip paths
    --ignore-file FILE       fingerprints/hashes to ignore
-b, --baseline-path FILE     JSON report of accepted findings
-f, --report-format FMT      text | json | sarif | csv | junit | stats (anonymized counts)
-r, --report-path FILE       write report to file (format inferred from extension;
                             never written through a symlink, replaced atomically)
    --redact[=N]             mask N% of each secret (default 80; --redact=0 shows all)
    --hash-key-file FILE     keyed hmac-sha256: fingerprints (or SECRETSCAN_HASH_KEY)
    --exit-code N            exit code on findings (default 1)
    --min-severity LEVEL     info | low | medium | high | critical
    --max-target-megabytes N skip larger files (default 10)
    --max-decode-depth N     base64/hex/percent decoding depth (default 2, 0 = off)
    --threads N              workers (default: CPU count)
    --verify                 check secrets against provider APIs
    --only-verified          report only live secrets (with --verify)
    --timeout DUR            abort after DUR
    --remediation MODE       short | full | none (text output guidance)
    --secret-store STORE     env | vault | aws | azure | gcp
    --fix-patch FILE         write a reviewable patch replacing literals with env lookups
    --fix [--yes]            apply those edits in place (asks first unless --yes)
    --policy P               strict (scan default) | balanced (hook default): high and medium block
    --min-confidence C       hide findings below low | medium | high (counted)
    --show-suppressed        list candidates removed by filters/allowlists, with reasons
    --no-color, -q/--quiet, --no-inline-ignore, --no-noise-filters
dir:   --follow-symlinks
git:   --staged | --pre-push [--remote NAME] | --log-opts "ARGS"
stdin: --diff, --label NAME

CI

Azure DevOps (see ci/azure-pipelines.yml):

- script: |
    secretscan git --log-opts "origin/$(System.PullRequest.TargetBranchName)..HEAD" \
      -r $(Build.ArtifactStagingDirectory)/secretscan.sarif
  displayName: Secret scan

GitHub Actions (see ci/github-actions.yml): upload the SARIF output to code scanning.

Both templates publish the SARIF (code scanning, a build artifact), so both read SECRETSCAN_HASH_KEY from the CI secret store: define it with a random value (openssl rand -hex 32) and the published fingerprints are keyed. Do not publish JSON, CSV or SARIF reports without a key where people who should not see the secrets can read them (see Secret fingerprints).

Push proxy / server-side use

For a git push proxy (for example FINOS GitProxy, or your own git-receive-pack interceptor), write the incoming pack to a temporary bare repo and run:

secretscan git /tmp/push.git --log-opts "$OLD..$NEW" -f json --redact -q

Alternatively, pipe the diff of the new commits into secretscan stdin --diff. A non-zero exit means reject the push, and the JSON tells the proxy what to report back.

Verification

--verify calls each provider's read-only identity endpoint (e.g. GitHub /user, Slack auth.test) and marks findings verified, invalid, unverified or error. This sends secrets over the network. Never use it in hooks, and in locked-down networks expect error. Standard HTTPS_PROXY and system CA settings are honored. Only a JSON answer from the provider decides: proxy block or caution pages, rate limits (429) and server errors are unverified, network failures error. GitHub and GitLab tokens the public endpoint rejects stay unverified (they may belong to a self-managed instance).

Benchmark

Full results, versions, misses and reproduction steps: bench/results/2026-10-15.md and 2026-10-16. Everything can be regenerated with bench/reproduce.sh or the benchmark workflow (pinned tools and datasets). Most independent evidence first.

1. Independent precision challenge (Samsung CredData). 11,565 files from 337 real repositories with manually labeled credential lines, scored per line with CredData's method, excluding categories that are identifiers rather than secrets. Repositories are split in half by hash; every improvement is designed on one half and measured once on the other.

Latest holdout (2026-10-17), on 181 repositories never used for design:

Tool (default settings) Precision Recall F1
secretscan 91.5% 59.2% 0.719
secretscan, before the latest round 83.7% 48.0% 0.611
gitleaks 91.8% 28.9% 0.440
gitleaks + secretscan triage 93.6% 27.5% 0.425
trufflehog 3.97.9 (--no-verification) 44.7% 3.4% 0.064

On unseen repositories secretscan matches gitleaks' precision while finding twice as many of the labeled secrets. Under balanced, the hook default (testFixtures = "block"), 39.9% of them block at 90.2% precision. On the half used for design it reaches 98.8% / 89.0%; the gap is the honest measure of how much of that transfers. Triage drops 54 of gitleaks' true findings, 41 of them AWS WAF ChangeToken values that are deliberately not treated as credentials (label disagreements).

The very first CredData run, before any CredData-informed change, is in 2026-10-15.

2. Independent ground truth (leaky-repo, 96 labeled risk lines).

Tool Risk lines found Extra lines (FPs)
secretscan, before any leaky-repo-informed change 36/96 (37.5%) 3
secretscan, current (rules added for the gaps it exposed) 65/96 (67.7%) 4
gitleaks 21/96 (21.9%) 1
trufflehog 3.97.9 10/96 (10.4%) 0
detect-secrets 1.5.0 37/96 (38.5%) 2

3. Manual adjudication of package code. All 132 secretscan findings on 44,284 files of 52 pinned npm/PyPI packages were inspected: 0 live credentials, 116 example credentials that packages ship (test private keys, documented passwords, sample SAS tokens; reported on purpose) and 16 false positives (0.36 per 1,000 files), mostly identifiers in minified bundles.

4. Regression suite (generated data, tuned on). 529 generated secrets, 91 false positives from public gitleaks/trufflehog issues, and staged-commit latency. These numbers catch regressions; they are not proof.

Tool Recall Safety set Issue FPs Findings / 1k package files Commit latency p50
secretscan 529/529 (all block under balanced) 40/40 0/91 2.98 (2.69 blocking under balanced) 44 ms
gitleaks 435/529 28/40 16/91 6.98 85 ms
gitleaks + secretscan triage 435/529 28/40 0/91 5.04 (1.49 blocking under balanced) –
detect-secrets 388/529 28/40 23/91 – –

5. Cost of the noise filters, per alphabet. 20,000 random secrets per alphabet go through the generic pipeline in CI, which fails if filters remove more than 0.02% of any alphabet (0.05% for passwords; TestFalseNegativesPerAlphabet).

trufflehog 3.97.9 (run with --no-verification) finds 3.4% of CredData's secrets on the holdout at 44.7% precision, since it only has provider-specific detectors; its recall reflects detector coverage, not its usual verified-only workflow. See the results.

Not yet shown: a second independently labeled corpus, an outside reproduction, and real developers' dismissal rates. The rules for running the second benchmark (frozen detection code, labels used as given, publish whatever the outcome) and for label disagreements such as ChangeToken are in docs/benchmark-policy.md; bench/score_labeled.py scores any line-labeled corpus. secretscan dir . -f stats prints counts only (no paths, values or fingerprints) for sharing results in an issue.

Reproduce: bench/reproduce.sh, or the benchmark workflow in a fork. Every figure above is generated from bench/results/latest.json; CI fails if any published figure disagrees with it or with the dated results file.

How noise is filtered

Provider rules with distinctive prefixes (ghp_, AKIA, xoxb-, ...) are precise on their own. Nearly all noise comes from generic "keyword = value" rules. Their values must pass these checks, adapted from what each tool does well:

Filter Rejects Idea from
Character-bigram language model ("gibberish" score, internal/wordy), trained on CPython source SecurityProfiles, isOnDemandRevalidate, key-2024-primary detect-secrets (gibberish), trufflehog (word lists)
Code-identifier shape crypto.SHA256, KeyUsageCRLSign, C.GO_RSA_PADDING gitleaks (^[a-zA-Z_.-]+$)
Structure: = inside value, UUIDs, numbers, timestamps, oauth-2025-04-20, ... truncation kwargs, config pairs, IDs, versions detect-secrets
Sequential / decoded-prose values 0123456789…, base64 of a secret or {"foo":"bar"} detect-secrets
Key name says it isn't a secret (matched before the value) public_key_id, NextToken, *Name, *Url, sha256, nonce gitleaks (match allowlist)
Published sample credentials (stored as SHA-256) AWS doc keys, Azure Storage emulator key, jwt.io token, Stripe doc keys trufflehog, gitleaks
Charset-aware entropy floor (raises recall) random hex at 3.0 bits, mixed-class passwords detect-secrets
Symbols only in literals and env-style files a*b+c in code; keeps P@ss!... in .env, YAML, Dockerfile —
Word tokens: split on separators and case, words cover the value headersToExcludeInCacheKey, 0011_update_proxy_permissions —
Value repeats its own key ("webhookKey": "my_webhookKey") generated API-doc placeholders —
Specific rule wins over generic on overlap duplicate reports gitleaks

Each filter is a few lines in internal/detect/detect.go with regression tests drawn from real false positives. --no-noise-filters turns them off for audits.

Performance

All keywords are found in one Aho-Corasick pass, and each rule's regex then runs only in a bounded window around its hits. Token rules keep RE2's literal-prefix fast path: boundary checks happen in code, not in the regex. Decoding candidates come from byte scanners, and huge embedded base64 blobs are skipped. The Go stdlib (92 MB) scans in under 3 s on 2 vCPUs, and a typical pre-commit takes a few milliseconds.

Development

make test     # unit + end-to-end (real git repos, hooks, bare remote)
make bench
make validate # check the example config
go generate ./internal/wordy   # retrain the bigram model (default corpus: CPython stdlib)

Test secrets are generated at runtime, so this repository contains no realistic tokens that would trip push protection on GitHub or Azure DevOps.

Contributing

Issues and pull requests are welcome. False-positive and missed-secret reports are especially useful, and so are aggregate results from your own code (secretscan dir . -f stats, which contains no paths or values) and challenges to the suppression logic. The latest security audit of triage, --fix and --verify is in docs/security-audit.md. Changes are listed in CHANGELOG.md; stability guarantees are in docs/compatibility.md. See CONTRIBUTING.md, and report vulnerabilities privately per SECURITY.md.

License

Apache License 2.0. See LICENSE and NOTICE for third-party acknowledgements (several rule patterns are modelled on gitleaks, MIT).

Directories

Path Synopsis
bench
fingerprint command
Command fingerprint prints a hash of secretscan's detection code: the non-test Go files of the detection packages, parsed and printed without comments, so a comment or formatting change does not count as a change in detection while any change to code, rules or regexes does.
Command fingerprint prints a hash of secretscan's detection code: the non-test Go files of the detection packages, parsed and printed without comments, so a comment or formatting change does not count as a change in detection while any change to code, rules or regexes does.
cmd
secretscan command
Command secretscan finds hard-coded secrets in files, git history, staged changes, outgoing pushes and diffs.
Command secretscan finds hard-coded secrets in files, git history, staged changes, outgoing pushes and diffs.
internal
config
Package config defines the scanner configuration schema (a superset of the gitleaks v8 TOML format), loads and merges configs, and compiles rules.
Package config defines the scanner configuration schema (a superset of the gitleaks v8 TOML format), loads and merges configs, and compiles rules.
detect
Package detect runs compiled rules over text fragments and produces findings.
Package detect runs compiled rules over text fragments and produces findings.
remediate
Package remediate turns findings into actionable guidance: how urgent it is, how to rotate the credential, how to stop needing it at all (keyless / workload identity), and how to load it efficiently in the file's language.
Package remediate turns findings into actionable guidance: how urgent it is, how to rotate the credential, how to stop needing it at all (keyless / workload identity), and how to load it efficiently in the file's language.
report
Package report renders findings as text, JSON, SARIF, CSV or JUnit XML.
Package report renders findings as text, JSON, SARIF, CSV or JUnit XML.
sources
Package sources turns files, git history and diffs into fragments.
Package sources turns files, git history and diffs into fragments.
toml
Package toml implements a dependency-free parser for the subset of TOML v1.0 used by scanner configuration files (including gitleaks-style configs): tables, arrays of tables, dotted and quoted keys, all four string forms, integers, floats, booleans, nested/multi-line arrays and inline tables.
Package toml implements a dependency-free parser for the subset of TOML v1.0 used by scanner configuration files (including gitleaks-style configs): tables, arrays of tables, dotted and quoted keys, all four string forms, integers, floats, booleans, nested/multi-line arrays and inline tables.
triage
Package triage is a precision layer for other scanners: it re-scores findings from gitleaks, trufflehog or any SARIF producer using secretscan's context analysis and evidence model.
Package triage is a precision layer for other scanners: it re-scores findings from gitleaks, trufflehog or any SARIF producer using secretscan's context analysis and evidence model.
verify
Package verify optionally checks whether detected secrets are live by calling the issuing provider's read-only identity endpoint.
Package verify optionally checks whether detected secrets are live by calling the issuing provider's read-only identity endpoint.
wordy
Package wordy scores how much a string reads like natural-language or programming words, using a character-bigram model.
Package wordy scores how much a string reads like natural-language or programming words, using a character-bigram model.
wordy/gen command
Command gen trains the character-bigram table used by package wordy from the words found in a corpus of text/source files and writes table.go.
Command gen trains the character-bigram table used by package wordy from the words found in a corpus of text/source files and writes table.go.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL