Three defects the #12506 hardening left behind, each red on the base tip:
- SAFE_PUBLIC_ERROR_IDENTIFIERS listed 250 codes while the code emits 49 more
(huggingchat_generation_error, the uc_* family, zai_stream_error,
provider_circuit_open, model_lockout, heap_pressure, unsupported_media_type,
upgrade_required, …). Every one of them was silently rewritten to the generic
status-derived code, so clients lost the specific reason. Each added code was
verified against its emitting call site.
- The path redactor swallowed the credential marker an earlier pass had already
written: 'TLS request failed at /srv/…/client.ts:44:9 access_token=[REDACTED]'
collapsed to a bare '<path>', hiding which credential leaked. The span now
ends at a redaction marker.
- Labeled credential assignments (api_key=…, access_token=…) are projected
before the path tokenizer for the same reason raw URI credentials already
were, otherwise the path span absorbs the assignment before it can be marked.
The tunnel characterization test now states, per leak shape, what the shared
sanitizer covers: the four path-shaped leaks are covered upstream now, the
tailscale auth key is not — which is exactly why publicSafeTunnelError stays.
The public-body assertions are untouched.
#12506's unquoted-path ambiguity resolution failed closed across the
*entire remainder* of a message once it saw an unshielded route-looking
span (no HTTP-method context) followed by ambiguous prose and then a
second, legitimately route-shielded reference (e.g. "POST /v1/foo").
resolveEndpoint() only distinguished "ambiguous" from "resolved", so
hitting that second absolute-looking span always re-triggered the
fail-closed value.length branch via hasFilesystemEvidence, deleting
the shielded route and everything after it instead of just redacting
the first, unshielded span.
This broke POST /v1/chat/completions' image-only-model guard (#6457):
its 400 body's second sentence ("Use POST /v1/images/generations
instead.") was being erased by sanitizeErrorMessage(), so
tests/unit/chat-rejects-image-only-model.test.ts failed release-green
on release/v3.8.51.
resolveEndpoint() now takes an ignoreAmbiguity flag; when the next
absolute-looking span is itself route-context-shielded per the
existing hasRouteContextBefore() check, its mere presence no longer
forces a value.length swallow. Added a direct regression test in
tests/unit/error-message-sanitization.test.ts pinning the exact
pattern down at the sanitizeErrorMessage level.
Validado sobre o tip de `release/v3.8.51` depois de reconciliar com o #12620, que entrou primeiro nesta mesma sessão e ataca a mesma classe de problema por outra arquitetura.
**A colisão e como foi resolvida.** O #12620 consertou o GHSA-qv45-56jc-4wmj adicionando `RAW_CREDENTIAL_PATTERNS` a `error.ts` e importando-os em `upstreamErrorPassthrough.ts`. Este PR resolve o mesmo problema quebrando `error.ts` em `errorSanitization.ts` + `errorPathRedaction.ts`. Mantive a divisão em módulos deste PR, porque ao comparar os dois vocabulários o dele já era mais amplo: o `STRONG_CREDENTIAL_TOKEN` daqui cobre `sk-`/`sk_` **com lookbehind e uma variante para a forma embutida** (que pega `sk-proj-…`), mais Slack `xox-`, AWS `AKIA`/`ASIA`, `github_pat_`/`ghp_`/`glpat-` e JWT de três segmentos.
A única forma que o #12620 carregava e este conjunto não tinha era a chave do Google (`AIza…`) — adicionada aqui, com o mesmo quantificador limitado que os irmãos usam (AGENTS.md → PII §1, já que isso roda sobre corpos upstream não confiáveis).
**A verificação não foi por inspeção.** Rodei as suítes do próprio #12620 contra esta estrutura: **48/48** em `error-sanitizer-sk-key-qv45`, `bifrost-relay-response-leak-9m72`, `search-baseurl-client-override-3f8g` e `search-baseurl-ssrf-guard` — incluindo a asserção anti-drift daquela suíte, que é o oráculo certo aqui: *para todo corpo que a camada de passthrough recusa como vazante, o sanitizador de fallback não pode devolvê-lo intacto*. Ela passa, então a propriedade de segurança dos três GHSAs sobrevive à troca de arquitetura.
Os 21 arquivos de teste deste PR: **259/259**. `typecheck:core` limpo.