mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +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.
102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import test from "node:test";
|
|
import {
|
|
projectProviderValidationResultForPublicResponse,
|
|
toValidationErrorResult,
|
|
} from "../../src/lib/providers/validation/transport.ts";
|
|
|
|
test("provider validation sanitizes thrown error details", () => {
|
|
const result = toValidationErrorResult(
|
|
new Error(
|
|
"Provider probe failed at /srv/private/provider-key.json " +
|
|
"access_token=provider-secret\n at validate (/srv/private/validator.ts:42:7)"
|
|
)
|
|
);
|
|
|
|
assert.equal(result.valid, false);
|
|
assert.match(result.error, /Provider probe failed/i);
|
|
assert.doesNotMatch(result.error, /srv\/private|provider-secret|validator\.ts|\bat validate\b/i);
|
|
assert.equal(result.unsupported, false);
|
|
});
|
|
|
|
test("provider validation fails closed for hostile thrown values", () => {
|
|
const hostile = new Proxy(
|
|
{},
|
|
{
|
|
getPrototypeOf(): never {
|
|
throw new Error("access_token=prototype-secret at /srv/private/prototype.ts:1:2");
|
|
},
|
|
get(_target, property): unknown {
|
|
if (property === "code" || property === "isRetryable") {
|
|
throw new Error("access_token=metadata-secret at /srv/private/metadata.ts:1:2");
|
|
}
|
|
if (property === "toString") {
|
|
return () => {
|
|
throw new Error("access_token=coercion-secret at /srv/private/coercion.ts:1:2");
|
|
};
|
|
}
|
|
return undefined;
|
|
},
|
|
}
|
|
);
|
|
|
|
assert.deepEqual(toValidationErrorResult(hostile), {
|
|
valid: false,
|
|
error: "Validation failed",
|
|
unsupported: false,
|
|
});
|
|
});
|
|
|
|
test("provider validation route sanitizes unexpected failures before persistent logging", () => {
|
|
const routeSource = fs.readFileSync(
|
|
new URL("../../src/app/api/providers/validate/route.ts", import.meta.url),
|
|
"utf8"
|
|
);
|
|
|
|
assert.match(
|
|
routeSource,
|
|
/console\.log\(\s*"Error validating API key:",\s*sanitizeErrorMessage\(error\) \|\| "Validation failed"\s*\)/
|
|
);
|
|
assert.doesNotMatch(routeSource, /console\.log\(\s*"Error validating API key:",\s*error\s*\)/);
|
|
});
|
|
|
|
test("provider validation final response projection sanitizes validator errors and warnings", () => {
|
|
const projected = projectProviderValidationResultForPublicResponse({
|
|
valid: false,
|
|
error:
|
|
"Provider echoed access_token=response-secret at /srv/private/provider.json\n" +
|
|
" at validate (/srv/private/validator.ts:42:7)",
|
|
warning: "Retry after reading C:\\Users\\admin\\private\\warning.json",
|
|
method: "probe",
|
|
});
|
|
const serialized = JSON.stringify(projected);
|
|
|
|
assert.equal(projected.valid, false);
|
|
assert.equal(projected.method, "probe");
|
|
assert.doesNotMatch(
|
|
serialized,
|
|
/response-secret|srv\/private|validator\.ts|C:\\Users|warning\.json/i
|
|
);
|
|
});
|
|
|
|
test("provider validation projection preserves intentionally empty fields without synthetic text", () => {
|
|
const projected = projectProviderValidationResultForPublicResponse({
|
|
valid: false,
|
|
error: "",
|
|
warning: "",
|
|
});
|
|
|
|
assert.equal(projected.error, "");
|
|
assert.equal(projected.warning, "");
|
|
});
|
|
|
|
test("provider validation route applies the final response projection", () => {
|
|
const routeSource = fs.readFileSync(
|
|
new URL("../../src/app/api/providers/validate/route.ts", import.meta.url),
|
|
"utf8"
|
|
);
|
|
|
|
assert.match(routeSource, /projectProviderValidationResultForPublicResponse\(/);
|
|
});
|