Writing regexes that catch API keys without false positives

The naive approach to finding API keys with regex goes like this: match something that looks random. [A-Za-z0-9]{32} catches the real key — and also catches UUIDs, hashes, build IDs, and half the identifiers in your codebase. The naive approach to avoiding false positives goes like this: match the exact known format, sk-[A-Za-z0-9]{20}, which catches nothing once the vendor changes the format. The hard part of writing secret regexes is that both failure modes are expensive: too many false positives and the scan gets ignored; too few and the leak is real.

Structure the pattern like the real thing

Real secrets have structure: a prefix that identifies the vendor and a random payload. Match that structure instead of “randomness”:

  • Anchor on the prefix. sk-[A-Za-z0-9]{20,40} beats a bare character class. The prefix is what makes the match specific — and it’s also what breaks when a vendor changes format, which is why prefix knowledge needs maintenance.
  • Respect separators. Keys often appear with delimiters (-, _, .). [A-Za-z0-9_-]{32} includes the delimiter so the match doesn’t split the key in half.
  • Use word boundaries, not line anchors. \b around the pattern prevents matching inside longer identifiers — you don’t want to flag the tail of my_sk_abc123. In RE2, \b and \d are available; lookahead and backreferences are not, so design accordingly.

Add entropy as a second opinion

Regex tells you the shape matches; entropy tells you the content is random. Together they are far stronger than either alone. A 40-character alphanumeric string that starts with sk- and scores high on Shannon entropy is a key; the same string reading aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa is not. The standard pipeline is: keyword pre-filter to skip cheap non-matches, regex to establish format, entropy with a threshold (3.5 is a common default) to confirm randomness. This also defuses the classic false positive: a UUID is 36 characters of hex and dashes, which looks like a key — but a rule anchored on a vendor prefix won’t touch it.

Practical patterns worth stealing

Some patterns that hold up in production scans:

# AWS access key
\bAKIA[0-9A-Z]{16}\b

# Generic prefixed secret key
\b(sk|pk|rk|tk)_[A-Za-z0-9]{20,60}\b

# Private key header
-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----

# Slack webhook
\bhttps://hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]+

Each one is anchored, delimited, and specific enough that a hash or a UUID won’t match. If you’re writing your own, extract the vendor’s documented format from their docs and build a fixture test — one real key, one UUID, one hash, one random English sentence — before trusting it.

Tuning beats perfecting

No rule set reaches zero false positives, so design the pipeline for triage, not purity. Keep per-rule controls: a log mode for noisy but plausible rules, a mask mode for high-confidence ones, and the ability to disable a rule without deleting it. Let rules reload without restarting the scanner. When a rule fires on something that isn’t a secret, that’s a signal — either the rule needs narrowing or the codebase has a naming collision worth knowing about.

One more layer is worth having: match known values directly. A pattern says “this looks like a secret”; a known-value match says “this is one of our secrets” — zero false positives by construction. Run both, and you get breadth from patterns and precision from the value list.

If you’re building your own scanning layer, the pipeline in trustless is a useful reference: 40 gitleaks-derived rules (MIT, attribution in the NOTICE file) behind a keyword pre-filter, RE2 regex, and Shannon entropy at a default threshold of 3.5, with per-rule log/mask modes, a custom rules_file, hot reload on SIGHUP, and an atomic config swap so a bad rule file can’t disable scanning. The same rule set feeds its DLP reverse proxy and its retroactive scrub commands.

Which false positive in your last secret scan did you fix by changing the rule — and which one did you fix by ignoring the scan?