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.
222 lines
7.5 KiB
TypeScript
222 lines
7.5 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { handleModeration } = await import("../../open-sse/handlers/moderations.ts");
|
|
const { MODERATION_PROVIDERS, getModerationProvider, parseModerationModel } =
|
|
await import("../../open-sse/config/moderationRegistry.ts");
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
test.afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
|
|
test("MODERATION_PROVIDERS registers mistral with the Mistral moderations base URL", () => {
|
|
const provider = getModerationProvider("mistral");
|
|
assert.ok(provider);
|
|
assert.equal(provider.baseUrl, "https://api.mistral.ai/v1/moderations");
|
|
assert.ok(provider.models.some((m: { id: string }) => m.id === "mistral-moderation-latest"));
|
|
assert.ok(MODERATION_PROVIDERS.mistral);
|
|
});
|
|
|
|
test("parseModerationModel routes mistral moderation models to the mistral provider", () => {
|
|
assert.deepEqual(parseModerationModel("mistral/mistral-moderation-latest"), {
|
|
provider: "mistral",
|
|
model: "mistral-moderation-latest",
|
|
});
|
|
assert.deepEqual(parseModerationModel("mistral-moderation-latest"), {
|
|
provider: "mistral",
|
|
model: "mistral-moderation-latest",
|
|
});
|
|
});
|
|
|
|
test("handleModeration proxies mistral moderation requests to the mistral endpoint", async () => {
|
|
let captured: any;
|
|
globalThis.fetch = async (url: any, options: any = {}) => {
|
|
captured = { url: String(url), headers: options.headers };
|
|
return Response.json({ id: "modr-mistral", results: [{ flagged: false }] });
|
|
};
|
|
|
|
const response = await handleModeration({
|
|
body: { model: "mistral/mistral-moderation-latest", input: "check this" },
|
|
credentials: { apiKey: "sk-mistral" },
|
|
});
|
|
|
|
assert.equal(captured.url, "https://api.mistral.ai/v1/moderations");
|
|
assert.equal(captured.headers.Authorization, "Bearer sk-mistral");
|
|
assert.equal(response.status, 200);
|
|
});
|
|
|
|
test("handleModeration requires input", async () => {
|
|
const response = await handleModeration({
|
|
body: { model: "openai/omni-moderation-latest" },
|
|
credentials: { apiKey: "sk-test" },
|
|
});
|
|
const payload = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.equal(payload.error.message, "input is required");
|
|
});
|
|
|
|
test("handleModeration rejects unknown moderation models", async () => {
|
|
const response = await handleModeration({
|
|
body: { model: "mystery/moderation", input: "hello" },
|
|
credentials: { apiKey: "sk-test" },
|
|
});
|
|
const payload = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 400);
|
|
assert.match(payload.error.message, /No moderation provider found/);
|
|
});
|
|
|
|
test("handleModeration requires credentials for the resolved provider", async () => {
|
|
const response = await handleModeration({
|
|
body: { input: "hello" },
|
|
credentials: null,
|
|
});
|
|
const payload = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 401);
|
|
assert.equal(payload.error.message, "No credentials for moderation provider: openai");
|
|
});
|
|
|
|
test("handleModeration proxies successful requests with default model and accessToken fallback", async () => {
|
|
let captured;
|
|
|
|
globalThis.fetch = async (url, options = {}) => {
|
|
captured = {
|
|
url: String(url),
|
|
headers: options.headers,
|
|
body: JSON.parse(String(options.body || "{}")),
|
|
};
|
|
|
|
return Response.json({
|
|
id: "modr-1",
|
|
results: [{ flagged: false }],
|
|
});
|
|
};
|
|
|
|
const response = await handleModeration({
|
|
body: { input: "all clear" },
|
|
credentials: { accessToken: "oauth-token" },
|
|
});
|
|
|
|
assert.equal(captured.url, "https://api.openai.com/v1/moderations");
|
|
assert.equal(captured.headers.Authorization, "Bearer oauth-token");
|
|
assert.deepEqual(captured.body, {
|
|
model: "omni-moderation-latest",
|
|
input: "all clear",
|
|
});
|
|
assert.equal(response.status, 200);
|
|
assert.equal(response.headers.get("access-control-allow-origin"), null);
|
|
assert.match(response.headers.get("access-control-allow-methods") || "", /OPTIONS/);
|
|
assert.deepEqual(await response.json(), {
|
|
id: "modr-1",
|
|
results: [{ flagged: false }],
|
|
});
|
|
});
|
|
|
|
test("handleModeration returns upstream error payloads with CORS headers", async () => {
|
|
globalThis.fetch = async () =>
|
|
new Response('{"error":"busy"}', {
|
|
status: 429,
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
|
|
const response = await handleModeration({
|
|
body: { model: "openai/text-moderation-latest", input: "check this" },
|
|
credentials: { apiKey: "sk-test" },
|
|
});
|
|
|
|
assert.equal(response.status, 429);
|
|
assert.equal(await response.text(), '{"error":"busy"}');
|
|
assert.equal(response.headers.get("content-type"), "application/json");
|
|
assert.equal(response.headers.get("access-control-allow-origin"), null);
|
|
assert.match(response.headers.get("access-control-allow-methods") || "", /OPTIONS/);
|
|
});
|
|
|
|
test("handleModeration sanitizes structured upstream error bodies", async () => {
|
|
globalThis.fetch = async () =>
|
|
Response.json(
|
|
{
|
|
error: {
|
|
message: "quota metadata at /srv/provider/private.json",
|
|
api_key: "credential-value-12345",
|
|
},
|
|
},
|
|
{ status: 429 }
|
|
);
|
|
|
|
const response = await handleModeration({
|
|
body: { model: "openai/text-moderation-latest", input: "check this" },
|
|
credentials: { apiKey: "sk-test" },
|
|
});
|
|
const payload = (await response.json()) as {
|
|
error: { message: string; api_key?: string };
|
|
};
|
|
|
|
assert.equal(response.status, 429);
|
|
assert.equal(payload.error.api_key, undefined);
|
|
assert.doesNotMatch(payload.error.message, /srv\/provider/i);
|
|
assert.doesNotMatch(JSON.stringify(payload), /credential-value-12345/i);
|
|
});
|
|
|
|
test("handleModeration canonicalizes blank, plaintext, and mislabeled upstream failures", async () => {
|
|
const scenarios = [
|
|
{ name: "blank", body: " ", contentType: "application/json" },
|
|
{
|
|
name: "plaintext",
|
|
body: "access_token=moderation-plain-secret at /srv/private/moderation.txt",
|
|
contentType: "text/plain",
|
|
},
|
|
{
|
|
name: "mislabeled",
|
|
body: "<html>api_key=moderation-html-secret at /srv/private/error.html</html>",
|
|
contentType: "application/json",
|
|
},
|
|
];
|
|
|
|
for (const scenario of scenarios) {
|
|
globalThis.fetch = async () =>
|
|
new Response(scenario.body, {
|
|
status: 502,
|
|
headers: { "content-type": scenario.contentType },
|
|
});
|
|
const response = await handleModeration({
|
|
body: { model: "openai/text-moderation-latest", input: "check this" },
|
|
credentials: { apiKey: "sk-test" },
|
|
});
|
|
const text = await response.text();
|
|
const payload = JSON.parse(text) as { error: { message: string } };
|
|
|
|
assert.equal(response.status, 502, scenario.name);
|
|
assert.match(response.headers.get("content-type") || "", /application\/json/i, scenario.name);
|
|
assert.match(
|
|
response.headers.get("access-control-allow-methods") || "",
|
|
/OPTIONS/,
|
|
scenario.name
|
|
);
|
|
assert.equal(typeof payload.error.message, "string", scenario.name);
|
|
assert.doesNotMatch(
|
|
text,
|
|
/moderation-plain-secret|moderation-html-secret|srv\/private|<html>/i,
|
|
scenario.name
|
|
);
|
|
}
|
|
});
|
|
|
|
test("handleModeration returns a 500 when the upstream request throws", async () => {
|
|
globalThis.fetch = async () => {
|
|
throw new Error("socket closed");
|
|
};
|
|
|
|
const response = await handleModeration({
|
|
body: { model: "openai/text-moderation-latest", input: "check this" },
|
|
credentials: { apiKey: "sk-test" },
|
|
});
|
|
const payload = (await response.json()) as any;
|
|
|
|
assert.equal(response.status, 500);
|
|
assert.match(payload.error.message, /Moderation request failed: socket closed/);
|
|
});
|