mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +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.
92 lines
3.3 KiB
TypeScript
92 lines
3.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
|
|
const TEST_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-stream-failure-code-"));
|
|
const TEST_DATA_DIR = path.join(TEST_ROOT, "data");
|
|
const TEST_PLUGINS_DIR = path.join(TEST_ROOT, "plugins");
|
|
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
|
|
const ORIGINAL_PLUGINS_DIR = process.env.OMNIROUTE_PLUGINS_DIR;
|
|
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
fs.mkdirSync(TEST_PLUGINS_DIR, { recursive: true });
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
process.env.OMNIROUTE_PLUGINS_DIR = TEST_PLUGINS_DIR;
|
|
|
|
const core = await import("../../../src/lib/db/core.ts");
|
|
const failureUsage = await import("../../../open-sse/handlers/chatCore/failureUsage.ts");
|
|
const usageHistory = await import("../../../src/lib/usage/usageHistory.ts");
|
|
const { createStreamFailureFinalizers } =
|
|
await import("../../../open-sse/utils/streamFailureFinalization.ts");
|
|
|
|
test.after(() => {
|
|
core.resetDbInstance();
|
|
if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR;
|
|
else process.env.DATA_DIR = ORIGINAL_DATA_DIR;
|
|
if (ORIGINAL_PLUGINS_DIR === undefined) delete process.env.OMNIROUTE_PLUGINS_DIR;
|
|
else process.env.OMNIROUTE_PLUGINS_DIR = ORIGINAL_PLUGINS_DIR;
|
|
fs.rmSync(TEST_ROOT, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
});
|
|
|
|
test("stream failure persists only the projected public classification", () => {
|
|
const opaqueCode = "opaque-stream-code-secret-9382746";
|
|
let completionCode: string | null | undefined;
|
|
let persistedCode: string | undefined;
|
|
let classifierCode: string | undefined;
|
|
const { handleStreamFailure } = createStreamFailureFinalizers({
|
|
isFailureCompletionRecorded: () => false,
|
|
onStreamComplete: (payload) => {
|
|
completionCode = payload.errorCode;
|
|
},
|
|
persistFailureUsage: (_status, errorCode) => {
|
|
persistedCode = errorCode;
|
|
},
|
|
onStreamFailure: (failure) => {
|
|
classifierCode = failure.code;
|
|
},
|
|
});
|
|
|
|
assert.equal(
|
|
handleStreamFailure({ status: 502, message: "upstream failed", code: opaqueCode }),
|
|
true
|
|
);
|
|
assert.equal(completionCode, "bad_gateway");
|
|
assert.equal(persistedCode, "bad_gateway");
|
|
assert.equal(classifierCode, opaqueCode);
|
|
});
|
|
|
|
test("pre-response failures persist only the projected public classification", async () => {
|
|
const opaqueCode = "opaque-pre-response-code-secret-6382951";
|
|
const projectedCode = failureUsage.projectFailureUsageErrorCode({
|
|
statusCode: 502,
|
|
message: "upstream request failed",
|
|
errorCode: opaqueCode,
|
|
errorType: "opaque-pre-response-type-secret-9472013",
|
|
});
|
|
|
|
assert.equal(projectedCode, "bad_gateway");
|
|
|
|
const provider = "persistent-error-code-boundary";
|
|
await usageHistory.saveRequestUsage(
|
|
failureUsage.buildFailureUsageRecord({
|
|
provider,
|
|
model: "model",
|
|
connectionId: null,
|
|
apiKeyInfo: null,
|
|
effectiveServiceTier: "standard",
|
|
isCombo: false,
|
|
comboStrategy: null,
|
|
statusCode: 502,
|
|
errorCode: projectedCode,
|
|
latencyMs: 1,
|
|
})
|
|
);
|
|
|
|
const rows = await usageHistory.getUsageHistory({ provider });
|
|
assert.equal(rows.length, 1);
|
|
assert.equal(rows[0]?.errorCode, "bad_gateway");
|
|
assert.doesNotMatch(JSON.stringify(rows), /opaque-pre-response|6382951|9472013/);
|
|
});
|