Skip to content

secretsscan: tighten Docker PAT, add new vendor patterns, cap quantifiers#2697

Merged
dgageot merged 3 commits intodocker:mainfrom
dgageot:expand-secret-redaction-catalogue
May 7, 2026
Merged

secretsscan: tighten Docker PAT, add new vendor patterns, cap quantifiers#2697
dgageot merged 3 commits intodocker:mainfrom
dgageot:expand-secret-redaction-catalogue

Conversation

@dgageot
Copy link
Copy Markdown
Member

@dgageot dgageot commented May 7, 2026

Expands the secret-redaction catalogue and fixes a class of bugs in the rules added along the way.

Docker PAT/OAT

  • Tightened the existing dckr_pat_ rule: dropped (?i) and the hyphen from the body class — Docker's spec is strictly alphanumeric.
  • Added a new rule for Docker Hub Organization Access Tokens (dckr_oat_).

New vendor-prefixed patterns

Confirmed against gitleaks default rules and vendor docs:

Vendor Prefix
1Password service account ops_eyJ
OpenRouter sk-or-v1-
SonarQube/Cloud squ_ / sqp_ / sqa_
Pinecone pckey_
Supabase service-role sb_secret_
Tailscale auth + API tokens tskey-auth- / tskey-api-
Vercel personal/CLI/integration vcp_ / vck_ / vci_
Razorpay rzp_test_ / rzp_live_
Adyen AQE
Plaid access token access-(sandbox|development|production)-
PostHog personal API phx_
Render API rnd_
Honeycomb ingest + config hcaik_ / hcaic_
Akamai EdgeGrid akab-
Adafruit IO aio_

Bug fix: capped greedy quantifiers

The first pass of new rules used unbounded {N,} quantifiers. With Go's RE2 those match greedily and silently absorb adjacent alphanumeric text into the redaction span — vcp_aaa…aaaTRAILINGTEXT would collapse to a single [REDACTED], dropping the trailing context. Same class of bug the existing TestRedactDoesNotSwallowAdjacentTextAfterSlackRotatingToken was added to prevent.

Every new rule now has an explicit upper bound (e.g. {20,80} for Vercel, {250,1000} for 1Password — RE2's per-quantifier ceiling), and there is a regression test (TestRedactDoesNotSwallowAdjacentTextAfterPrefixedTokens) that pushes each body past its cap and asserts the trailing text survives.

@dgageot dgageot requested a review from a team as a code owner May 7, 2026 14:37
Copy link
Copy Markdown

@docker-agent docker-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

2 findings (1 LIKELY · 1 CONFIRMED) in newly-added code.

Comment thread pkg/secretsscan/rules.go
// openrouter-api-key. OpenRouter (LLM router) keys carry
// the documented `sk-or-v1-` prefix followed by a 64-char
// lowercase-hex body.
expression: `sk-or-v1-[a-f0-9]{64}`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] OpenRouter key body pattern [a-f0-9]{64} is too narrow — likely causes false negatives

The comment says "64-char lowercase-hex body," but OpenRouter API keys in practice use a base64url-encoded body, not pure hex. Gitleaks' upstream rule for OpenRouter uses [a-zA-Z0-9]{64} (full alphanumeric) rather than [a-f0-9]{64}.

Any real key whose body contains g–z or A–Z would silently bypass redaction with the current pattern.

The test only repeats "a" 64 times — all within the [a-f] set — so it cannot detect this mismatch.

Suggested fix:

expression: `sk-or-v1-[a-zA-Z0-9]{64}`,

{"tailscale_auth_key", "tskey-auth-" + strings.Repeat("h", 12) + "-" + strings.Repeat("i", 90)},
{"tailscale_api_token", "tskey-api-" + strings.Repeat("j", 12) + "-" + strings.Repeat("k", 90)},
}
const suffix = " and the rest of the line"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Quantifier-cap test uses a space-prefixed suffix — the upper bound is never actually validated

TestRedactDoesNotSwallowAdjacentTextAfterPrefixedTokens is supposed to prove that an explicit upper bound (e.g. {20,80}) prevents the regex from swallowing adjacent alphanumeric text. But the suffix is " and the rest of the line" — it starts with a space.

Because every body character class in the tested rules is alphanumeric ([A-Za-z0-9], [A-Za-z0-9_-], etc.), a space already terminates the match even with an unbounded quantifier ({N,}). The test would pass identically if every {N,M} upper bound were replaced with an open-ended {N,}, so it gives no confidence that the caps are real.

To actually exercise the cap, the suffix must consist of characters that the body class would consume — e.g.:

const suffix = strings.Repeat("Z", 200) // alphanumeric, consumed by an unbounded rule

Then the assert that suffix survives would only pass when the quantifier ceiling genuinely blocks it.

@dgageot dgageot merged commit c5ba831 into docker:main May 7, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants