Secret detection

How precise is secret detection? We measured it

Summary

0.0% false-positive rate over a labeled corpus of benign, credential-shaped values - measured, not asserted.
100% of the 111 credential formats in our test corpus are detected - with 10 more published openly as known gaps, not hidden.

The hard part of secret detection is not spotting a key. It is staying silent on the forty things shaped exactly like one - the commit hash, the request ID, the README's base64 image, the quickstart's example key. Fire on those and the alerts get muted, and a muted scanner never sees the real leak - so the number we put to the test is the false-positive rate.

  • 0 / 40 benign decoys flagged
  • 111 formats across 73 services detected
  • 10 gaps published, not hidden

A forty-character hex string is a git commit SHA, a content digest, a request ID, or a live AWS secret key - only context tells them apart. A scanner that guesses wrong in the noisy direction buries you in false alarms on your own commit hashes and example keys until you stop reading them. So we built a labeled corpus of the benign values that look like credentials, measured the false-positive rate, and - unusually - published the list of formats we do not yet detect.

The result: over 40 benign, credential-shaped values drawn from the documented false-positive families - content hashes, identifiers, encoded blobs, example keys, code symbols - Skarn raises 0 false positives - a measured 0.0% false-positive rate. On the same corpus, 111 current credential formats across 73 services are all detected, and the 10 formats we do not yet catch are published as an explicit gap list.

Every credential format is grounded in its detector definition or provider spec, and nearly every benign value in the spec or the documented scanner false-positive that establishes it as a non-secret; the few that are not are regression guards for false positives this benchmark itself caught and we then fixed. Every token in the corpus is a format-valid synthetic value a detector treats exactly like a live key, and none is live.

This is the secret-detection sibling of our attack-chain detection precision measurement - the same corpus method and build gate, applied to a different surface. Measured 2026-07-12.

The numbers

The false-positive count is the headline; recall on the tested formats is 100% (111 of 111), held against regression, and the gap list is published in full. On a tool you run against your own work every day, a quiet, honest scanner beats a loud, overreaching one.

0.0%
false-positive rate on benign credential-shaped values
0
false positives over 40 non-secret decoys
111
current credential formats detected - 100% recall on the tested set
73
services covered
10
current formats published as known gaps
5
benign false-positive families covered
161
labeled secret-track fixtures, 158 with a cited public source
189
synthetic secrets exercised, none ever leaked in output

Why a false-positive number is the right number

A precision claim is only worth as much as the corpus behind it. The credible bar on the secrets surface was set by published benchmarks with a named dataset, a stated method, and exact precision and recall figures. We follow that bar: a defined corpus, a method published in full, and a number that is the output of one command - reproducible and falsifiable, not a marketing figure.

The false-positive rate is the number that matters for a tool you run against your own work every day. The hard part of secret detection is not matching AKIA...; it is staying silent on the forty other things that look like AKIA.... A 40-character hex string is a git SHA, a content checksum, a request ID, or an AWS secret key, depending only on context. Skarn's value is that it tells them apart.

Skarn is not the only scanner that reads this surface locally. GitGuardian's Developer Endpoint Protection, announced 2026-06-16, scans AI tool directories and log files on the endpoint, and states that only structured metadata leaves it - so the honest axis of comparison is not local versus cloud. It is what gets correlated. The documented scope of that product is a credential inventory, honeytokens, and forwarding to a SIEM; as of that announcement it documents no attack-chain, behavioral, or MITRE ATLAS correlation across the session. A credential inventory answers "is there a key here". Skarn measures precision on that question and on the harder one next to it - did this session behave like an attack - and publishes the false-positive rate for each, with the command that produces it.

How the corpus is built - real scenarios, synthetic secrets

Two rules govern every fixture.

Real scenarios
Every credential format is grounded in its authoritative source - an official provider doc, a published detector definition, or a format spec. Almost every benign value is grounded in the spec or the documented scanner false-positive that establishes it as a non-secret: the UUID and JWT RFCs, the ULID, nanoid, and KSUID specifications, the W3C trace-context header, the Subresource Integrity spec, npm and Go module checksum formats, and real scanner false-positive reports. 158 of the 161 secret-track fixtures carry that citation in their label metadata. The other 3 are grounded in something narrower but no weaker: they are regression guards for false positives this benchmark caught in Skarn itself, which we then fixed.
Synthetic secrets
Not one real credential appears anywhere in the corpus. Every token is synthetic, format-valid, and non-live - it matches a detector's shape, and its checksum where one applies, such as a GitHub token's CRC, but unlocks nothing. The two values that look most real are deliberately the universal PUBLIC documentation constants - the example JWTs printed in the spec and on jwt.io, and a payment provider's shared sample test key - included as decoys precisely to prove the scanner stays quiet on documentation. An automated invariant extracts every secret-shaped token from the corpus and proves none of them ever appears, unredacted, in any output Skarn produces; 189 synthetic secrets are pushed through that gate and it passes.

