Skip to content

fix(security): add Groq, xAI and OpenAI-compatible key shapes to the credential catalog - #13744

Merged
diegosouzapw merged 1 commit into
release/v3.8.51from
fix/sec-credential-catalog-r4q7
Sep 15, 2026
Merged

diegosouzapw merged 1 commit into
release/v3.8.51from
fix/sec-credential-catalog-r4q7

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Fixes the privately reported GHSA-r4q7-7f24-m29p.

CREDENTIAL_PATTERNS (open-sse/utils/credentialPatterns.ts) is the single catalog both the
opt-in credential-masker guardrail and the public error sanitizer iterate. It had no entry for
Groq or xAI, and only knew the exact 48-char OpenAI sk- form.

Measured on the release tip before this change

Shape Public sanitizer Guardrail
Groq gsk_ + 52 leak leak
xAI xai- + 80 leak leak
DeepSeek sk- + 32 hex redacted leak
sk- + 20 / 36 / 40 / 51 (not 48) redacted leak
Kimi/Moonshot sk- + 48 redacted redacted (coincidental exact-48 match)

Each shape was probed bare, inside a sentence and inside a JSON error body. The advisory says
both layers leak every sk- variant — that part is overstated: the public path already caught
all of them through STRONG_CREDENTIAL_TOKEN. For the sk- family the exposure was the guardrail.

Change

  • groq and xai entries after anthropic_alt.
  • A generic openai_compatible sk- fallback as the last entry. Order matters: both consumers
    replace as they iterate, so openai_proj / openai / anthropic* stamp their specific label
    first and the fallback only sees shapes nothing else claimed. Its lookbehind mirrors
    STRONG_CREDENTIAL_TOKEN, so words like risk-… do not match.
  • All three regexes are a fixed prefix plus one bounded character class — linear, no nested
    quantifiers.

Validation (TDD)

Check Result
New guardrail cases (per shape × 3 contexts, label ordering, negatives) — on the tip red
Same cases after the fix 41 / 41
Wider catalog / sanitizer suites 367 / 368 — see note
typecheck:core, check:open-sse-typecheck clean
eslint + prettier (pre-commit) clean

Note on the one red: sanitizers.property.test.ts → terminates on long adversarial input (ReDoS guard) hit 3034 ms while five validation runs were in parallel on a box at load average
57. Benchmarking sanitizeErrorMessage on the untouched tip and on this branch gave
indistinguishable times, and those times do not grow with input length (1 000 → 20 000 chars),
which is the opposite of what ReDoS looks like. The test input (aaa@bbb.com 111) also matches
none of the three new patterns.

Out of scope, left as follow-ups

  • tskey-auth-… (Tailscale) was never in the catalog.
  • The guardrail does not decode \uXXXX escapes the way the public path does.

⚠️ base-red inherited: #12732 — failures outside the catalog and sanitizer suites come from the tip.

…credential catalog

GHSA-r4q7-7f24-m29p. `CREDENTIAL_PATTERNS` (open-sse/utils/credentialPatterns.ts)
is the single catalog iterated in order by both the opt-in credential-masker
guardrail and the public error sanitizer. It had no entry for Groq (`gsk_`) or
xAI (`xai-`), and only knew the exact 48-char OpenAI `sk-` form.

Measured on the release tip before this change:

| Shape                        | public sanitizer | guardrail |
|------------------------------|------------------|-----------|
| Groq  gsk_ + 52              | LEAK             | LEAK      |
| xAI   xai- + 80              | LEAK             | LEAK      |
| DeepSeek sk- + 32 hex        | redacted         | LEAK      |
| sk- + 20/36/40/51 (not 48)   | redacted         | LEAK      |

The public path already caught every `sk-` shape through STRONG_CREDENTIAL_TOKEN,
so the advisory's "both layers" framing only holds for gsk_/xai-; for the sk-
family the exposure was the guardrail.

Adds `groq` and `xai` after `anthropic_alt`, and a generic `openai_compatible`
`sk-` fallback as the LAST entry. Ordering matters: both consumers replace as
they iterate, so `openai_proj`, `openai` and `anthropic*` stamp their specific
label first and the fallback only sees shapes nothing else claimed. The
lookbehind mirrors STRONG_CREDENTIAL_TOKEN so `risk-…`-style words do not match.
All three regexes are a fixed prefix plus one bounded character class — linear,
no nested quantifiers.

Tests are red-first: the new guardrail cases (bare / sentence / JSON-body
contexts per shape, plus label-ordering and negative cases) and the catalog
coverage array in error-sensitive-redaction both failed on the tip.

Follow-ups deliberately left out of scope: `tskey-auth-` (Tailscale) was never in
the catalog, and the guardrail does not decode `\uXXXX` escapes the way the
public path does.
@diegosouzapw
diegosouzapw merged commit e498c34 into release/v3.8.51 Sep 15, 2026
16 of 21 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…credential catalog (diegosouzapw#13744)

GHSA-r4q7-7f24-m29p. `CREDENTIAL_PATTERNS` (open-sse/utils/credentialPatterns.ts)
is the single catalog iterated in order by both the opt-in credential-masker
guardrail and the public error sanitizer. It had no entry for Groq (`gsk_`) or
xAI (`xai-`), and only knew the exact 48-char OpenAI `sk-` form.

Measured on the release tip before this change:

| Shape                        | public sanitizer | guardrail |
|------------------------------|------------------|-----------|
| Groq  gsk_ + 52              | LEAK             | LEAK      |
| xAI   xai- + 80              | LEAK             | LEAK      |
| DeepSeek sk- + 32 hex        | redacted         | LEAK      |
| sk- + 20/36/40/51 (not 48)   | redacted         | LEAK      |

The public path already caught every `sk-` shape through STRONG_CREDENTIAL_TOKEN,
so the advisory's "both layers" framing only holds for gsk_/xai-; for the sk-
family the exposure was the guardrail.

Adds `groq` and `xai` after `anthropic_alt`, and a generic `openai_compatible`
`sk-` fallback as the LAST entry. Ordering matters: both consumers replace as
they iterate, so `openai_proj`, `openai` and `anthropic*` stamp their specific
label first and the fallback only sees shapes nothing else claimed. The
lookbehind mirrors STRONG_CREDENTIAL_TOKEN so `risk-…`-style words do not match.
All three regexes are a fixed prefix plus one bounded character class — linear,
no nested quantifiers.

Tests are red-first: the new guardrail cases (bare / sentence / JSON-body
contexts per shape, plus label-ordering and negative cases) and the catalog
coverage array in error-sensitive-redaction both failed on the tip.

Follow-ups deliberately left out of scope: `tskey-auth-` (Tailscale) was never in
the catalog, and the guardrail does not decode `\uXXXX` escapes the way the
public path does.
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.

1 participant