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.