mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-17 01:40:44 +00:00
lint: classify raw socket callsites
This commit is contained in:
184
test/scripts/check-managed-proxy-runtime-mutation.test.ts
Normal file
184
test/scripts/check-managed-proxy-runtime-mutation.test.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { findManagedProxyRuntimeMutationLines } 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([]);
|
||||
});
|
||||
});
|
||||
192
test/scripts/check-raw-socket-callsite-classification.test.ts
Normal file
192
test/scripts/check-raw-socket-callsite-classification.test.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { findRawSocketClientCallLines } from "../../scripts/check-raw-socket-callsite-classification.mjs";
|
||||
|
||||
describe("check-raw-socket-callsite-classification", () => {
|
||||
it("finds raw net, tls, and http2 client calls", () => {
|
||||
const source = `
|
||||
import net from "node:net";
|
||||
import * as tls from "node:tls";
|
||||
import http2 from "node:http2";
|
||||
net.connect({ host: "example.com", port: 6667 });
|
||||
tls.connect({ host: "example.com", port: 6697 });
|
||||
http2.connect("https://api.example.com");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
|
||||
});
|
||||
|
||||
it("ignores comments, strings, and unrelated connect methods", () => {
|
||||
const source = `
|
||||
// net.connect({ host: "example.com" });
|
||||
const text = "tls.connect({ host: 'example.com' })";
|
||||
client.connect(transport);
|
||||
websocket.connect();
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([]);
|
||||
});
|
||||
|
||||
it("handles aliased imports, requires, and dynamic literal imports", () => {
|
||||
const source = `
|
||||
import * as rawNet from "node:net";
|
||||
const rawTls = require("node:tls");
|
||||
const rawHttp2 = await import("node:http2");
|
||||
rawNet.connect({ host: "127.0.0.1", port: 1 });
|
||||
rawTls.connect({ host: "127.0.0.1", port: 1 });
|
||||
rawHttp2.connect("https://example.com");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
|
||||
});
|
||||
|
||||
it("finds element-access raw socket calls", () => {
|
||||
const source = `
|
||||
import net from "node:net";
|
||||
import tls from "node:tls";
|
||||
import http2 from "node:http2";
|
||||
net["connect"]({ host: "127.0.0.1", port: 1 });
|
||||
tls["createConnection"]({ host: "127.0.0.1", port: 1 });
|
||||
http2["connect"]("https://example.com");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
|
||||
});
|
||||
|
||||
it("finds destructured dynamic-import default raw module aliases", () => {
|
||||
const source = `
|
||||
const { default: rawNet } = await import("node:net");
|
||||
const { default: rawTls } = await import("node:tls");
|
||||
const { default: rawHttp2 } = await import("node:http2");
|
||||
rawNet.connect({ host: "127.0.0.1", port: 1 });
|
||||
rawTls.connect({ host: "127.0.0.1", port: 1 });
|
||||
rawHttp2.connect("https://example.com");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
|
||||
});
|
||||
|
||||
it("finds direct raw module receiver calls", () => {
|
||||
const source = `
|
||||
require("node:net").connect({ host: "127.0.0.1", port: 1 });
|
||||
require("node:tls").createConnection({ host: "127.0.0.1", port: 1 });
|
||||
(await import("node:http2")).connect("https://example.com");
|
||||
(await import("node:net")).default.connect({ host: "127.0.0.1", port: 1 });
|
||||
(await import("node:tls")).default.createConnection({ host: "127.0.0.1", port: 1 });
|
||||
(await import("node:http2")).default.connect("https://example.com");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([2, 3, 4, 5, 6, 7]);
|
||||
});
|
||||
|
||||
it("finds named default raw module imports", () => {
|
||||
const source = `
|
||||
import { default as rawNet } from "node:net";
|
||||
import { default as rawTls } from "node:tls";
|
||||
import { default as rawHttp2 } from "node:http2";
|
||||
rawNet.connect({ host: "127.0.0.1", port: 1 });
|
||||
rawTls.connect({ host: "127.0.0.1", port: 1 });
|
||||
rawHttp2.connect("https://example.com");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
|
||||
});
|
||||
|
||||
it("finds raw socket module object aliases", () => {
|
||||
const source = `
|
||||
import net from "node:net";
|
||||
import tls from "node:tls";
|
||||
import http2 from "node:http2";
|
||||
const rawNet = net;
|
||||
const rawTls = tls;
|
||||
const rawHttp2 = http2;
|
||||
rawNet.connect({ host: "127.0.0.1", port: 1 });
|
||||
rawTls.connect({ host: "127.0.0.1", port: 1 });
|
||||
rawHttp2.connect("https://example.com");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([8, 9, 10]);
|
||||
});
|
||||
|
||||
it("finds aliases to raw socket module members", () => {
|
||||
const source = `
|
||||
import net from "node:net";
|
||||
import tls from "node:tls";
|
||||
import http2 from "node:http2";
|
||||
const netConnect = net.connect;
|
||||
const tlsConnect = tls.connect;
|
||||
const h2Connect = http2.connect;
|
||||
const Socket = net.Socket;
|
||||
const { createConnection } = net;
|
||||
netConnect({ host: "127.0.0.1", port: 1 });
|
||||
tlsConnect({ host: "127.0.0.1", port: 1 });
|
||||
h2Connect("https://example.com");
|
||||
createConnection({ host: "127.0.0.1", port: 1 });
|
||||
new Socket().connect("/tmp/socket");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([10, 11, 12, 13, 14]);
|
||||
});
|
||||
|
||||
it("finds destructured require and dynamic import raw socket bindings", () => {
|
||||
const source = `
|
||||
const { connect, createConnection, Socket } = require("node:net");
|
||||
const { connect: tlsConnect } = await import("node:tls");
|
||||
connect({ host: "127.0.0.1", port: 1 });
|
||||
createConnection({ host: "127.0.0.1", port: 1 });
|
||||
tlsConnect({ host: "127.0.0.1", port: 1 });
|
||||
new Socket().connect("/tmp/socket");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([4, 5, 6, 7]);
|
||||
});
|
||||
|
||||
it("finds stored Socket instance connect calls", () => {
|
||||
const source = `
|
||||
import net from "node:net";
|
||||
const client = new net.Socket();
|
||||
client.connect("/tmp/socket");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([4]);
|
||||
});
|
||||
|
||||
it("finds named raw socket imports", () => {
|
||||
const source = `
|
||||
import { connect as netConnect, createConnection, Socket } from "node:net";
|
||||
import { connect as tlsConnect } from "node:tls";
|
||||
import { connect as http2Connect } from "node:http2";
|
||||
netConnect({ host: "127.0.0.1", port: 1 });
|
||||
createConnection({ host: "127.0.0.1", port: 1 });
|
||||
tlsConnect({ host: "127.0.0.1", port: 1 });
|
||||
http2Connect("https://example.com");
|
||||
new Socket().connect("/tmp/socket");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7, 8, 9]);
|
||||
});
|
||||
|
||||
it("finds createConnection and constructed Socket.connect client calls", () => {
|
||||
const source = `
|
||||
import net from "node:net";
|
||||
import tls from "node:tls";
|
||||
net.createConnection({ host: "127.0.0.1", port: 1 });
|
||||
tls.createConnection({ host: "127.0.0.1", port: 1 });
|
||||
new net.Socket().connect("/tmp/socket");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([4, 5, 6]);
|
||||
});
|
||||
|
||||
it("handles parenthesized and asserted module identifiers", () => {
|
||||
const source = `
|
||||
import net from "node:net";
|
||||
import tls from "node:tls";
|
||||
import http2 from "node:http2";
|
||||
(net as typeof import("node:net")).connect({ host: "127.0.0.1", port: 1 });
|
||||
(tls as typeof import("node:tls")).connect({ host: "127.0.0.1", port: 1 });
|
||||
(http2 as typeof import("node:http2")).connect("https://example.com");
|
||||
`;
|
||||
|
||||
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
|
||||
});
|
||||
});
|
||||
@@ -30,6 +30,22 @@ describe("run-additional-boundary-checks", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("runs managed proxy runtime mutation guard in CI", () => {
|
||||
expect(BOUNDARY_CHECKS).toContainEqual({
|
||||
label: "lint:tmp:managed-proxy-runtime-mutation",
|
||||
command: "pnpm",
|
||||
args: ["run", "lint:tmp:managed-proxy-runtime-mutation"],
|
||||
});
|
||||
});
|
||||
|
||||
it("runs raw socket classification guard in CI", () => {
|
||||
expect(BOUNDARY_CHECKS).toContainEqual({
|
||||
label: "lint:tmp:raw-socket-classification",
|
||||
command: "pnpm",
|
||||
args: ["run", "lint:tmp:raw-socket-classification"],
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes concurrency input", () => {
|
||||
expect(resolveConcurrency("6")).toBe(6);
|
||||
expect(resolveConcurrency("0")).toBe(4);
|
||||
|
||||
Reference in New Issue
Block a user