Repository navigation
Changelog
All notable changes are listed here. The project follows
Semantic Versioning; see docs/compatibility.md
for what counts as a breaking change.
Unreleased
- Release verification is a reusable workflow (
verify-release.yml):
release.ymlcalls it after publishing, and it can be run by hand for
any published tag. Its Windows smoke test failed on v1.1.0 although the
binary behaved correctly (pwsh ends a step with the last native exit
code, here the expected 1 from blocking a secret); fixed, and the same
test now runs in CI on every push. - The CI templates (
ci/github-actions.yml,ci/azure-pipelines.yml)
publish SARIF, so they now passSECRETSCAN_HASH_KEYfrom the CI secret
store: published fingerprints are keyed once the secret is defined (plain
SHA-256, as before, until then; Azure warns). - A hash key that looks like an unexpanded CI variable (
$(...),
${{ ... }},${...}) is refused: an undefined variable's literal text
is public and must not become the key. - README: fingerprints are documented as sensitive metadata; reports
without a key should not be published where people who may not see the
secrets can read them.
1.1.0 - 2026-10-17
The first release with published binaries. The v1.0.0 GitHub release was
created by hand before the release workflow ran, so it has no binaries,
checksums or signatures (go install ...@v1.0.0 works); install v1.1.0
instead. Detection is unchanged from 1.0.0: the detection-code fingerprint
the benchmark was measured on is identical.
Fixes
triagechose a trufflehog finding's source location in Go map order
when itsSourceMetadatanamed several (for exampleFilesystemand
Git), so it could check a different file or commit on each run. Entries
are now read in a fixed order and used only when they agree, or when
exactly one matches the report'sSourceName; otherwise the finding stays
uncorroborated, its evidence lists the conflicting locations, and no
source is read for it.- Output files are never written through a link: reports, fix patches,
hooks and--fixrewrites go to a new file in the destination directory
that is renamed into place. A symlink at a report or patch path is refused
(terminal and pipe links such as/dev/stdoutstill work), a symlinked
hook is replaced by a regular file,--fixskips symlinked sources, and a
failed report write leaves no partial file. triage --max-report-megabytes N(default 256): a larger report, from a
file or stdin, is rejected before parsing with exit 2.
Added
- Keyed secret fingerprints: with
SECRETSCAN_HASH_KEY(or
--hash-key-file, at least 16 bytes), every fingerprint written to a
report (JSON and CSVSecretHash, SARIFpartialFingerprints) is
hmac-sha256:<hex>, which cannot be tested against guesses without the
key. Keyed ignore entries and keyed baselines match with the same key;
plainsha256:entries keep working. Without a key reports still carry
the plain SHA-256, now documented as guessable for weak secrets. - Tests that no raw secret (a weak password, a provider token, a generic
key, URL credentials, a private key) appears in any report format, the
suppression ledger, the summary, report files or triage output.
Benchmark integrity and confidence semantics
- Every published benchmark figure (README, docs, CHANGELOG, release notes,
install-hook) is generated frombench/results/latest.json, and CI
checks each one against the row it comes from in the dated results file.
It also rejects any percentage published outside a generated passage, and
any balanced figure that does not name itstestFixturessetting. This
fixed stale figures: trufflehog in the README (an earlier full-dataset
run), detect-secrets' leaky-repo extra lines, two CHANGELOG lines, and a
triage figure that had no source row. bench/breakdown.py: precision by confidence, path, context, detector
family and policy, from the holdout report, with detection and blocking
recall separately. Confidence is documented in the CLI help, the SARIF
output (tool.driver.properties.confidenceScale) and docs/precision.md as
a qualitative evidence level, not a probability.- Every provider-format rule (86) is tested at test, fixture, example and
doc paths,.env.exampleand compose files: none is downgraded or stops
blocking under balanced (TestNoProviderFormatDowngradedByLocation). - docs/benchmark-policy.md:
labels used as given, a register of label disagreements (AWS WAF
ChangeToken) reported only as a secondary figure, and a protocol for a
second independent benchmark. The detection-code fingerprint is recorded
with the figures, and a release fails if they were measured on different
detection code.bench/score_labeled.pyscores any line-labeled corpus.
Release
- The release workflow publishes into a release that already exists (one
created by hand on GitHub) instead of failing: it uploads the binaries,
checksums and signature and sets the notes. CONTRIBUTING.md documents
releasing: push the tag only.
1.0.0 - 2026-10-17
First public release: 136 rules, gitleaks-compatible config and reports,
staged/push/history/diff scanning, git hooks, placeholder and local-credential
recognition, noise filters with a suppression ledger, confidence levels and
strict/balanced policies, triage for other scanners' reports, live
verification, remediation guidance and explain. Benchmark results:
bench/results/2026-10-17.md
(earlier rounds: 2026-10-15, 2026-10-16).
Placeholders, local credentials and new detectors
Designed on CredData's dev half, measured once on its test half: precision
83.7% -> 91.5%, recall 48.0% -> 59.2% on repositories never used for design.
- Placeholder credentials (
some_password,s3krit-password,admin123,
postgres:postgres@) are dropped with a ledger reason
(placeholder-vocabulary,echoes-key-name,user-equals-password);
so are digests under hash keys, .NETPublicKeyTokens, translated UI
strings, algorithm names, crypto test-vector key names and patterned hex.
Word+number passwords other than one-word defaults are still reported. - New
Contextfield (local/example): credentials for localhost,
private networks,*.localand compose service hosts, and values in
docker-compose files,.env.exampleand.env.test, are reported at low
confidence and listed for review underbalanced(strict still blocks).
URL credentials to local hosts were previously allowlisted silently. - New detectors: HTTP
Authorization: Basic(only when it decodes to
user:password) andBearerheaders, SQLCREATE/ALTER USER ... PASSWORD
andIDENTIFIED BY,NAME_PASSWORD=in compose files,docker -e, ENV and
inline env, command-line passwords (curl -u,sshpass -p,mysql -p,
opensslpass:), password calls (setPassword,SetBasicAuth,
NetworkCredential) and token setters (SetAuthToken). - UUIDs under credential-named keys are reported at low confidence; UUIDs
under request, correlation, paging and concurrency keys (including AWS WAF
ChangeToken, which grants no access) are not. password-assignmentmatches indexers (["password"] =), comparisons,
wrapper calls and 4-5 character values; UI labels under password keys are
filtered.- Rules can declare content validators (
basic-auth,opaque-token); a
value that fails validation never becomes a finding.
Fixes from an external code review
triageenforces its 8 MiB source limit while reading: working-tree
files are size-checked before reading and read through a capped reader;
history blobs are size-checked withgit cat-file -sand streamed with a
cap, so a huge file named in a report can no longer exhaust memory.--verifychecks each secret once per run even under concurrency
(in-flight requests are shared), and only a provider's documented success
shape verifies (loginfor GitHub,object: balancefor Stripe,
data.viewer.idfor Linear, ...): JSON error objects, empty documents and
malformed bodies are unverified. 403 is no longer treated as verified.
GitHub App installation tokens are checked against
/installation/repositories.- README, docs and the install-hook message take their benchmark figures
frombench/results/latest.json(bench/render_docs.py); CI fails if a
published figure is stale.
Fixes from an adversarial recall and security review
- One-word default passwords plus digits (
admin@123,Welcome@2024,
P@ssw0rd2023,Password1) under password keys are reported at low
confidence outside test, example and doc files instead of being dropped
as placeholders (still dropped there, ledger reason
default-password-in-test-or-doc). - Local context no longer applies to Kubernetes service DNS
(*.svc.cluster.local), other multi-label.localnames
(sql01.corp.local),.env.local/.env.*.local, or any path with a
prod/production/prd/livesegment. redis://:password@host(empty user) is reported byurl-credentials.- Placeholder phrases must be the whole value:
mysecretkey_8f2kqand
yourkey_<random>are reported. - Unquoted values in
.env.*.*,.envrc, Makefiles, systemd units and
.txtfiles are literals: random base64 there was dropped as
operator-in-codeabout 40% of the time. Base64 starting with/or+
is matched bygeneric-api-key. - Digit-free Bearer tokens (long, mixed case, high entropy) are reported;
Authorization: <uuid>counts as a credential-named key. --verify: only a JSON answer verifies (a proxy's HTML block or caution
page no longer marks every secret live), and a 401 for GitHub/GitLab
tokens isunverified, notinvalid, because self-managed instances
issue the same formats.triage -f sarifkeeps the source tool, triage status and commit, and a
filtered value explains a reported finding only when it covers it.--fixnever rewrites a literal inside a comment, multi-line string or
heredoc, Python bytes literals, or constant positions (PHP property and
parameter defaults, C# case labels and optional parameters, Java
constants used incase/annotations, TypeScript enums); Ruby, PHP and
shell results are syntax-checked when the interpreter is installed.- Inputs skipped for
--max-target-megabytes(files, stdin tail, diff
additions) are reported in the summary instead of passing silently.
Changes since the first public commit, made in response to an external
review (benchmark rigor, default policy, filter safety, triage and --fix
safety, release hygiene):
Detection
- Fixed: unquoted hex values (
Key = acc993b3…) were classed as code
identifiers and dropped, in scans and triage. - New rules for formats an independent benchmark (leaky-repo) showed were
missed: unquoted passwords in env/INI/properties/YAML (password-in-config),
WordPress/PHPdefine()secrets, DjangoSECRET_KEY, LaravelAPP_KEY,
XML password attributes and<Pass>elements,.netrcpasswords, AWS key
IDs in SigV4 credential scopes (Credential=AKIA…/20260101/…), and
shadow,proftpdpasswd, Railsconfig/master.keyand Firefox
logins.jsonas sensitive files.passis now a password key name. - Weak dictionary passwords (
admin123) are reported at reduced confidence
instead of being dropped; exact placeholder words are still filtered. - Fixed:
SECRET_KEY=…was suppressed by the secret-reference name allowlist. - New noise filters, each with keep/reject/borderline tests: word tokens,
values that repeat their own key name, string concatenation, e-mail
addresses, and signature/auth scheme names (AWS4-HMAC-SHA256).
Policy and hooks
- Git hooks (installed and pre-commit framework) default to the balanced
policy; scans and CI still default to strict.install-hookprints the
trade-off, and hooks explain every blocked finding. - Balanced now blocks high and medium confidence (before: high only) and
lists weak evidence for review. - Generic values in test/example files block under balanced by default
(testFixtures = "block");testFixtures = "review"in.secretscan.toml
or--test-fixtures reviewlists them for review instead. Provider formats
in test files always block. The measured blocking recall and precision of
each setting are in the README and docs/precision.md. - Triage's uncorroborated findings are low confidence (review under
balanced, blocking under strict), as documented.
Triage
- Conservative mode (default): shape-only filters are not applied to a
value under a credential-named key;--mode standardapplies every filter.
Regression tests cover each missed class (UUID tokens, hex values,
pagination tokens, base64 underkey =, getenv defaults) and the false
positives that must stay filtered. - Memory is bounded: findings are processed grouped by file with one file
held at a time (8,350 gitleaks findings over 1 GB: 13 s, 203 MB). - Source is read only inside
--root; absolute paths,../and escaping
symlinks are rejected. History findings are read at their commit with no
working-tree fallback; unavailable or changed source stays uncorroborated. - Reads reports from stdin (
-), matches partially redacted values
(gitleaks--redact=N), warns about unredacted or group/world-readable
reports, and--delete-inputremoves the report after reading.
Remediation
--fixapplies edits only after confirmation or--yes;--fix-patchis
the documented default. Go/Python/JavaScript edits are syntax-checked and
skipped if they would not parse; literals nested in other strings are never
rewritten; output always reminds you to rotate.
Security audit fixes (docs/security-audit.md)
- Triage read the wrong file at a commit when
--rootwas a subdirectory. - Triage symlink containment now also covers files that don't exist yet.
- Reports and
--fix-patchfiles are forced to 0600 even when overwriting. --verifyno longer follows redirects (custom auth headers could be
forwarded to the redirect target).--fixreplacements fail fast naming the variable (JS/TS, Java, C#) and
skip literals that must stay compile-time constants.
Distribution
install.ps1for Windows (checksum and optional signature verification);
installers are tested on Linux, macOS and Windows on every push, and the
post-release install test covers Windows.- Releases sign
SHA256SUMSwith Sigstore (keyless, bound to the release
workflow) and attach GitHub build-provenance attestations;install.sh
verifies the signature when cosign is installed
(SECRETSCAN_REQUIRE_SIGNATURE=1makes it mandatory), and the post-release
install test requires it. install.sh: one-command install of the release binary with SHA-256
verification; refuses to install on a mismatch.- The release workflow installs every published release from scratch on
Linux and macOS (install script,go install @tag, pre-commit hook) and
checks it blocks a generated token. secretscan versionreports the module version forgo installbuilds.
Reporting and benchmarks
bench/reproduce.shand thebenchmarkworkflow reproduce every published
number with pinned tools and datasets, locally or in a fork's Actions.- CredData is split into dev/test halves by repository (pre-registered);
CredData-informed changes are designed on dev and reported on test. - CI reports failing tests as annotations.
-f stats: anonymized counts (rule, confidence, file type) for sharing.bench/creddata.py: independent precision challenge on Samsung CredData
(337 real repositories), including gitleaks + triage; first-run results
published with every false-positive and miss class.- detect-secrets runs in all benchmarks with
--no-verify(by default it
sends matched values to provider APIs). bench/leaky_repo.py(independent ground truth),bench/adjudicate.pywith
hand labels for every corpus finding, pinned corpus versions, per-alphabet
false-negative measurement, and dated results inbench/results/.- README claims now lead with independent data and list misses and limits.