test(mitm): bypass matching + manager bypass-json writer

Cover the new CJS routing primitives and the bypass-JSON manager write
path so the C1/C2 contracts cannot regress silently.

- `mitm-server-connect.test.ts` (27 tests): exercises the
  `_internal/bypass.cjs` shim used by `server.cjs`:
    - DEFAULT_BYPASS_PATTERNS shape (≥4 regexes, all RegExp)
    - Default bank / gov / okta / auth0 → bypass
    - Bypass beats target match (precedence)
    - Known target hostname → target
    - Unknown hostname → passthrough
    - User glob pattern → bypass
    - Empty / undefined hostname → passthrough
    - Set-vs-Array shape for targetHosts
    - Case-insensitive hostname matching
    - bypassGlobMatch wildcard semantics + ReDoS-safe linear walk
    - parseBypassJson with valid / empty / malformed / wrong-shape inputs
    - Spec assertions on server.cjs source — header injection (C2),
      sanitizeErrorMessage wrapping (Hard Rule #12), and CONNECT handler
      registration (C1). These three guard against future regressions
      that would silently re-introduce the bugs the C1/C2 fixes closed.
- `mitm-manager-bypass-json.test.ts` (5 tests): exercises
  `writeBypassJson()`:
    - Creates the `mitm/` dir + valid JSON file shape
    - Empty array roundtrips as empty patterns
    - Falls back to `getUserBypassPatterns()` when no arg passed
    - Default patterns from the DB are NOT written to the file
    - Overwrites prior content
This commit is contained in:
diegosouzapw
2026-05-28 16:48:44 -03:00
parent 69cc148543
commit f6ed411c62
2 changed files with 379 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
/**
* Tests for `writeBypassJson()` in src/mitm/manager.ts.
*
* The function persists user bypass patterns to `<DATA_DIR>/mitm/bypass.json`
* which is read by `src/mitm/server.cjs` at boot to drive the CONNECT
* handler's bypass routing decision.
*
* Plan reference: 11-agent-bridge.plan.md §4.6 + master-plan-group-A.md §3.5.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(
path.join(os.tmpdir(), "omniroute-mitm-bypass-json-")
);
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const bypassDb = await import("../../src/lib/db/agentBridgeBypass.ts");
const manager = await import("../../src/mitm/manager.ts");
async function resetStorage() {
core.resetDbInstance();
for (let attempt = 0; attempt < 10; attempt++) {
try {
if (fs.existsSync(TEST_DATA_DIR)) {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
}
break;
} catch (error: unknown) {
const code = (error as { code?: string } | null)?.code;
if ((code === "EBUSY" || code === "EPERM") && attempt < 9) {
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
} else {
throw error;
}
}
}
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(async () => {
await resetStorage();
});
test.after(async () => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("writeBypassJson — creates mitm/ dir and writes JSON file", () => {
manager.writeBypassJson(["custom.example.com"]);
const file = path.join(TEST_DATA_DIR, "mitm", "bypass.json");
assert.ok(fs.existsSync(file), "bypass.json must exist after write");
const payload = JSON.parse(fs.readFileSync(file, "utf-8"));
assert.equal(payload.version, 1);
assert.ok(typeof payload.generatedAt === "string");
assert.deepEqual(payload.patterns, ["custom.example.com"]);
});
test("writeBypassJson — empty array writes empty patterns", () => {
manager.writeBypassJson([]);
const file = path.join(TEST_DATA_DIR, "mitm", "bypass.json");
const payload = JSON.parse(fs.readFileSync(file, "utf-8"));
assert.deepEqual(payload.patterns, []);
});
test("writeBypassJson — pulls from DB when no patterns argument passed", () => {
// Seed user patterns via the DB module.
bypassDb.replaceUserBypassPatterns(["*.from-db.example.com", "literal.com"]);
manager.writeBypassJson();
const file = path.join(TEST_DATA_DIR, "mitm", "bypass.json");
const payload = JSON.parse(fs.readFileSync(file, "utf-8"));
assert.deepEqual(
payload.patterns.sort(),
["*.from-db.example.com", "literal.com"].sort()
);
});
test("writeBypassJson — does NOT write default patterns (those live in server.cjs)", () => {
// Seed defaults via the DB module — these should NOT appear in the JSON.
bypassDb.seedDefaultBypassPatterns([
"*.bank.test",
"*.gov.test",
"okta.com",
"auth0.com",
]);
manager.writeBypassJson();
const file = path.join(TEST_DATA_DIR, "mitm", "bypass.json");
const payload = JSON.parse(fs.readFileSync(file, "utf-8"));
// No user patterns were set → user-only output is empty.
assert.deepEqual(payload.patterns, []);
});
test("writeBypassJson — JSON is well-formed and overwrites previous file", () => {
manager.writeBypassJson(["first.com"]);
manager.writeBypassJson(["second.com", "third.com"]);
const file = path.join(TEST_DATA_DIR, "mitm", "bypass.json");
const payload = JSON.parse(fs.readFileSync(file, "utf-8"));
assert.deepEqual(payload.patterns, ["second.com", "third.com"]);
});

View File

@@ -0,0 +1,275 @@
/**
* Tests for the bypass / passthrough / target routing primitives used by
* the CJS proxy in `src/mitm/server.cjs`.
*
* The CJS proxy itself cannot be required in tests (it spawns a TLS server
* and exits when ROUTER_API_KEY is missing). Instead, we exercise the
* `_internal/bypass.cjs` shim that `server.cjs` depends on. That shim
* carries all the routing logic — `server.cjs` is now a thin wiring layer
* around it.
*
* Plan reference:
* - 11-agent-bridge.plan.md §4.6
* - master-plan-group-A.md §3.5 / §12 #16
*/
import test from "node:test";
import assert from "node:assert/strict";
import { createRequire } from "node:module";
const requireCjs = createRequire(import.meta.url);
const shim = requireCjs("../../src/mitm/_internal/bypass.cjs") as {
DEFAULT_BYPASS_PATTERNS: RegExp[];
bypassGlobMatch: (h: string, p: string) => boolean;
routeBypass: (
h: string,
targetHosts: Set<string> | string[] | undefined,
userPatterns: string[]
) => "bypass" | "target" | "passthrough";
parseBypassJson: (raw: string) => string[];
};
const TARGETS = new Set([
"daily-cloudcode-pa.googleapis.com",
"api.githubcopilot.com",
]);
test("DEFAULT_BYPASS_PATTERNS — at least the 4 mandatory regexes", () => {
assert.ok(shim.DEFAULT_BYPASS_PATTERNS.length >= 4);
for (const re of shim.DEFAULT_BYPASS_PATTERNS) {
assert.ok(re instanceof RegExp);
}
});
test("routeBypass — default bank pattern → bypass", () => {
assert.equal(shim.routeBypass("my.bank.example", TARGETS, []), "bypass");
assert.equal(shim.routeBypass("secure.bank.com", TARGETS, []), "bypass");
});
test("routeBypass — default gov pattern → bypass", () => {
assert.equal(shim.routeBypass("portal.gov.br", TARGETS, []), "bypass");
assert.equal(shim.routeBypass("tax.gov", TARGETS, []), "bypass");
});
test("routeBypass — default okta pattern → bypass", () => {
assert.equal(shim.routeBypass("mycorp.okta.com", TARGETS, []), "bypass");
assert.equal(shim.routeBypass("okta.com", TARGETS, []), "bypass");
});
test("routeBypass — default auth0 pattern → bypass", () => {
assert.equal(shim.routeBypass("myapp.auth0.com", TARGETS, []), "bypass");
});
test("routeBypass — bypass beats target match (precedence)", () => {
// Hypothetical: user has an okta-hosted Copilot account. Bypass wins.
const targets = new Set(["my.okta.com"]);
assert.equal(shim.routeBypass("my.okta.com", targets, []), "bypass");
});
test("routeBypass — known target hostname → target", () => {
assert.equal(
shim.routeBypass("daily-cloudcode-pa.googleapis.com", TARGETS, []),
"target"
);
assert.equal(shim.routeBypass("api.githubcopilot.com", TARGETS, []), "target");
});
test("routeBypass — unknown hostname → passthrough", () => {
assert.equal(shim.routeBypass("example.com", TARGETS, []), "passthrough");
assert.equal(shim.routeBypass("api.openai.com", TARGETS, []), "passthrough");
});
test("routeBypass — user glob pattern → bypass", () => {
const userPatterns = ["*.internal.example.com"];
assert.equal(
shim.routeBypass("admin.internal.example.com", TARGETS, userPatterns),
"bypass"
);
assert.equal(
shim.routeBypass("external.example.com", TARGETS, userPatterns),
"passthrough"
);
});
test("routeBypass — empty hostname → passthrough", () => {
assert.equal(shim.routeBypass("", TARGETS, []), "passthrough");
assert.equal(
shim.routeBypass(undefined as unknown as string, TARGETS, []),
"passthrough"
);
});
test("routeBypass — targetHosts may be an array (not just Set)", () => {
const targetsArr = [
"daily-cloudcode-pa.googleapis.com",
"api.githubcopilot.com",
];
assert.equal(
shim.routeBypass("api.githubcopilot.com", targetsArr, []),
"target"
);
assert.equal(shim.routeBypass("example.com", targetsArr, []), "passthrough");
});
test("routeBypass — case-insensitive on hostname", () => {
assert.equal(shim.routeBypass("MyApp.Okta.COM", TARGETS, []), "bypass");
assert.equal(
shim.routeBypass(
"DAILY-cloudcode-pa.googleapis.com".toLowerCase(),
TARGETS,
[]
),
"target"
);
});
test("bypassGlobMatch — exact match (no wildcard)", () => {
assert.ok(shim.bypassGlobMatch("api.openai.com", "api.openai.com"));
assert.ok(!shim.bypassGlobMatch("api.openai.com", "api.anthropic.com"));
});
test("bypassGlobMatch — single wildcard at start", () => {
assert.ok(shim.bypassGlobMatch("foo.example.com", "*.example.com"));
assert.ok(shim.bypassGlobMatch("bar.example.com", "*.example.com"));
assert.ok(!shim.bypassGlobMatch("example.org", "*.example.com"));
});
test("bypassGlobMatch — wildcard at end", () => {
assert.ok(shim.bypassGlobMatch("api.example.com", "api.*"));
assert.ok(!shim.bypassGlobMatch("svc.example.com", "api.*"));
});
test("bypassGlobMatch — too many wildcards → rejected", () => {
// 10 wildcards → segments.length === 11, exceeds the cap.
const pat = "a*b*c*d*e*f*g*h*i*j*k";
assert.equal(shim.bypassGlobMatch("abcdefghijk", pat), false);
});
test("bypassGlobMatch — case-insensitive", () => {
assert.ok(shim.bypassGlobMatch("FOO.EXAMPLE.COM", "*.example.com"));
});
test("bypassGlobMatch — does not throw on regex-special chars in pattern", () => {
// No regex compilation happens — the helper is a linear string walk.
assert.doesNotThrow(() => shim.bypassGlobMatch("test.com", "(invalid["));
});
test("parseBypassJson — valid JSON with patterns array", () => {
const raw = JSON.stringify({
version: 1,
patterns: ["*.internal.example.com", "Custom.Host.COM"],
});
const parsed = shim.parseBypassJson(raw);
assert.deepEqual(parsed, ["*.internal.example.com", "custom.host.com"]);
});
test("parseBypassJson — empty input → []", () => {
assert.deepEqual(shim.parseBypassJson(""), []);
});
test("parseBypassJson — malformed JSON → []", () => {
assert.deepEqual(shim.parseBypassJson("not json"), []);
});
test("parseBypassJson — missing patterns property → []", () => {
assert.deepEqual(shim.parseBypassJson(JSON.stringify({ version: 1 })), []);
});
test("parseBypassJson — patterns not an array → []", () => {
assert.deepEqual(
shim.parseBypassJson(JSON.stringify({ patterns: "foo" })),
[]
);
});
test("parseBypassJson — filters out non-string and empty entries", () => {
const raw = JSON.stringify({
patterns: ["valid.com", "", null, 42, "Another.COM"],
});
const parsed = shim.parseBypassJson(raw);
assert.deepEqual(parsed, ["valid.com", "another.com"]);
});
test("C2 header contract — server.cjs intercept must inject x-omniroute-source and x-omniroute-agent", async () => {
// This is a documentation/spec assertion: the exact header names that
// server.cjs::intercept must inject per master plan §3.5. If anyone
// edits server.cjs to remove or rename them, this test fails and
// flags the regression.
const fs = await import("node:fs");
const path = await import("node:path");
const url = await import("node:url");
const here = path.dirname(url.fileURLToPath(import.meta.url));
const serverPath = path.resolve(here, "../../src/mitm/server.cjs");
const src = fs.readFileSync(serverPath, "utf-8");
assert.match(
src,
/"x-omniroute-source":\s*"agent-bridge"/,
'server.cjs must inject "x-omniroute-source: agent-bridge"'
);
assert.match(
src,
/"x-omniroute-agent":\s*agentId/,
'server.cjs must inject "x-omniroute-agent: <id>" from the host→agent map'
);
// Antigravity non-regression: the historical host must still resolve to
// the antigravity agent id, so the existing flow continues to work.
assert.match(
src,
/TARGET_HOST_AGENT\.set\(lower,\s*id\)/,
"server.cjs must populate TARGET_HOST_AGENT from targets.json"
);
assert.match(
src,
/TARGET_HOST_AGENT\.set\(h,\s*"antigravity"\)/,
"server.cjs must seed antigravity baseline in TARGET_HOST_AGENT"
);
});
test("Hard Rule #12 — server.cjs intercept error path uses sanitizeErrorMessage", async () => {
// Spec assertion: error responses must NOT leak raw err.message.
const fs = await import("node:fs");
const path = await import("node:path");
const url = await import("node:url");
const here = path.dirname(url.fileURLToPath(import.meta.url));
const serverPath = path.resolve(here, "../../src/mitm/server.cjs");
const src = fs.readFileSync(serverPath, "utf-8");
// sanitizeErrorMessage must wrap the error body.
assert.match(
src,
/sanitizeErrorMessage\(error\s*&&\s*error\.message\)/,
"intercept() error path must route through sanitizeErrorMessage()"
);
// The historical raw-leak pattern must be gone from the error body literal.
const errorBodyRegion = src.match(
/res\.end\(\s*JSON\.stringify\(\{\s*error[\s\S]*?type:\s*"mitm_error"[\s\S]*?\}\)\s*\)/
);
assert.ok(errorBodyRegion, "intercept() must build a JSON error body");
assert.doesNotMatch(
errorBodyRegion[0],
/message:\s*error\.message[^a-zA-Z_]/,
"intercept() error body must not contain raw error.message"
);
});
test("C1 contract — server.cjs registers a CONNECT handler", async () => {
const fs = await import("node:fs");
const path = await import("node:path");
const url = await import("node:url");
const here = path.dirname(url.fileURLToPath(import.meta.url));
const serverPath = path.resolve(here, "../../src/mitm/server.cjs");
const src = fs.readFileSync(serverPath, "utf-8");
assert.match(
src,
/server\.on\(\s*"connect"/,
"server.cjs must register a CONNECT handler"
);
assert.match(
src,
/net\.connect\(/,
"server.cjs must dial upstream via net.connect for bypass/passthrough"
);
assert.match(
src,
/HTTP\/1\.1\s+200\s+Connection Established/,
"server.cjs CONNECT path must reply with 200 Connection Established"
);
});