import { afterEach, beforeEach, expect, test } from "@effect/vitest"; import { loadConfig } from "./config"; import executorConfig from "../executor.config"; const ENV_NAME = "EXECUTOR_ALLOW_STDIO_MCP"; const SECRET_ENV_NAME = "EXECUTOR_SECRET_KEY"; const TTL_ENV_NAME = "EXECUTOR_TOOLS_SYNC_TTL_MS"; const originalValue = process.env[ENV_NAME]; const originalSecret = process.env[SECRET_ENV_NAME]; const originalTtl = process.env[TTL_ENV_NAME]; const RATE_LIMIT_ENV_NAME = "EXECUTOR_DISABLE_AUTH_RATE_LIMIT"; const originalRateLimit = process.env[RATE_LIMIT_ENV_NAME]; const PROXY_HEADER_ENV_NAME = "EXECUTOR_TRUSTED_PROXY_HEADER"; const PROXIES_ENV_NAME = "EXECUTOR_TRUSTED_PROXIES"; const originalProxyHeader = process.env[PROXY_HEADER_ENV_NAME]; const originalProxies = process.env[PROXIES_ENV_NAME]; beforeEach(() => { process.env[SECRET_ENV_NAME] = originalSecret ?? "executor-config-test-secret"; }); afterEach(() => { if (originalValue === undefined) { delete process.env[ENV_NAME]; } else { process.env[ENV_NAME] = originalValue; } if (originalSecret === undefined) { delete process.env[SECRET_ENV_NAME]; } else { process.env[SECRET_ENV_NAME] = originalSecret; } if (originalTtl === undefined) { delete process.env[TTL_ENV_NAME]; } else { process.env[TTL_ENV_NAME] = originalTtl; } if (originalRateLimit === undefined) { delete process.env[RATE_LIMIT_ENV_NAME]; } else { process.env[RATE_LIMIT_ENV_NAME] = originalRateLimit; } if (originalProxyHeader === undefined) { delete process.env[PROXY_HEADER_ENV_NAME]; } else { process.env[PROXY_HEADER_ENV_NAME] = originalProxyHeader; } if (originalProxies === undefined) { delete process.env[PROXIES_ENV_NAME]; } else { process.env[PROXIES_ENV_NAME] = originalProxies; } }); const allowStdio = (): boolean => { const mcp = executorConfig.plugins().find((plugin) => plugin.id === "mcp"); expect(mcp).toBeDefined(); const clientConfig = mcp?.clientConfig; if ( clientConfig && typeof clientConfig === "object" && "allowStdio" in clientConfig && typeof clientConfig.allowStdio === "boolean" ) { return clientConfig.allowStdio; } expect.fail("MCP plugin did not expose its stdio setting"); return false; }; test("stdio MCP stays disabled when the opt-in is absent", () => { delete process.env[ENV_NAME]; expect(allowStdio()).toBe(false); }); test("stdio MCP stays disabled unless the opt-in is exactly true", () => { process.env[ENV_NAME] = "false"; expect(allowStdio()).toBe(false); process.env[ENV_NAME] = "TRUE"; expect(allowStdio()).toBe(false); }); test("stdio MCP is enabled when the opt-in is exactly true", () => { process.env[ENV_NAME] = "true"; expect(allowStdio()).toBe(true); }); test("an unset tools-sync TTL leaves the SDK default in place", () => { delete process.env[TTL_ENV_NAME]; expect(loadConfig().toolsSyncTtlMs).toBeUndefined(); process.env[TTL_ENV_NAME] = " "; expect(loadConfig().toolsSyncTtlMs).toBeUndefined(); }); test("a positive tools-sync TTL is forwarded verbatim", () => { process.env[TTL_ENV_NAME] = "60000"; expect(loadConfig().toolsSyncTtlMs).toBe(60000); }); // 0 keeps the SDK's own meaning — every catalog is expired on every read — // so the env var never means the opposite of the config field it feeds. test("a zero tools-sync TTL forwards as the SDK's always-stale 0", () => { process.env[TTL_ENV_NAME] = "0"; expect(loadConfig().toolsSyncTtlMs).toBe(0); }); // Case-insensitive: the disable tokens are operator intent, not a keyword, and // "OFF" typed in a systemd unit means what "off" means in a .env file. test.each(["off", "null", "false", "OFF", "Null", "FALSE", " Off "])( "the tools-sync TTL is disabled by %s", (raw) => { process.env[TTL_ENV_NAME] = raw; expect(loadConfig().toolsSyncTtlMs).toBeNull(); }, ); // A typo'd knob must not silently degrade into the 15-minute default; the // operator finds out at boot instead of wondering why catalogs never refresh. // "9007199254740993" and "1e30" are whole numbers that no longer round-trip // through a double — accepting them would boot a TTL the operator never wrote. test.each(["abc", "60_000", "1.5", "1e3ms", "NaN", "Infinity", "9007199254740993", "1e30"])( "a malformed tools-sync TTL (%s) refuses to boot", (raw) => { process.env[TTL_ENV_NAME] = raw; expect(() => loadConfig()).toThrow(/EXECUTOR_TOOLS_SYNC_TTL_MS/); }, ); test("a negative tools-sync TTL refuses to boot", () => { process.env[TTL_ENV_NAME] = "-1"; expect(() => loadConfig()).toThrow(/must not be negative/); }); test("auth rate limiting stays on unless the opt-out is exactly true", () => { delete process.env[RATE_LIMIT_ENV_NAME]; expect(loadConfig().authRateLimit).toBe(true); process.env[RATE_LIMIT_ENV_NAME] = "TRUE"; expect(loadConfig().authRateLimit).toBe(true); }); test("auth rate limiting is off when the opt-out is exactly true", () => { process.env[RATE_LIMIT_ENV_NAME] = "true"; expect(loadConfig().authRateLimit).toBe(false); }); test("no trusted proxy is configured by default", () => { delete process.env[PROXY_HEADER_ENV_NAME]; delete process.env[PROXIES_ENV_NAME]; expect(loadConfig().trustedProxy).toBeUndefined(); process.env[PROXY_HEADER_ENV_NAME] = " "; process.env[PROXIES_ENV_NAME] = " , "; expect(loadConfig().trustedProxy).toBeUndefined(); }); test("a trusted proxy header and address list are parsed together", () => { process.env[PROXY_HEADER_ENV_NAME] = " CF-Connecting-IP "; process.env[PROXIES_ENV_NAME] = "10.0.0.0/8, 192.0.2.10 ,2001:db8::/32"; expect(loadConfig().trustedProxy).toEqual({ header: "cf-connecting-ip", proxies: ["10.0.0.0/8", "192.0.2.10", "2001:db8::/32"], }); }); // A header with no proxy addresses would be honoured from anyone who can reach // the container; addresses with no header name nothing. Both refuse to boot. test("a trusted proxy header without addresses refuses to boot", () => { process.env[PROXY_HEADER_ENV_NAME] = "x-real-ip"; delete process.env[PROXIES_ENV_NAME]; expect(() => loadConfig()).toThrow(/must be set together/); }); test("trusted proxy addresses without a header refuse to boot", () => { delete process.env[PROXY_HEADER_ENV_NAME]; process.env[PROXIES_ENV_NAME] = "10.0.0.0/8"; expect(() => loadConfig()).toThrow(/must be set together/); }); test.each(["x real ip", "x-real-ip:", "x(real)ip"])( "a malformed trusted proxy header (%j) refuses to boot", (raw) => { process.env[PROXY_HEADER_ENV_NAME] = raw; process.env[PROXIES_ENV_NAME] = "10.0.0.0/8"; expect(() => loadConfig()).toThrow(/EXECUTOR_TRUSTED_PROXY_HEADER/); }, ); test("the server-stamped header cannot be named as the proxy header", () => { process.env[PROXY_HEADER_ENV_NAME] = "X-Executor-Client-IP"; process.env[PROXIES_ENV_NAME] = "10.0.0.0/8"; expect(() => loadConfig()).toThrow(/must not be "x-executor-client-ip"/); }); // Better Auth would only warn and skip a bad entry, silently leaving every // user in one bucket; refuse at boot and name the entry instead. test.each(["proxy.example.com", "10.0.0.0/33", "10.0.0/8", "*"])( "a malformed trusted proxy address (%j) refuses to boot", (raw) => { process.env[PROXY_HEADER_ENV_NAME] = "x-real-ip"; process.env[PROXIES_ENV_NAME] = `10.0.0.0/8,${raw}`; expect(() => loadConfig()).toThrow(`EXECUTOR_TRUSTED_PROXIES contains ${JSON.stringify(raw)}`); }, );