The benign families we test against

These are the values that fool a naive secret scanner. Skarn stays silent on all 40, across 5 families.

  • Identifiers (12) - UUIDs, ULIDs, nanoids, KSUIDs, distributed-tracing IDs, error-tracker event IDs, and the object and request IDs that payment and cloud APIs return on every call. Deliberately public.
  • Content hashes (8) - git SHA-1 and SHA-256 object names, Docker image digests, Cargo and Go module checksums, Subresource Integrity attributes, npm lockfile integrity, and the value of a field whose own name says it holds a digest. Fingerprints of public artifacts, not keys.
  • Example and placeholder keys (8) - documentation sample keys, publishable (client-side) keys, masked scanner output, .env.example placeholders, angle-bracket template placeholders, and a licence token quoted in prose or carrying a checksum that does not validate.
  • Code and config identifiers (7) - dotted symbol references, .NET assembly public-key tokens, environment-variable NAMES referenced in deploy scripts, public-key config fields, a public analytics beacon token embedded in page HTML, and version pins that hold a commit SHA.
  • Encoded data (5) - inline base64 images and fonts, sealed and encrypted ciphertext blobs, and the canonical example JWTs from specs and docs.

The corpus is built to be hard, not easy. It includes the boundary cases - a publishable key versus a secret key, a variable's NAME versus its value, a 40-hex commit SHA versus a 40-hex secret - precisely the places a sloppy scanner turns a config file into a false alarm. Each one trips a different suppression gate and stays silent, and each is a regression guard that fails the build if it ever starts firing.

The formats we detect

The same corpus proves the scanner did not go quiet to win on precision. 111 positives confirm current-format detection across 73 services: OpenAI (including the project, service-account, and admin key shapes), Anthropic, Groq, DeepSeek, xAI, Perplexity, Replicate, Hugging Face, Cohere, Mistral, Together, Fireworks, NVIDIA, OpenRouter, Voyage AI, Jina, Langfuse, Mem0, LlamaCloud, Cursor, Composio, RunPod, MiniMax, Friendli, Zhipu, Dify, Baseten, Ollama, and Deepgram; AWS (including both Bedrock key lifetimes), Azure, DigitalOcean, Fly.io, and Render; GitHub and GitLab in all their token variants, Bitbucket, npm, PyPI, RubyGems, Docker Hub, Buildkite, CircleCI, Vercel, Netlify, Jenkins, AppVeyor, and TeamCity; Stripe, Twilio, SendGrid, Mailgun, Slack, Discord, Microsoft Teams, Telegram, Postmark, Square, and PayPal; Supabase, Neon, MongoDB, Redis, Vault, Doppler, Dynatrace, Grafana, 1Password, connection strings, JWTs, SSH and PEM private keys, and Skarn's own licence tokens. Each positive is generated to match the live rule, so a miss is a real recall regression that fails the gate.

What we do not yet catch - and why we tell you

10 current formats have no dedicated rule yet: a handful of newer AI providers, including one whose newest key shape is still rolling out; a cloud storage shared-access-signature token; several unprefixed cloud and VPS provider tokens whose shape is too generic to match safely; a package-registry token; a managed-Redis token; and one managed-database password prefix. Some are still caught by generic high-entropy detection, with less precision and no service attribution. We track them as a published gap list rather than add aggressive rules that would regress precision elsewhere - because on a tool you run daily, a quiet, honest scanner beats a loud, overreaching one. The list shrinks over time; the honesty does not. A scanner's candour about what it misses is part of its trustworthiness, so we publish the gap register next to the precision number rather than behind it.

Reproduce it

The number is not a slide. It is the output of one command - so here is the command, the corpus it runs over, and the scorecard it prints.

The benchmark and the labeled corpus ship with the tool. One command scores the whole corpus and gates the build: it fails the moment one new benign value trips a detector, one current-format positive stops firing, or a number published on this page stops matching the corpus. A second command proves the corpus holds no live credential. skarn taxonomies prints the same figures from the binary itself.

