Files
OmniRoute/tests/integration/security-hardening.test.ts
Xiangzhe c51d74213e fix(ci): drain the electron packaging regression, the models-catalog e2e assertion and 10 integration reds
Electron Package Smoke — a packaging defect that had been hidden behind another
packaging defect for nine days. Once the loginHeaderCapture fix let the main process
start, the server underneath died on 'Cannot find module next': resources/app/server.js
shipped without resources/app/node_modules.

electron-builder discards the ROOT node_modules in code, not by configuration —
app-builder-lib/out/util/filter.js:42 has a hard-coded `if (relative === "node_modules")
return false` that runs before any filter pattern. The second extraResources entry
pointing INTO ../.build/electron-standalone/node_modules is what sidesteps it, because
those relative paths are never equal to "node_modules". #10325 removed that entry as an
apparent duplicate and flipped the test to assert "exactly once", freezing the
regression as if it were the contract. Restored, and the unit guard now pins both
entries — proven by mutation: reverting package.json to the post-#10325 shape fails the
guard 3/4, restoring it passes 4/4.

group-b-quota-plans-config — the assertion was impossible to satisfy on ANY route, and
the page was never broken. layout.tsx hands the whole message catalogue to
NextIntlClientProvider, React serialises that prop into the RSC payload, and en.json
carries "Internal Server Error" twice, so page.content() always contains it: probing
/dashboard, /dashboard/costs, /dashboard/settings and /login showed the string present
with every page rendering fine, and a pageerror probe on the failing run captured zero
client exceptions. This is the same trap that killed the sibling not.toContain("500")
in fc77100c3f ("raw HTML is unreliable") — that one was removed, this one was kept.
Now asserts on rendered text, which still catches a real error boundary. The pageerror
capture stays: the CI failure carried no stack trace, which is why it was misread twice.

