mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 19:32:20 +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.
70 lines
3.2 KiB
TypeScript
70 lines
3.2 KiB
TypeScript
// tests/unit/chatcore-stream-error-result.test.ts
|
|
// Characterization of isSemaphoreCapacityError / createStreamingErrorResult /
|
|
// getUpstreamErrorIdentifier — streaming error-result helpers extracted from handleChatCore
|
|
// (chatCore god-file decomposition, #3501). Locks the semaphore code matching, the SSE error
|
|
// envelope shape (status, headers, `data: {...}\n\ndata: [DONE]\n\n` body, optional code/type), and
|
|
// the string-code extraction.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
isSemaphoreCapacityError,
|
|
createStreamingErrorResult,
|
|
getUpstreamErrorIdentifier,
|
|
} from "../../open-sse/handlers/chatCore/streamErrorResult.ts";
|
|
|
|
test("isSemaphoreCapacityError matches the two semaphore codes only", () => {
|
|
assert.equal(isSemaphoreCapacityError({ code: "SEMAPHORE_TIMEOUT" }), true);
|
|
assert.equal(isSemaphoreCapacityError({ code: "SEMAPHORE_QUEUE_FULL" }), true);
|
|
assert.equal(isSemaphoreCapacityError({ code: "OTHER" }), false);
|
|
assert.equal(isSemaphoreCapacityError(null), false);
|
|
assert.equal(isSemaphoreCapacityError("SEMAPHORE_TIMEOUT"), false);
|
|
});
|
|
|
|
test("createStreamingErrorResult builds an SSE error envelope with [DONE] terminator", async () => {
|
|
const result = createStreamingErrorResult(503, "boom");
|
|
assert.equal(result.success, false);
|
|
assert.equal(result.status, 503);
|
|
assert.equal(result.error, "boom");
|
|
assert.equal(result.response.status, 503);
|
|
assert.equal(result.response.headers.get("Content-Type"), "text/event-stream");
|
|
assert.equal(result.response.headers.get("X-Accel-Buffering"), "no");
|
|
const body = await result.response.text();
|
|
assert.ok(body.startsWith("data: "));
|
|
assert.ok(body.endsWith("data: [DONE]\n\n"));
|
|
const json = JSON.parse(body.slice("data: ".length, body.indexOf("\n\n")));
|
|
assert.equal(json.error.message, "boom");
|
|
});
|
|
|
|
test("createStreamingErrorResult attaches optional code and type", async () => {
|
|
const result = createStreamingErrorResult(429, "slow down", "rate_limited", "rate_limit_error");
|
|
const body = await result.response.text();
|
|
const json = JSON.parse(body.slice("data: ".length, body.indexOf("\n\n")));
|
|
assert.equal(json.error.code, "rate_limited");
|
|
assert.equal(json.error.type, "rate_limit_error");
|
|
});
|
|
|
|
test("createStreamingErrorResult sanitizes code and type at the SSE boundary", async () => {
|
|
const result = createStreamingErrorResult(
|
|
502,
|
|
"upstream failed",
|
|
"sk-live-secret-value",
|
|
"server_error\nX-Leak: yes"
|
|
);
|
|
const body = await result.response.text();
|
|
const json = JSON.parse(body.slice("data: ".length, body.indexOf("\n\n"))) as {
|
|
error: { code: string; type: string };
|
|
};
|
|
|
|
assert.equal(json.error.code, "bad_gateway");
|
|
assert.equal(json.error.type, "server_error");
|
|
assert.doesNotMatch(body, /sk-live-secret-value|X-Leak/);
|
|
});
|
|
|
|
test("getUpstreamErrorIdentifier returns a non-empty string code or undefined", () => {
|
|
assert.equal(getUpstreamErrorIdentifier({ code: "ECONNRESET" }), "ECONNRESET");
|
|
assert.equal(getUpstreamErrorIdentifier({ code: "" }), undefined);
|
|
assert.equal(getUpstreamErrorIdentifier({ code: 123 }), undefined);
|
|
assert.equal(getUpstreamErrorIdentifier(null), undefined);
|
|
assert.equal(getUpstreamErrorIdentifier("ECONNRESET"), undefined);
|
|
});
|