Files
2026-07-13 13:39:12 +08:00

276 lines
12 KiB
TypeScript

// Tests for the Rule #12 error-sanitization gate (scripts/check/check-error-helper.mjs).
// Exercises the pure findErrorHelperViolations() against synthetic file shapes so the
// conservative heuristic (flag direct + indirect raw-error leaks, never internal sinks
// or helper-importing files) is locked down as a regression guard.
import test from "node:test";
import assert from "node:assert/strict";
// @ts-expect-error — .mjs gate module has no type declarations; runtime shape is known.
import {
findErrorHelperViolations,
KNOWN_MISSING_ERROR_HELPER,
} from "../../scripts/check/check-error-helper.mjs";
type FileEntry = { path: string; source: string };
type FindFn = (files: FileEntry[], allowlist: Set<string>) => string[];
const find = findErrorHelperViolations as FindFn;
const allowlist = KNOWN_MISSING_ERROR_HELPER as Set<string>;
const EMPTY = new Set<string>();
function run(source: string, path = "open-sse/executors/x.ts"): string[] {
return find([{ path, source } as FileEntry], EMPTY);
}
test("flags raw err.message assigned directly to an error: field", () => {
const src = `export function exec() {
try { doThing(); } catch (err) {
return { success: false, status: 502, error: err.message };
}
}`;
assert.deepEqual(run(src), ["open-sse/executors/x.ts"]);
});
test("flags raw err.message interpolated into a message: field", () => {
const src = `function build(err: Error) {
return new Response(JSON.stringify({ error: { message: \`boom: \${err.message}\` } }));
}`;
assert.deepEqual(run(src), ["open-sse/executors/x.ts"]);
});
test("flags err.stack placed into a message: field", () => {
const src = `function build(err: Error) {
return { error: { message: err.stack } };
}`;
assert.deepEqual(run(src), ["open-sse/executors/x.ts"]);
});
test("flags multi-line OpenAI error envelope inside new Response()", () => {
const src = `function build(err: unknown) {
return new Response(
JSON.stringify({
error: {
message: isTls
? \`tls failed: \${(err as Error).message}\`
: \`conn failed: \${err instanceof Error ? err.message : String(err)}\`,
type: "upstream_error",
},
}),
{ status: 502 }
);
}`;
assert.deepEqual(run(src), ["open-sse/executors/x.ts"]);
});
test("flags a tainted local variable passed into a response-builder call", () => {
const src = `function makeErrorResponse(s: number, m: string) { return new Response(m); }
function exec(err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
return { response: makeErrorResponse(401, \`auth failed: \${msg}\`) };
}`;
assert.deepEqual(run(src), ["open-sse/executors/x.ts"]);
});
test("flags errResp(msg) where msg is tainted", () => {
const src = `function errResp(message: string) { return new Response(JSON.stringify({ error: { message } })); }
function exec(err: unknown) {
const msg = err instanceof Error ? err.message : "Failed to get nonce";
return { response: errResp(msg) };
}`;
assert.deepEqual(run(src), ["open-sse/executors/x.ts"]);
});
test("flags forwarded upstream body.error.message without sanitize", () => {
const src = `function build(body: { error: { message: string } }) {
return { success: false, error: body.error.message };
}`;
assert.deepEqual(run(src), ["open-sse/executors/x.ts"]);
});
// --- Negative cases: the gate must NOT flag these (conservative, no false positives) ---
test("does NOT flag a file that imports utils/error (relative)", () => {
const src = `import { sanitizeErrorMessage } from "../utils/error.ts";
function build(err: Error) { return { error: { message: err.message } }; }`;
assert.deepEqual(run(src), []);
});
test("does NOT flag a file that imports utils/error (workspace alias)", () => {
const src = `import { buildErrorBody } from "@omniroute/open-sse/utils/error";
function build(err: Error) { return new Response(JSON.stringify({ error: { message: \`x \${err.message}\` } })); }`;
assert.deepEqual(run(src), []);
});
test("does NOT flag raw err.message inside a saveCallLog audit row", () => {
const src = `function exec(err: Error) {
saveCallLog({
method: "POST",
status: 502,
error: err.message,
requestBody: rb,
}).catch(() => {});
return ok;
}`;
assert.deepEqual(run(src), []);
});
test("does NOT flag raw err.message inside a log call", () => {
const src = `function exec(err: Error) {
log?.error?.("X", \`refresh error: \${err.message}\`);
return ok;
}`;
assert.deepEqual(run(src), []);
});
test("does NOT flag err.message inside a thrown Error", () => {
const src = `function exec(err: Error) {
throw new Error(\`SPA send failed: \${err instanceof Error ? err.message : String(err)}\`);
}`;
assert.deepEqual(run(src), []);
});
test("does NOT flag err.message inside reject()", () => {
const src = `new Promise((_, reject) => {
onErr((err: Error) => reject(new Error(\`failed: \${err.message}\`)));
});`;
assert.deepEqual(run(src), []);
});
test("does NOT flag upstream-event read event.error.message", () => {
const src = `function parse(event: { error: { message: string } }) {
const content = typeof event.error === "string" ? event.error : event.error.message;
return { choices: [{ message: { content } }] };
}`;
assert.deepEqual(run(src), []);
});
test("does NOT flag a sanitized body.error.message line", () => {
const src = `function build(body: { error: { message: string } }) {
return { error: sanitizeErrorMessage(body.error.message) };
}`;
assert.deepEqual(run(src), []);
});
// --- Allowlist behavior ---
test("an allowlisted path is suppressed even when it would otherwise flag", () => {
const src = `function build(err: Error) { return { error: { message: err.message } }; }`;
const path = "open-sse/executors/legacy.ts";
assert.deepEqual(find([{ path, source: src } as FileEntry], EMPTY), [path]);
assert.deepEqual(find([{ path, source: src } as FileEntry], new Set([path])), []);
});
test("the shipped allowlist freezes exactly the known current violators (all scopes)", () => {
const frozen = [...allowlist].sort();
// 6A.8: expanded scope includes src/app/api/**/route.ts.
// The original open-sse/executors+handlers violations were resolved before 6A.8 landed,
// so only the newly-discovered API route violations remain frozen.
assert.deepEqual(frozen, []);
});
async function assertRouteRemovedFromMissingHelperAllowlist(path: string) {
const source = await import("node:fs").then((fs) => fs.readFileSync(path, "utf8"));
assert.equal(allowlist.has(path), false);
assert.deepEqual(find([{ path, source }], EMPTY), []);
assert.ok(source.includes("sanitizeErrorMessage"));
}
test("import-json route has been removed from the shipped missing-helper allowlist", async () => {
await assertRouteRemovedFromMissingHelperAllowlist("src/app/api/settings/import-json/route.ts");
});
test("logs export route has been removed from the shipped missing-helper allowlist", async () => {
await assertRouteRemovedFromMissingHelperAllowlist("src/app/api/logs/export/route.ts");
});
test("proxy logs route has been removed from the shipped missing-helper allowlist", async () => {
await assertRouteRemovedFromMissingHelperAllowlist("src/app/api/usage/proxy-logs/route.ts");
});
test("models catalog route has been removed from the shipped missing-helper allowlist", async () => {
await assertRouteRemovedFromMissingHelperAllowlist("src/app/api/models/catalog/route.ts");
});
test("cli-tools backups route has been removed from the shipped missing-helper allowlist", async () => {
await assertRouteRemovedFromMissingHelperAllowlist("src/app/api/cli-tools/backups/route.ts");
});
test("cli-tools guide-settings route has been removed from the shipped missing-helper allowlist", async () => {
await assertRouteRemovedFromMissingHelperAllowlist(
"src/app/api/cli-tools/guide-settings/[toolId]/route.ts"
);
});
test("providers test-batch route has been removed from the shipped missing-helper allowlist", async () => {
await assertRouteRemovedFromMissingHelperAllowlist("src/app/api/providers/test-batch/route.ts");
});
test("returns multiple violating paths and preserves input order", () => {
const files: FileEntry[] = [
{ path: "open-sse/executors/a.ts", source: `return { error: { message: err.message } };` },
{
path: "open-sse/executors/b.ts",
source: `import { x } from "../utils/error.ts"; return { error: err.message };`,
},
{ path: "open-sse/executors/c.ts", source: `return { error: e.stack };` },
];
assert.deepEqual(find(files, EMPTY), ["open-sse/executors/a.ts", "open-sse/executors/c.ts"]);
});
// --- 6A.8: expanded scope (MCP server + API route.ts) ---
test("6A.8: flags a file under open-sse/mcp-server/ that forwards raw err.message", () => {
const src = `function handleTool(err: Error) { return { error: { message: err.message } }; }`;
const result = find([{ path: "open-sse/mcp-server/tools/fakeTool.ts", source: src }], EMPTY);
assert.deepEqual(result, ["open-sse/mcp-server/tools/fakeTool.ts"]);
});
test("6A.8: flags a src/app/api route.ts that forwards raw err.message", () => {
const src = `function handler(err: Error) { return new Response(JSON.stringify({ error: { message: err.message } })); }`;
const result = find([{ path: "src/app/api/widgets/route.ts", source: src }], EMPTY);
assert.deepEqual(result, ["src/app/api/widgets/route.ts"]);
});
test("6A.8: does NOT flag mcp-server file that imports utils/error", () => {
const src = `import { buildErrorBody } from "@omniroute/open-sse/utils/error";
function handleTool(err: Error) { return buildErrorBody(err); }`;
const result = find([{ path: "open-sse/mcp-server/tools/safeTool.ts", source: src }], EMPTY);
assert.deepEqual(result, []);
});
test("6A.8: does NOT flag api route.ts that imports utils/error", () => {
const src = `import { sanitizeErrorMessage } from "../../../../open-sse/utils/error.ts";
export async function POST(req: Request) { try { } catch (err) { return new Response(sanitizeErrorMessage(err)); } }`;
const result = find([{ path: "src/app/api/safe-route/route.ts", source: src }], EMPTY);
assert.deepEqual(result, []);
});
// --- 6A.8: stale-allowlist enforcement ---
// @ts-expect-error — reportStaleEntries exported from the gate module
import { reportStaleEntries } from "../../scripts/check/lib/allowlist.mjs";
type ReportStaleFn = (allowlist: Set<string> | string[], live: string[], gate: string) => string[];
const reportStale = reportStaleEntries as ReportStaleFn;
test("6A.8 stale: reportStaleEntries identifies entries that no longer match any live violation", () => {
const allow = new Set(["open-sse/executors/fixed.ts", "open-sse/executors/live.ts"]);
const live = ["open-sse/executors/live.ts"];
const stale = reportStale(allow, live, "check-error-helper");
assert.deepEqual(stale, ["open-sse/executors/fixed.ts"]);
});
test("6A.8 stale: no stale entries when all allowlist items are still live violations", () => {
const allow = new Set(["open-sse/executors/a.ts", "open-sse/executors/b.ts"]);
const live = ["open-sse/executors/a.ts", "open-sse/executors/b.ts"];
assert.deepEqual(reportStale(allow, live, "check-error-helper"), []);
});
test("6A.8: the shipped allowlist freezes the new expanded-scope known violators (api routes)", () => {
// These are the real violations found when expanding scope to src/app/api/**/route.ts.
// They are frozen as pre-existing; fixing one requires removing it from the allowlist.
const expectedApiViolators: string[] = [];
for (const p of expectedApiViolators) {
assert.ok(allowlist.has(p), `expected allowlist to contain pre-existing API violation: ${p}`);
}
});