mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 02:42:24 +03:00
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.
47 lines
1.6 KiB
TypeScript
47 lines
1.6 KiB
TypeScript
import { buildErrorBody, sanitizeUpstreamDetails } from "./error.ts";
|
|
|
|
interface SanitizedUpstreamErrorResponseOptions {
|
|
status: number;
|
|
rawBody: string;
|
|
fallbackMessage: string;
|
|
headers?: Record<string, string>;
|
|
}
|
|
|
|
/**
|
|
* Preserve a provider's JSON error shape while applying the canonical recursive sanitizer.
|
|
* Providers sometimes label plain text as JSON; those bodies use OmniRoute's canonical error
|
|
* envelope so the advertised content type always matches the response bytes.
|
|
*/
|
|
export function buildSanitizedUpstreamErrorResponse({
|
|
status,
|
|
rawBody,
|
|
fallbackMessage,
|
|
headers,
|
|
}: SanitizedUpstreamErrorResponseOptions): Response {
|
|
const trimmedBody = rawBody.trim();
|
|
|
|
if (trimmedBody) {
|
|
try {
|
|
const parsedBody: unknown = JSON.parse(trimmedBody);
|
|
const serializedBody = JSON.stringify(sanitizeUpstreamDetails(parsedBody));
|
|
if (serializedBody !== undefined) {
|
|
return new Response(serializedBody, {
|
|
status,
|
|
headers: { ...headers, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
} catch {
|
|
// Upstreams commonly return text or HTML despite an application/json response header.
|
|
// Treat it as an opaque message and use the canonical JSON envelope below.
|
|
}
|
|
}
|
|
|
|
// Non-JSON is an opaque upstream body. Do not echo even sanitized fragments:
|
|
// provider HTML/plaintext can contain credentials or implementation details
|
|
// outside the patterns the canonical sanitizer knows about.
|
|
return new Response(JSON.stringify(buildErrorBody(status, fallbackMessage)), {
|
|
status,
|
|
headers: { ...headers, "Content-Type": "application/json" },
|
|
});
|
|
}
|