Integration — 10 of the 14 shard-2 reds, all sibling-test gaps behind security fixes:
monitoring health now takes a Request and requires management auth (GHSA-mvf8-qc78-5mxm);
the OAuth import routes moved to requireManagementAuth (GHSA-mg76) — the test accepts
both guard shapes and gained a stronger anchor that every exported handler awaits a
guard on its own request, mutation-verified; skill tool names are derived from
encodeSkillToolName() and the fake upstream now returns the encoded name so
decodeSkillToolName() is exercised too; previous_response_id now fails closed (#10262);
proxy_logs persist as an async batch (#11182) so the test flushes first;
providerQuotaOverrides joined GET /api/resilience (#9871); the reasoning fixture used a
model that stopped being thinking-incompatible, replaced and pinned with a premise
assert so it cannot rot silently again.

A vacuous assert.ok(true, "all 10 streams completed without hanging") was replaced with
real anchors — content must arrive on every stream and the active Timeout count must not
grow.

Four are deliberately left red rather than aligned, each now tracked: #11551 (the
/v1/models after() wiring is dead — the route passes a third argument to a two-parameter
function and catalogCache never imports after, so the #8728 contract is unimplemented),
#11552 (~27% of requests emit an extra discarded upstream call; the delivered
distribution is exactly 0.70, so weighted routing is correct and the waste is the real
finding), the fixed-account combo pin (aligning it would destroy the per-step attribution
the test exists for), and the web_search fallback already tracked as #11524.

Package Artifact — the provenance stamp I added last round used git rev-parse HEAD, which
under pull_request is the ephemeral merge commit and therefore never an ancestor of the
release branch. Now takes the PR head sha.

Refs #10692
2026-08-25 16:46:23 -03:00

343 lines
14 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
const ROOT = path.resolve(import.meta.dirname, "../..");
function readIfExists(relPath) {
const full = path.join(ROOT, relPath);
if (!fs.existsSync(full)) return null;
return fs.readFileSync(full, "utf-8");
}
// ─── Docker Hardening Checks ─────────────────────────
test("Dockerfile uses non-root user", () => {
const content = readIfExists("Dockerfile");
if (!content) return;
// Keep as warning-only to avoid false negatives in current image policy.
const hasUser = /^USER\s+\S+/m.test(content);
if (!hasUser) {
console.log(" ⚠️ WARNING: Dockerfile does not specify a non-root USER");
}
});
test("Dockerfile does not COPY .env or secrets", () => {
const content = readIfExists("Dockerfile");
if (!content) return;
assert.equal(/COPY.*\.env\b/m.test(content), false, "Dockerfile should not COPY .env files");
});
test(".dockerignore excludes sensitive files", () => {
const content = readIfExists(".dockerignore");
if (!content) return;
assert.ok(content.includes(".env"), ".dockerignore should exclude .env files");
});
// ─── Secrets Hardening Checks ────────────────────────
test("package.json does not contain hardcoded legacy secrets", () => {
const pkg = readIfExists("package.json");
assert.ok(pkg, "package.json should exist");
const sensitivePatterns = [
"omniroute-default-secret-change-me",
"endpoint-proxy-api-key-secret",
"change-me-storage-encryption",
];
for (const pattern of sensitivePatterns) {
assert.equal(pkg.includes(pattern), false, `package.json should not contain "${pattern}"`);
}
});
test("proxy.ts does not contain hardcoded JWT_SECRET fallback", () => {
const content = readIfExists("src/proxy.ts");
assert.ok(content, "src/proxy.ts should exist");
assert.equal(
content.includes("omniroute-default-secret-change-me"),
false,
"src/proxy.ts should not have hardcoded JWT_SECRET fallback"
);
});
test("apiKey.ts does not contain legacy API_KEY_SECRET fallback literal", () => {
const content = readIfExists("src/shared/utils/apiKey.ts");
assert.ok(content, "src/shared/utils/apiKey.ts should exist");
assert.equal(
content.includes("endpoint-proxy-api-key-secret"),
false,
"src/shared/utils/apiKey.ts should not contain legacy fallback literal"
);
});
test(".env.example has empty JWT_SECRET (not a default value)", () => {
const envExample = readIfExists(".env.example");
assert.ok(envExample, ".env.example should exist");
const jwtLine = envExample.split("\n").find((l) => l.startsWith("JWT_SECRET="));
assert.ok(jwtLine, ".env.example should have JWT_SECRET");
const value = jwtLine.split("=")[1]?.trim();
assert.ok(!value || value === "", "JWT_SECRET should be empty in .env.example");
});
test(".env.example has empty API_KEY_SECRET (not a default value)", () => {
const envExample = readIfExists(".env.example");
assert.ok(envExample, ".env.example should exist");
const apiKeyLine = envExample.split("\n").find((l) => l.startsWith("API_KEY_SECRET="));
assert.ok(apiKeyLine, ".env.example should have API_KEY_SECRET");
const value = apiKeyLine.split("=")[1]?.trim();
assert.ok(!value || value === "", "API_KEY_SECRET should be empty in .env.example");
});
// ─── Schema Hardening Checks ─────────────────────────
test("schemas.ts does not use .passthrough() in executable code", () => {
const content = readIfExists("src/shared/validation/schemas.ts");
assert.ok(content, "src/shared/validation/schemas.ts should exist");
const lines = content.split("\n");
const codeLines = lines.filter((l) => !l.trim().startsWith("//") && !l.trim().startsWith("*"));
const hasPassthrough = codeLines.some((l) => l.includes(".passthrough()"));
assert.equal(
hasPassthrough,
false,
"schemas.ts should not use .passthrough() in code — fields must be explicitly listed"
);
});
// ─── Dependency / CI Checks ──────────────────────────
test("package.json does not depend on npm 'fs' package", () => {
const pkg = JSON.parse(readIfExists("package.json"));
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
assert.equal("fs" in allDeps, false, "Should not depend on npm 'fs' package (use node:fs)");
});
test("CI workflow exists and runs lint + tests", () => {
const content = readIfExists(".github/workflows/ci.yml");
assert.ok(content, "CI workflow should exist at .github/workflows/ci.yml");
assert.ok(content.includes("lint"), "CI should run linting");
assert.ok(
content.includes("test:unit") || content.includes("npm test") || content.includes("test"),
"CI should run tests"
);
});
test("package.json test script runs tests", () => {
const pkg = JSON.parse(readIfExists("package.json"));
const testScript = pkg.scripts?.test;
assert.ok(testScript, "package.json must have a test script");
assert.ok(
testScript.includes("--test") || testScript.includes("vitest") || testScript.includes("jest"),
`test script should run tests, got: ${testScript}`
);
});
// ─── Runtime Wiring Checks ───────────────────────────
test("chat handler wires guardrail pre-call validation", () => {
const content = readIfExists("src/sse/handlers/chat.ts");
assert.ok(content, "src/sse/handlers/chat.ts should exist");
assert.ok(
content.includes("guardrailRegistry") && content.includes("runPreCallHooks"),
"chat.ts should route request validation through the guardrail registry"
);
});
test("instrumentation-node.ts validates runtime env after restoring secrets", () => {
const content = readIfExists("src/instrumentation-node.ts");
assert.ok(content, "src/instrumentation-node.ts should exist");
assert.ok(
content.includes("enforceWebRuntimeEnv"),
"instrumentation-node.ts should call enforceWebRuntimeEnv"
);
});
// ─── T06/T07 Regression Checks ───────────────────────
test("callLogs.ts wires no-log and PII sanitization before persistence", () => {
const content = readIfExists("src/lib/usage/callLogs.ts");
assert.ok(content, "src/lib/usage/callLogs.ts should exist");
assert.ok(
content.includes('from "../compliance"') || content.includes('from "../compliance/noLog"'),
"callLogs.ts should import compliance module"
);
// PII sanitization for error strings was extracted to callLogs/format.ts by #5725
// (sanitizeErrorForLog); callLogs.ts still wires it in before persistence, and the
// extracted helper keeps the piiSanitizer dependency — so the "sanitize before
// persist" invariant holds post-refactor (verified on both the helper and the file).
assert.ok(
content.includes("sanitizeErrorForLog") && content.includes('from "./callLogs/format"'),
"callLogs.ts should wire the extracted PII-sanitizing error helper (sanitizeErrorForLog)"
);
const formatHelperContent = readIfExists("src/lib/usage/callLogs/format.ts");
assert.ok(formatHelperContent, "src/lib/usage/callLogs/format.ts should exist");
assert.ok(
formatHelperContent.includes('from "../../piiSanitizer"'),
"callLogs/format.ts should import piiSanitizer (PII sanitization still wired post-#5725)"
);
assert.ok(content.includes("isNoLog("), "callLogs.ts should check no-log policy");
const payloadHelperContent = readIfExists("src/lib/logPayloads.ts");
assert.ok(payloadHelperContent, "src/lib/logPayloads.ts should exist");
assert.ok(
content.includes("protectPayloadForLog") && content.includes('from "../logPayloads"'),
"callLogs.ts should route payload protection through shared log helpers"
);
assert.ok(
payloadHelperContent.includes("export function sanitizePayloadPII"),
"logPayloads.ts should keep recursive PII sanitization logic"
);
});
test("API key update route and DB layer wire persisted no-log controls", () => {
const routeContent = readIfExists("src/app/api/keys/[id]/route.ts");
assert.ok(routeContent, "src/app/api/keys/[id]/route.ts should exist");
assert.ok(routeContent.includes("noLog"), "key PATCH route should handle noLog field");
const dbContent = readIfExists("src/lib/db/apiKeys.ts");
assert.ok(dbContent, "src/lib/db/apiKeys.ts should exist");
assert.ok(dbContent.includes("no_log"), "api key DB module should persist no_log column");
});
test("MCP server enforces scopes from caller context before tool execution", () => {
const serverContent = readIfExists("open-sse/mcp-server/server.ts");
assert.ok(serverContent, "open-sse/mcp-server/server.ts should exist");
assert.ok(
serverContent.includes("resolveCallerScopeContext"),
"MCP server should resolve caller scopes from request context"
);
assert.ok(
serverContent.includes("evaluateToolScopes"),
"MCP server should evaluate required scopes per tool"
);
const scopeContent = readIfExists("open-sse/mcp-server/scopeEnforcement.ts");
assert.ok(scopeContent, "open-sse/mcp-server/scopeEnforcement.ts should exist");
assert.ok(
scopeContent.includes("authInfo"),
"scope enforcement should parse authInfo scopes when provided by transport"
);
});
test("ACP agents route requires management authentication before CLI discovery", () => {
const content = readIfExists("src/app/api/acp/agents/route.ts");
assert.ok(content, "src/app/api/acp/agents/route.ts should exist");
assert.ok(
content.includes('from "@/shared/utils/apiAuth"'),
"ACP agents route should import shared API auth"
);
assert.ok(
content.includes("if (!(await isAuthenticated(request)))"),
"ACP agents route should reject unauthenticated requests before spawning discovery"
);
});
test("T06 route payload validation uses validateBody in critical endpoints", () => {
const targets = [
"src/app/api/usage/budget/route.ts",
"src/app/api/policies/route.ts",
"src/app/api/fallback/chains/route.ts",
"src/app/api/models/route.ts",
"src/app/api/provider-models/route.ts",
"src/app/api/pricing/route.ts",
"src/app/api/rate-limits/route.ts",
"src/app/api/resilience/route.ts",
"src/app/api/v1/embeddings/route.ts",
"src/app/api/v1/images/generations/route.ts",
"src/app/api/v1/audio/speech/route.ts",
"src/app/api/v1/moderations/route.ts",
"src/app/api/v1/rerank/route.ts",
"src/app/api/oauth/[provider]/[action]/route.ts",
"src/app/api/oauth/cursor/import/route.ts",
"src/app/api/oauth/kiro/import/route.ts",
"src/app/api/oauth/kiro/social-exchange/route.ts",
"src/app/api/cloud/credentials/update/route.ts",
"src/app/api/cloud/model/resolve/route.ts",
"src/app/api/cloud/models/alias/route.ts",
"src/app/api/sync/cloud/route.ts",
"src/app/api/combos/[id]/route.ts",
"src/app/api/combos/test/route.ts",
"src/app/api/db-backups/route.ts",
"src/app/api/evals/route.ts",
"src/app/api/keys/[id]/route.ts",
"src/app/api/models/alias/route.ts",
"src/app/api/provider-nodes/route.ts",
"src/app/api/provider-nodes/[id]/route.ts",
"src/app/api/provider-nodes/validate/route.ts",
"src/app/api/providers/[id]/route.ts",
"src/app/api/providers/test-batch/route.ts",
"src/app/api/providers/validate/route.ts",
"src/app/api/v1beta/models/[...path]/route.ts",
"src/app/api/cli-tools/antigravity-mitm/route.ts",
"src/app/api/cli-tools/antigravity-mitm/alias/route.ts",
"src/app/api/cli-tools/backups/route.ts",
"src/app/api/cli-tools/claude-settings/route.ts",
"src/app/api/cli-tools/cline-settings/route.ts",
"src/app/api/cli-tools/codex-profiles/route.ts",
"src/app/api/cli-tools/codex-settings/route.ts",
"src/app/api/cli-tools/droid-settings/route.ts",
"src/app/api/cli-tools/guide-settings/[toolId]/route.ts",
"src/app/api/cli-tools/kilo-settings/route.ts",
"src/app/api/cli-tools/openclaw-settings/route.ts",
];
for (const relPath of targets) {
const content = readIfExists(relPath);
assert.ok(content, `${relPath} should exist`);
assert.ok(
content.includes("validateBody("),
`${relPath} should validate payload with validateBody`
);
}
});
test("OAuth routes that can create provider connections require auth guard", () => {
const targets = [
"src/app/api/oauth/[provider]/[action]/route.ts",
"src/app/api/oauth/cursor/import/route.ts",
"src/app/api/oauth/kiro/import/route.ts",
"src/app/api/oauth/kiro/social-authorize/route.ts",
"src/app/api/oauth/kiro/social-exchange/route.ts",
];
for (const relPath of targets) {
const content = readIfExists(relPath);
assert.ok(content, `${relPath} should exist`);
// Two accepted guard shapes. GHSA-mg76 moved the cursor/kiro *import* routes
// onto requireManagementAuth, which is strictly STRONGER than the legacy
// pair: it demands a management principal (dashboard session, manage-scoped
// key, CLI token) instead of merely "any authenticated caller", and answers
// 401/403 itself — so the literal "Unauthorized" no longer appears in the
// route file. The remaining routes still carry the legacy triple.
const usesManagementGuard = content.includes("requireManagementAuth(request");
const usesLegacyGuard =
content.includes("isAuthRequired") &&
content.includes("isAuthenticated") &&
content.includes("Unauthorized");
assert.ok(
usesManagementGuard || usesLegacyGuard,
`${relPath} must guard connection-creating handlers with requireManagementAuth or the isAuthRequired/isAuthenticated pair`
);
// Positive anchor: a guard somewhere in the file proves nothing if one of the
// exported handlers skips it. Slice the file per exported handler and require
// EACH body to await a guard on its own `request` — a guard living only in a
// helper (or in a sibling handler) no longer satisfies this.
const handlerSlices = content
.split(/(?=export\s+async\s+function\s+(?:GET|POST|PUT|PATCH|DELETE)\b)/)
.filter((slice) =>
/^export\s+async\s+function\s+(?:GET|POST|PUT|PATCH|DELETE)\b/.test(slice)
);
assert.ok(handlerSlices.length > 0, `${relPath} should export at least one HTTP handler`);
for (const slice of handlerSlices) {
const verb = /export\s+async\s+function\s+(\w+)/.exec(slice)?.[1];
assert.match(
slice,
/await\s+(?:require\w*Auth|isAuthRequired)\s*\(\s*(?:request|req)\b/,
`${relPath}: exported handler ${verb} does not await an auth guard on its own request`
);
}
}
});