zig build -Dtest-mode benchmark zig build -Dtest-mode redaction-check

What the first command scores, on the secret track: 161 labeled fixtures, in the four label kinds the corpus uses.

  • 111 positives - current credential formats across 73 services, each a format-valid synthetic token generated to match the live rule. Each must fire its expected rule; a miss is a recall regression and fails the build.
  • 40 decoys - benign, credential-shaped values across the 5 false-positive families: 12 identifiers, 8 content hashes, 8 example and placeholder keys, 7 code and config identifiers, 5 encoded data. Each must produce no finding at all; one finding is a false positive and fails the build.
  • 10 known gaps - current formats with no dedicated rule yet. Tracked, published on this page, and deliberately gate-neutral: they are the register, not a failure.
  • 0 known bugs - the fourth kind, for a rule that exists but is defective. That register is currently empty.

158 of the 161 carry a cited public source in their label metadata; the other 3 are regression guards for false positives this benchmark caught in Skarn itself. The wider corpus these sit in holds 261 fixtures across the behavioral, supply-chain, lockfile, and insecure-code tracks as well.

Every token in it is synthetic and non-live, and that is not an assurance but a second gate: zig build -Dtest-mode redaction-check extracts all 189 secret-shaped tokens from the corpus and asserts none of them appears unredacted in any output Skarn produces - every format, the baseline file, the audit log, the export, the local scan API, and the guard verdicts.

The scorecard the gate prints:

FALSE-POSITIVE RATE (benign fixtures that fired any rule) all decoys: 0/108 (0.0%) behavioral decoys: 0/59 (0.0%) <-- attack-pattern / chain precision (L1) secret decoys: 0/40 (0.0%) <-- leaked-credential precision secret recall: 111/111 (100.0%) current-format coverage (held against regression) GATE: PASS

The raw corpus is not published as a download, and the reason is the same one that governs the rest of this page. The fixture bodies, the generator, and the scoring rule map together spell out the exact shape each detector keys on and the precise boundary of every suppression - a tuning guide for evading the scanner. What we miss is not the withheld part: the gap register is published in full, above. What is withheld is the map of how each rule decides. The manifest, the command, and the expected output are the falsifiable part - they state exactly what is measured and what result would contradict it.

Honest limitations

Stated plainly so the number survives scrutiny rather than inviting it.

It is precision, not prevalence
The number is a false-positive measurement over a curated corpus, not a field study. It says Skarn does not false-fire on these 40 real benign values - not that the false-positive rate in the wild is exactly zero. A corpus sampled from real traffic would strengthen it further; this one is built adversarially from the documented false-positive families, which is the right first bar.
Recall is 100% on the tested set, not a completeness claim
All 111 tested formats fire - 100% recall on the tested set, held against regression - but that is coverage of the formats we test, not a claim of completeness over every credential format in existence. The 10-entry gap register is the explicit incompleteness statement; several of those gaps are still caught generically, so the true miss set is smaller than 10 and the register is a conservative upper bound. The false-positive rate stays the number we lead with.
Test keys are still flagged on purpose
A test-mode key and the canonical AWS docs example key are intentionally still flagged as secrets; only the universally-shared sample constants - a payment provider's shared sample test key and the canonical example JWTs - are suppressed. A test-mode key is a real, if low-blast-radius, secret, so environment classification down-ranks it rather than hiding it.

References and sources

The credential formats are grounded in scanner detector definitions and official specs; the benign families cite the same specs and the documented scanner false-positives that establish each value as a non-secret. Each source was fetched and verified.

Identifier and checksum specs grounding the benign families, not linked individually above: the ULID, nanoid, and KSUID specifications; the Go module go.sum checksum format and the npm package-lock integrity field; and the .NET strong-name public-key-token documentation. Provider key formats for the 73 covered services are grounded in the gitleaks and trufflehog detector definitions above and the respective official provider docs. Sources re-verified 2026-07-12; corpus counts re-measured against the live gate the same day.

Read the full report

The full write-up is a superset of this page: an executive summary, an extended public-safe methodology, the complete corpus breakdown with every cited source, a reproducibility appendix, and a checklist for assessing your own exposure. Free to read offline or forward.

Download the full report (PDF)

See your own sessions

The corpus shows the shape of the problem. A scoped assessment shows yours - run locally on a developer's machine, nothing uploaded, and you keep the redacted report.

Runs on macOS, Windows, and Linux, on both Intel and ARM.