mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-23 15:37:44 +00:00
262 lines
9.3 KiB
TypeScript
262 lines
9.3 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { expectDefined } from "@openclaw/normalization-core";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { parse } from "yaml";
|
|
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
|
|
|
const workflow = parse(readFileSync(".github/workflows/codeql-critical-quality.yml", "utf8")) as {
|
|
jobs: Record<string, { steps: { id?: string; name: string; run?: string }[] }>;
|
|
};
|
|
const steps = [
|
|
...expectDefined(workflow.jobs["quality-shards"], "CodeQL shard selection job").steps,
|
|
...expectDefined(workflow.jobs["network-runtime-boundary"], "network CodeQL job").steps,
|
|
];
|
|
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
|
const qaOwner = "extensions/qa-lab/src/gateway-child-setup.ts";
|
|
const codexTransport = "extensions/codex/src/app-server/transport-websocket.ts";
|
|
|
|
function runStep(name: string, root: string, env: Record<string, string>) {
|
|
const script = steps.find((step) => step.id === name || step.name === name)?.run;
|
|
if (!script) {
|
|
throw new Error(`Missing workflow script: ${name}`);
|
|
}
|
|
return spawnSync("bash", ["--noprofile", "--norc", "-c", script], {
|
|
cwd: root,
|
|
encoding: "utf8",
|
|
env: { PATH: process.env.PATH, HOME: root, TMPDIR: root, ...env },
|
|
timeout: 10_000,
|
|
});
|
|
}
|
|
|
|
function scan(
|
|
files: { filename: string; patch?: string | null }[],
|
|
failCall = 0,
|
|
response?: string,
|
|
step = "network-diff-scan",
|
|
) {
|
|
const root = tempDirs.make("codeql-network-scan-");
|
|
const bin = path.join(root, "bin");
|
|
mkdirSync(bin);
|
|
writeFileSync(path.join(root, "files.json"), response ?? JSON.stringify(files));
|
|
writeFileSync(path.join(root, "calls"), "0");
|
|
const output = path.join(root, "output");
|
|
writeFileSync(output, "");
|
|
// Execute the workflow's own jq filters, replacing only the GitHub API boundary.
|
|
writeFileSync(
|
|
path.join(bin, "gh"),
|
|
`#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
[[ "$1" == api && "$2" == --paginate && "$3" == "repos/openclaw/openclaw/pulls/123/files?per_page=100" ]]
|
|
call=$(( $(cat calls) + 1 ))
|
|
echo "$call" > calls
|
|
if [[ "$call" == "$FAIL_CALL" ]]; then
|
|
echo "synthetic GitHub fetch failure" >&2
|
|
exit 7
|
|
fi
|
|
if [[ "$#" == 3 ]]; then
|
|
cat files.json
|
|
else
|
|
[[ "$#" == 5 && "$4" == --jq ]]
|
|
exec jq -r "$5" files.json
|
|
fi
|
|
`,
|
|
{ mode: 0o755 },
|
|
);
|
|
const result = runStep(step, root, {
|
|
PATH: `${bin}${path.delimiter}${process.env.PATH}`,
|
|
PR_NUMBER: "123",
|
|
REPOSITORY: "openclaw/openclaw",
|
|
GITHUB_OUTPUT: output,
|
|
FAIL_CALL: String(failCall),
|
|
EVENT_NAME: "pull_request",
|
|
});
|
|
return {
|
|
result,
|
|
output: readFileSync(output, "utf8"),
|
|
calls: Number(readFileSync(path.join(root, "calls"), "utf8")),
|
|
};
|
|
}
|
|
|
|
describe("network CodeQL PR routing", () => {
|
|
it("selects the network shard for the exec-host socket owner", () => {
|
|
const { result, output } = scan(
|
|
[{ filename: "src/infra/exec-host.ts" }],
|
|
0,
|
|
undefined,
|
|
"detect",
|
|
);
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(output.split("\n")).toContain("network_runtime=true");
|
|
});
|
|
|
|
it.each([
|
|
["tests/network-runtime/packages/net-policy/src/client.ts", true],
|
|
["tests/unrelated/example.qlref", false],
|
|
["tests/network-runtime-extra/example.qlref", false],
|
|
])("selects the network shard only for its own semantic fixtures: %s", (file, selected) => {
|
|
const { result, output } = scan(
|
|
[{ filename: `.github/codeql/openclaw-boundary/${file}` }],
|
|
0,
|
|
undefined,
|
|
"detect",
|
|
);
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(output.split("\n")).toContain(`network_runtime=${selected}`);
|
|
});
|
|
|
|
it.each([
|
|
["ordinary network change", qaOwner, "+const ready = true;", false],
|
|
["unrelated source", "src/example.ts", '+import net from "node:net";', false],
|
|
[
|
|
"test-only import",
|
|
"extensions/qa-lab/src/example.e2e.test.tsx",
|
|
'+import net from "node:net";',
|
|
false,
|
|
],
|
|
["removed import", qaOwner, '-import net from "node:net";', false],
|
|
["net import", qaOwner, '+import net from "node:net";', true],
|
|
["tls require", "extensions/irc/src/client.ts", '+const tls = require("tls");', true],
|
|
["http2 import", "src/infra/push-apns-http2.ts", '+import http2 from "node:http2";', true],
|
|
["raw connection", "src/infra/net/client.ts", "+net.createConnection(options);", true],
|
|
["socket constructor", "src/infra/exec-host.ts", "+new Socket();", true],
|
|
[
|
|
"transport path as data",
|
|
"packages/net-policy/src/client.ts",
|
|
`+net.connect(options); // ${codexTransport}: `,
|
|
true,
|
|
],
|
|
[
|
|
"unrelated query",
|
|
".github/codeql/openclaw-boundary/queries/example.ql",
|
|
"+select example",
|
|
false,
|
|
],
|
|
[
|
|
"similar query name",
|
|
".github/codeql/openclaw-boundary/queries/raw-socket-callsite-classification.ql.bak",
|
|
"+select example",
|
|
false,
|
|
],
|
|
])("routes %s", (_name, filename, patch, fullCodeql) => {
|
|
const { result, output } = scan([{ filename, patch }]);
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(output.trim()).toBe(`full_codeql=${fullCodeql}`);
|
|
});
|
|
|
|
it.each([undefined, null])("routes unavailable patches (%s) by source scope", (patch) => {
|
|
for (const [filename, fullCodeql] of [
|
|
[qaOwner, true],
|
|
["packages/net-policy/src/client.ts", true],
|
|
["src/infra/net/client.ts", true],
|
|
["src/infra/exec-host.ts", true],
|
|
["extensions/qa-lab/src/example.e2e.test.tsx", false],
|
|
["src/infra/net/client.test.ts", false],
|
|
["src/example.ts", false],
|
|
] as const) {
|
|
const { result, output } = scan([{ filename, ...(patch === undefined ? {} : { patch }) }]);
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(output.trim(), filename).toBe(`full_codeql=${fullCodeql}`);
|
|
}
|
|
});
|
|
|
|
it.each(["network-diff-scan", "detect"])("consumes one paginated snapshot in %s", (step) => {
|
|
const response = `${JSON.stringify([{ filename: "src/example.ts", patch: "+const ok = true;" }])}\n${JSON.stringify([{ filename: qaOwner, patch: null }])}`;
|
|
const { result, output, calls } = scan([], 0, response, step);
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(calls).toBe(1);
|
|
expect(output.split("\n")).toContain(
|
|
step === "detect" ? "network_runtime=true" : "full_codeql=true",
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
"HTTP_PROXY",
|
|
"HTTPS_PROXY",
|
|
"NO_PROXY",
|
|
"GLOBAL_AGENT_HTTP_PROXY",
|
|
"OPENCLAW_PROXY_ACTIVE",
|
|
])("escalates added %s references", (key) => {
|
|
const { result, output } = scan([
|
|
{ filename: "src/infra/net/proxy/proxy-lifecycle.ts", patch: `+process.env.${key} = value;` },
|
|
]);
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(output.trim()).toBe("full_codeql=true");
|
|
});
|
|
|
|
it.each([
|
|
".github/codeql/codeql-network-runtime-boundary-critical-quality.yml",
|
|
".github/codeql/openclaw-boundary/queries/raw-socket-callsite-classification.ql",
|
|
".github/codeql/openclaw-boundary/queries/managed-proxy-runtime-mutation.ql",
|
|
".github/codeql/openclaw-boundary/tests/network-runtime/raw-socket-callsite-classification.qlref",
|
|
codexTransport,
|
|
])("always escalates contract owner %s", (filename) => {
|
|
const { result, output } = scan([{ filename }]);
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(output.trim()).toBe("full_codeql=true");
|
|
});
|
|
|
|
it("keeps semantic escalation when a contract edit also adds proxy policy tokens", () => {
|
|
const { result, output } = scan([
|
|
{ filename: codexTransport, patch: "+process.env.HTTP_PROXY = value;" },
|
|
]);
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(output.trim()).toBe("full_codeql=true");
|
|
});
|
|
|
|
it.each(["network-diff-scan", "detect"])(
|
|
"fails closed when GitHub metadata fails in %s",
|
|
(step) => {
|
|
const { result, output } = scan(
|
|
[{ filename: qaOwner, patch: "+const ready = true;" }],
|
|
1,
|
|
undefined,
|
|
step,
|
|
);
|
|
expect(result.status).toBe(7);
|
|
expect(result.stderr).toContain("synthetic GitHub fetch failure");
|
|
expect(output).toBe("");
|
|
},
|
|
);
|
|
|
|
it.each(["network-diff-scan", "detect"])("fails closed on malformed metadata in %s", (step) => {
|
|
const { result, output } = scan([], 0, "{", step);
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("parse error");
|
|
expect(output).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("network CodeQL findings gate", () => {
|
|
it.each([
|
|
["clean SARIF", JSON.stringify({ runs: [{ results: [] }] }), true],
|
|
[
|
|
"error finding",
|
|
JSON.stringify({
|
|
runs: [{ results: [{ level: "error", message: { text: "unclassified socket" } }] }],
|
|
}),
|
|
false,
|
|
],
|
|
[
|
|
"warning finding",
|
|
JSON.stringify({
|
|
runs: [{ results: [{ level: "warning", message: { text: "proxy mutation" } }] }],
|
|
}),
|
|
false,
|
|
],
|
|
["missing SARIF", undefined, false],
|
|
["invalid SARIF", "{", false],
|
|
])("enforces %s", (_name, sarif, succeeds) => {
|
|
const root = tempDirs.make("codeql-network-findings-");
|
|
if (sarif !== undefined) {
|
|
writeFileSync(path.join(root, "result.sarif"), sarif);
|
|
}
|
|
const result = runStep("Fail on network runtime boundary findings", root, {
|
|
SARIF_OUTPUT: root,
|
|
});
|
|
expect(result.error).toBeUndefined();
|
|
expect(result.status === 0, result.stderr).toBe(succeeds);
|
|
});
|
|
});
|