90 lines
3.8 KiB
TypeScript
90 lines
3.8 KiB
TypeScript
/**
|
|
* Static guard tests for the error-sanitization fixes in release/v3.8.2.
|
|
*
|
|
* Review findings: two authenticated routes returned raw error text in their
|
|
* HTTP body (`(error as Error).message` / `String(error)`), violating the
|
|
* project's error-sanitization policy (CLAUDE.md hard rule 12,
|
|
* docs/security/ERROR_SANITIZATION.md). These guards pin the fix in source so
|
|
* the anti-pattern cannot silently return. (Static-source assertions mirror the
|
|
* established style of cli-tools-auth-hardening.test.ts.)
|
|
*/
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
|
|
function readRoute(rel: string): string {
|
|
return fs.readFileSync(path.join(REPO_ROOT, rel), "utf8");
|
|
}
|
|
|
|
const CACHE_STATS = "src/app/api/cache/stats/route.ts";
|
|
const HERMES = "src/app/api/cli-tools/hermes-agent-settings/route.ts";
|
|
const COPILOT_CHAT = "src/app/api/copilot/chat/route.ts";
|
|
const DB_IMPORT = "src/app/api/db-backups/import/route.ts";
|
|
|
|
test("db-backups/import awaits getSettings() before re-hydrating the system prompt", () => {
|
|
// getSettings() is async; without `await`, importedSettings is a Promise and
|
|
// `.systemPrompt` is always undefined, so the #2470 re-hydration silently
|
|
// never fires after a DB import.
|
|
const src = readRoute(DB_IMPORT);
|
|
assert.match(
|
|
src,
|
|
/const importedSettings = await getSettings\(\);/,
|
|
"must await getSettings() so systemPrompt re-hydration actually runs"
|
|
);
|
|
assert.ok(
|
|
!/const importedSettings = getSettings\(\);/.test(src),
|
|
"must not read systemPrompt off an un-awaited Promise"
|
|
);
|
|
});
|
|
|
|
test("cache/stats route routes errors through sanitizeErrorMessage", () => {
|
|
const src = readRoute(CACHE_STATS);
|
|
assert.match(src, /import \{ sanitizeErrorMessage \}/, "must import sanitizeErrorMessage");
|
|
assert.match(src, /sanitizeErrorMessage\(error\)/, "catch blocks must sanitize the error");
|
|
assert.ok(
|
|
!/\(error as Error\)\.message/.test(src),
|
|
"must not put raw (error as Error).message in the response body"
|
|
);
|
|
});
|
|
|
|
test("hermes-agent-settings GET sanitizes its error response (no raw String(error))", () => {
|
|
const src = readRoute(HERMES);
|
|
assert.match(src, /import \{ sanitizeErrorMessage \}/, "must import sanitizeErrorMessage");
|
|
assert.match(src, /sanitizeErrorMessage\(error\)/, "GET catch must sanitize the error");
|
|
assert.ok(
|
|
!/error: String\(error\)/.test(src),
|
|
"must not return raw String(error) in the response body"
|
|
);
|
|
});
|
|
|
|
test("hermes-agent-settings POST validates baseUrl as an http(s) URL", () => {
|
|
const src = readRoute(HERMES);
|
|
assert.match(
|
|
src,
|
|
/import \{ validateBaseUrl \}/,
|
|
"must import the shared validateBaseUrl helper"
|
|
);
|
|
assert.match(src, /validateBaseUrl\(baseUrl\)/, "POST must validate the supplied baseUrl");
|
|
});
|
|
|
|
test("hermes-agent-settings POST validates preview mode before use", () => {
|
|
const src = readRoute(HERMES);
|
|
assert.match(src, /preview: z\.boolean\(\)\.optional\(\)/, "preview must be schema-bound");
|
|
assert.match(src, /const \{ baseUrl, keyId, apiKey, selections, preview \} = parsed\.data;/);
|
|
assert.match(src, /if \(preview === true\)/);
|
|
assert.ok(!/body\.preview/.test(src), "must not reference an undefined body variable");
|
|
});
|
|
|
|
test("copilot chat route requires management auth and sanitizes thrown errors", () => {
|
|
const src = readRoute(COPILOT_CHAT);
|
|
assert.match(src, /requireManagementAuth/, "route should enforce management auth");
|
|
assert.match(src, /const authError = await requireManagementAuth\(request\);/);
|
|
assert.match(src, /sanitizeErrorMessage\(error\)/, "catch blocks must sanitize errors");
|
|
assert.ok(!/error\.message/.test(src), "must not return raw error.message");
|
|
});
|