test: merge system run denial matrices

This commit is contained in:
Peter Steinberger
2026-04-20 23:26:13 +01:00
parent 11eae6b2d8
commit b07c40a5a8

View File

@@ -617,33 +617,91 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
},
);
it("handles transparent env wrappers in allowlist mode", async () => {
const { runCommand, sendInvokeResult } = await runSystemInvoke({
it("handles transparent and semantic env wrappers in allowlist mode", async () => {
const transparent = await runSystemInvoke({
preferMacAppExecHost: false,
security: "allowlist",
command: ["env", "tr", "a", "b"],
});
if (process.platform === "win32") {
expect(runCommand).not.toHaveBeenCalled();
expectInvokeErrorMessage(sendInvokeResult, { message: "allowlist miss" });
return;
expect(transparent.runCommand).not.toHaveBeenCalled();
expectInvokeErrorMessage(transparent.sendInvokeResult, { message: "allowlist miss" });
} else {
const runArgs = vi.mocked(transparent.runCommand).mock.calls[0]?.[0] as string[] | undefined;
expect(runArgs).toBeDefined();
expect(runArgs?.[0]).toMatch(/(^|[/\\])tr$/);
expect(runArgs?.slice(1)).toEqual(["a", "b"]);
expectInvokeOk(transparent.sendInvokeResult);
}
const runArgs = vi.mocked(runCommand).mock.calls[0]?.[0] as string[] | undefined;
expect(runArgs).toBeDefined();
expect(runArgs?.[0]).toMatch(/(^|[/\\])tr$/);
expect(runArgs?.slice(1)).toEqual(["a", "b"]);
expectInvokeOk(sendInvokeResult);
});
it("denies semantic env wrappers in allowlist mode", async () => {
const { runCommand, sendInvokeResult } = await runSystemInvoke({
const semantic = await runSystemInvoke({
preferMacAppExecHost: false,
security: "allowlist",
command: ["env", "FOO=bar", "tr", "a", "b"],
});
expect(runCommand).not.toHaveBeenCalled();
expectInvokeErrorMessage(sendInvokeResult, { message: "allowlist miss" });
expect(semantic.runCommand).not.toHaveBeenCalled();
expectInvokeErrorMessage(semantic.sendInvokeResult, { message: "allowlist miss" });
});
it("denies shell payload carriers in allowlist mode without explicit approval", async () => {
const shellPayloadCases: Array<
| {
label: string;
command: string[];
ask?: "off" | "on-miss";
message: string;
approvalRequired?: false;
}
| {
label: string;
command: string[];
ask?: "off" | "on-miss";
approvalRequired: true;
}
> = [
{
label: "env -S",
command: ["env", "-S", 'sh -c "echo pwned"'],
message: "allowlist miss",
ask: "off",
},
{
label: "semicolon chain simple command",
command:
process.platform === "win32"
? ["cmd.exe", "/d", "/s", "/c", "openclaw status; id"]
: ["/bin/sh", "-lc", "openclaw status; id"],
approvalRequired: true,
},
{
label: "semicolon chain path read",
command:
process.platform === "win32"
? ["cmd.exe", "/d", "/s", "/c", "openclaw status; cat /etc/passwd"]
: ["/bin/sh", "-lc", "openclaw status; cat /etc/passwd"],
approvalRequired: true,
},
{
label: "PowerShell encoded command",
command: ["pwsh", "-EncodedCommand", "ZQBjAGgAbwAgAHAAdwBuAGUAZAA="],
approvalRequired: true,
},
];
for (const testCase of shellPayloadCases) {
const { runCommand, sendInvokeResult, sendNodeEvent } = await runSystemInvoke({
preferMacAppExecHost: false,
security: "allowlist",
ask: testCase.ask ?? "on-miss",
command: testCase.command,
});
expect(runCommand, testCase.label).not.toHaveBeenCalled();
if (testCase.approvalRequired) {
expectApprovalRequiredDenied({ sendNodeEvent, sendInvokeResult });
} else if ("message" in testCase) {
expectInvokeErrorMessage(sendInvokeResult, { message: testCase.message });
}
}
});
it.runIf(process.platform !== "win32")(
@@ -1087,48 +1145,6 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
expectApprovalRequiredDenied({ sendNodeEvent, sendInvokeResult });
});
it("denies env -S shell payloads in allowlist mode", async () => {
const { runCommand, sendInvokeResult } = await runSystemInvoke({
preferMacAppExecHost: false,
security: "allowlist",
command: ["env", "-S", 'sh -c "echo pwned"'],
});
expect(runCommand).not.toHaveBeenCalled();
expectInvokeErrorMessage(sendInvokeResult, { message: "allowlist miss" });
});
it("denies semicolon-chained shell payloads in allowlist mode without explicit approval", async () => {
const payloads = ["openclaw status; id", "openclaw status; cat /etc/passwd"];
for (const payload of payloads) {
const command =
process.platform === "win32"
? ["cmd.exe", "/d", "/s", "/c", payload]
: ["/bin/sh", "-lc", payload];
const { runCommand, sendInvokeResult } = await runSystemInvoke({
preferMacAppExecHost: false,
security: "allowlist",
ask: "on-miss",
command,
});
expect(runCommand, payload).not.toHaveBeenCalled();
expectInvokeErrorMessage(sendInvokeResult, {
message: "SYSTEM_RUN_DENIED: approval required",
exact: true,
});
}
});
it("denies PowerShell encoded-command payloads in allowlist mode without explicit approval", async () => {
const { runCommand, sendInvokeResult, sendNodeEvent } = await runSystemInvoke({
preferMacAppExecHost: false,
security: "allowlist",
ask: "on-miss",
command: ["pwsh", "-EncodedCommand", "ZQBjAGgAbwAgAHAAdwBuAGUAZAA="],
});
expect(runCommand).not.toHaveBeenCalled();
expectApprovalRequiredDenied({ sendNodeEvent, sendInvokeResult });
});
it("rejects unsafe environment inputs before execution", async () => {
const shellCommand =
process.platform === "win32"
@@ -1537,100 +1553,68 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
it("keeps cmd.exe transport wrappers approval-gated on Windows", async () => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const tempDir = createFixtureDir("openclaw-cmd-wrapper-allow-");
try {
const scriptPath = path.join(tempDir, "check_mail.cmd");
fs.writeFileSync(scriptPath, "@echo off\r\necho ok\r\n");
await withTempApprovalsHome({
approvals: createAllowlistOnMissApprovals({
agents: {
main: {
allowlist: [{ pattern: scriptPath }],
},
},
}),
run: async () => {
const invoke = await runSystemInvoke({
preferMacAppExecHost: false,
command: ["cmd.exe", "/d", "/s", "/c", `${scriptPath} --limit 5`],
cwd: tempDir,
security: "allowlist",
ask: "on-miss",
isCmdExeInvocation: (argv) => {
const token = argv[0]?.trim();
if (!token) {
return false;
}
const base = path.win32.basename(token).toLowerCase();
return base === "cmd.exe" || base === "cmd";
},
});
expect(invoke.runCommand).not.toHaveBeenCalled();
expectApprovalRequiredDenied({
sendNodeEvent: invoke.sendNodeEvent,
sendInvokeResult: invoke.sendInvokeResult,
});
for (const testCase of [
{
name: "direct cmd.exe",
commandPrefix: ["cmd.exe", "/d", "/s", "/c"],
},
});
} finally {
platformSpy.mockRestore();
}
});
it.each([
{
name: "keeps env cmd.exe transport wrappers approval-gated on Windows",
command: ["env", "cmd.exe", "/d", "/s", "/c"],
},
{
name: "keeps env-assignment cmd.exe transport wrappers approval-gated on Windows",
command: ["env", "FOO=bar", "cmd.exe", "/d", "/s", "/c"],
},
])("$name", async ({ command }) => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const tempDir = createFixtureDir("openclaw-env-cmd-wrapper-allow-");
try {
const scriptPath = path.join(tempDir, "check_mail.cmd");
fs.writeFileSync(scriptPath, "@echo off\r\necho ok\r\n");
const wrappedCommand = [...command, `${scriptPath} --limit 5`];
await withTempApprovalsHome({
approvals: createAllowlistOnMissApprovals({
agents: {
main: {
allowlist: [{ pattern: scriptPath }],
},
},
}),
run: async () => {
const seenArgv: string[][] = [];
const invoke = await runSystemInvoke({
preferMacAppExecHost: false,
command: wrappedCommand,
cwd: tempDir,
security: "allowlist",
ask: "on-miss",
isCmdExeInvocation: (argv) => {
seenArgv.push([...argv]);
const token = argv[0]?.trim();
if (!token) {
return false;
}
const base = path.win32.basename(token).toLowerCase();
return base === "cmd.exe" || base === "cmd";
},
});
expect(seenArgv).toContainEqual(["cmd.exe", "/d", "/s", "/c", `${scriptPath} --limit 5`]);
expect(invoke.runCommand).not.toHaveBeenCalled();
expectApprovalRequiredDenied({
sendNodeEvent: invoke.sendNodeEvent,
sendInvokeResult: invoke.sendInvokeResult,
});
{
name: "env cmd.exe",
commandPrefix: ["env", "cmd.exe", "/d", "/s", "/c"],
},
});
{
name: "env-assignment cmd.exe",
commandPrefix: ["env", "FOO=bar", "cmd.exe", "/d", "/s", "/c"],
},
]) {
const tempDir = createFixtureDir("openclaw-cmd-wrapper-allow-");
const scriptPath = path.join(tempDir, "check_mail.cmd");
fs.writeFileSync(scriptPath, "@echo off\r\necho ok\r\n");
const command = [...testCase.commandPrefix, `${scriptPath} --limit 5`];
await withTempApprovalsHome({
approvals: createAllowlistOnMissApprovals({
agents: {
main: {
allowlist: [{ pattern: scriptPath }],
},
},
}),
run: async () => {
const seenArgv: string[][] = [];
const invoke = await runSystemInvoke({
preferMacAppExecHost: false,
command,
cwd: tempDir,
security: "allowlist",
ask: "on-miss",
isCmdExeInvocation: (argv) => {
seenArgv.push([...argv]);
const token = argv[0]?.trim();
if (!token) {
return false;
}
const base = path.win32.basename(token).toLowerCase();
return base === "cmd.exe" || base === "cmd";
},
});
expect(seenArgv, testCase.name).toContainEqual([
"cmd.exe",
"/d",
"/s",
"/c",
`${scriptPath} --limit 5`,
]);
expect(invoke.runCommand, testCase.name).not.toHaveBeenCalled();
expectApprovalRequiredDenied({
sendNodeEvent: invoke.sendNodeEvent,
sendInvokeResult: invoke.sendInvokeResult,
});
},
});
}
} finally {
platformSpy.mockRestore();
}