import { describe, expect, it } from "vitest"; import { findManagedProxyRuntimeMutationLines, findManagedProxyRuntimeMutations, isAllowedManagedProxyRuntimeMutation, } from "../../scripts/check-managed-proxy-runtime-mutation.mjs"; describe("check-managed-proxy-runtime-mutation", () => { it("finds assignments and deletes for proxy env vars", () => { const source = ` process.env.HTTP_PROXY = "http://proxy"; process.env["HTTPS_PROXY"] = "http://proxy"; delete process.env.NO_PROXY; delete process.env["GLOBAL_AGENT_NO_PROXY"]; `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4, 5]); }); it("finds global object alias GLOBAL_AGENT mutations", () => { const source = ` const globalRecord = global; const agent = globalRecord.GLOBAL_AGENT; globalRecord.GLOBAL_AGENT = {}; globalRecord["GLOBAL_AGENT"] = {}; delete globalRecord.GLOBAL_AGENT; delete globalRecord["GLOBAL_AGENT"]; agent.HTTP_PROXY = "http://proxy"; `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([4, 5, 6, 7, 8]); }); it("finds GLOBAL_AGENT mutations", () => { const source = ` global.GLOBAL_AGENT = {}; global.GLOBAL_AGENT.NO_PROXY = "localhost"; global["GLOBAL_AGENT"].HTTP_PROXY = "http://proxy"; delete global.GLOBAL_AGENT.HTTPS_PROXY; `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4, 5]); }); it("finds Object.assign and Object.defineProperty mutations", () => { const source = ` Object.assign(global.GLOBAL_AGENT, { NO_PROXY: "localhost" }); Object.assign(process.env, { NO_PROXY: "localhost" }); Object.defineProperty(process.env, "HTTP_PROXY", { value: "http://proxy" }); `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4]); }); it("finds missing managed-proxy env key mutations", () => { const source = ` process.env.GLOBAL_AGENT_FORCE_GLOBAL_AGENT = "true"; process.env.OPENCLAW_PROXY_LOOPBACK_MODE = "gateway-only"; `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3]); }); it("finds defineProperty mutations with constant proxy keys", () => { const source = ` const proxyKey = "HTTP_PROXY"; const agentKey = "NO_PROXY"; Object.defineProperty(process.env, proxyKey, { value: "http://proxy" }); Object.defineProperty(global.GLOBAL_AGENT, agentKey, { value: "localhost" }); `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([4, 5]); }); it("finds destructured process.env alias mutations", () => { const source = ` const { env } = process; env.HTTP_PROXY = "http://proxy"; env["NO_PROXY"] = "localhost"; `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([3, 4]); }); it("finds process.env alias and constant key mutations", () => { const source = ` const env = process.env; const proxyKey = "HTTP_PROXY"; env.HTTPS_PROXY = "http://proxy"; env[proxyKey] = "http://proxy"; delete env.NO_PROXY; Object.assign(env, { GLOBAL_AGENT_HTTP_PROXY: "http://proxy" }); Object.defineProperty(env, "OPENCLAW_PROXY_ACTIVE", { value: "1" }); `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([4, 5, 6, 7, 8]); }); it("finds dynamic process.env key mutations from forbidden key arrays", () => { const source = ` const proxyKeys = ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"]; for (const key of proxyKeys) { process.env[key] = "http://proxy"; } for (const key of proxyKeys) { delete process.env[key]; } `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([4, 7]); }); it("finds dynamic process.env key mutations from spread-built forbidden key arrays", () => { const source = ` const lower = ["http_proxy", "https_proxy"]; const upper = ["HTTP_PROXY", "HTTPS_PROXY"]; const all = [...lower, ...upper, "OPENCLAW_PROXY_LOOPBACK_MODE"]; for (const key of all) { delete process.env[key]; } `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([6]); }); it("ignores dynamic process.env key mutations from unrelated key arrays", () => { const source = ` const normalKeys = ["PATH", "HOME"]; for (const key of normalKeys) { process.env[key] = "value"; } `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([]); }); it("finds GLOBAL_AGENT alias mutations", () => { const source = ` const agent = global.GLOBAL_AGENT; agent.HTTP_PROXY = "http://proxy"; agent["NO_PROXY"] = "localhost"; delete agent.HTTPS_PROXY; `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([3, 4, 5]); }); it("finds globalThis.GLOBAL_AGENT mutations alongside global.GLOBAL_AGENT", () => { const source = ` globalThis.GLOBAL_AGENT = {}; globalThis.GLOBAL_AGENT.NO_PROXY = "localhost"; globalThis["GLOBAL_AGENT"].HTTP_PROXY = "http://proxy"; delete globalThis.GLOBAL_AGENT.HTTPS_PROXY; `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4, 5]); }); it('finds process["env"] mixed access mutations', () => { const source = ` process["env"].HTTP_PROXY = "http://proxy"; process["env"]["HTTPS_PROXY"] = "http://proxy"; delete process["env"].NO_PROXY; `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4]); }); it("does not flag Object.assign on a non-process .env namespace", () => { const source = ` Object.assign(config.env, { NO_PROXY: "localhost" }); Object.defineProperty(config.env, "HTTP_PROXY", { value: "http://proxy" }); `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([]); }); it("ignores reads, unrelated env vars, comments, and strings", () => { const source = ` const current = process.env.HTTP_PROXY; process.env.PATH = "/usr/bin"; const text = "process.env.NO_PROXY = '*'"; // global.GLOBAL_AGENT.NO_PROXY = '*'; `; expect(findManagedProxyRuntimeMutationLines(source)).toEqual([]); }); it("reports the enclosing owner scope for each mutation", () => { const source = ` function applyProxyEnv() { process.env.HTTP_PROXY = "http://proxy"; } class NoProxyLeaseManager { release() { delete process.env.NO_PROXY; } } const updateProxy = () => { global.GLOBAL_AGENT.NO_PROXY = "localhost"; }; `; expect(findManagedProxyRuntimeMutations(source)).toEqual([ { line: 3, scope: "applyProxyEnv" }, { line: 8, scope: "NoProxyLeaseManager.release" }, { line: 13, scope: "updateProxy" }, ]); }); it("allows approved owner scopes without exact line allowlists", () => { expect( isAllowedManagedProxyRuntimeMutation({ relativePath: "src/infra/net/proxy/proxy-lifecycle.ts", scope: "applyProxyEnv", }), ).toBe(true); expect( isAllowedManagedProxyRuntimeMutation({ relativePath: "extensions/browser/src/browser/cdp-proxy-bypass.ts", scope: "NoProxyLeaseManager.release", }), ).toBe(true); expect( isAllowedManagedProxyRuntimeMutation({ relativePath: "src/infra/net/proxy/proxy-lifecycle.ts", scope: "startProxy", }), ).toBe(false); }); });