Compare commits

...

1 Commits

Author SHA1 Message Date
Vincent Koc
486d285c24 fix(gateway): advertise exec approval node commands 2026-05-30 12:31:06 +02:00
3 changed files with 77 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ import {
isNodeCommandAllowed,
normalizeDeclaredNodeCommands,
resolveNodeCommandAllowlist,
resolveNodePairingCommandAllowlist,
} from "./node-command-policy.js";
describe("gateway/node-command-policy", () => {
@@ -139,12 +140,34 @@ describe("gateway/node-command-policy", () => {
expect(allowlist.has("system.run")).toBe(false);
expect(allowlist.has("system.run.prepare")).toBe(false);
expect(allowlist.has("system.which")).toBe(false);
expect(allowlist.has("system.execApprovals.get")).toBe(false);
expect(allowlist.has("system.execApprovals.set")).toBe(false);
expect(allowlist.has("browser.proxy")).toBe(false);
expect(allowlist.has("screen.snapshot")).toBe(false);
expect(allowlist.has("system.notify")).toBe(true);
}
});
it("allows exec approval commands only through desktop node pairing approval", () => {
const cfg = {} as OpenClawConfig;
const desktopNode = { platform: "windows", deviceFamily: "Windows" };
const pairingAllowlist = resolveNodePairingCommandAllowlist(cfg, desktopNode);
expect(pairingAllowlist.has("system.execApprovals.get")).toBe(true);
expect(pairingAllowlist.has("system.execApprovals.set")).toBe(true);
const unapprovedRuntimeAllowlist = resolveNodeCommandAllowlist(cfg, desktopNode);
expect(unapprovedRuntimeAllowlist.has("system.execApprovals.get")).toBe(false);
expect(unapprovedRuntimeAllowlist.has("system.execApprovals.set")).toBe(false);
const approvedRuntimeAllowlist = resolveNodeCommandAllowlist(cfg, {
...desktopNode,
approvedCommands: ["system.execApprovals.get", "system.execApprovals.set"],
});
expect(approvedRuntimeAllowlist.has("system.execApprovals.get")).toBe(true);
expect(approvedRuntimeAllowlist.has("system.execApprovals.set")).toBe(true);
});
it("keeps defaults for first-party native platform labels with matching families", () => {
const cfg = {} as OpenClawConfig;

View File

@@ -1,6 +1,7 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
NODE_BROWSER_PROXY_COMMAND,
NODE_EXEC_APPROVALS_COMMANDS,
NODE_SYSTEM_NOTIFY_COMMAND,
NODE_SYSTEM_RUN_COMMANDS,
} from "../infra/node-commands.js";
@@ -47,11 +48,13 @@ const IOS_SYSTEM_COMMANDS = [NODE_SYSTEM_NOTIFY_COMMAND];
const SYSTEM_COMMANDS = [
...NODE_SYSTEM_RUN_COMMANDS,
...NODE_EXEC_APPROVALS_COMMANDS,
NODE_SYSTEM_NOTIFY_COMMAND,
NODE_BROWSER_PROXY_COMMAND,
];
const DESKTOP_HOST_COMMANDS = new Set<string>([
...NODE_SYSTEM_RUN_COMMANDS,
...NODE_EXEC_APPROVALS_COMMANDS,
NODE_BROWSER_PROXY_COMMAND,
...SCREEN_COMMANDS,
]);

View File

@@ -105,6 +105,57 @@ describe("reconcileNodePairingOnConnect", () => {
);
});
it("preserves Windows exec approval commands in the paired node surface", async () => {
const requestPairing = vi.fn(async (input: NodePairingRequestInput) => ({
status: "pending" as const,
request: { ...input, requestId: "req-windows", ts: 1 },
created: true,
}));
const result = await reconcileNodePairingOnConnect({
cfg: {} as never,
connectParams: makeNodeConnectParams({
client: {
id: GATEWAY_CLIENT_IDS.NODE_HOST,
version: "test",
platform: "windows",
deviceFamily: "Windows",
mode: GATEWAY_CLIENT_MODES.NODE,
},
caps: ["system"],
commands: [
"system.run.prepare",
"system.run",
"system.which",
"system.execApprovals.get",
"system.execApprovals.set",
],
}),
pairedNode: null,
requestPairing,
});
expect(result.declaredCommands).toEqual([
"system.run.prepare",
"system.run",
"system.which",
"system.execApprovals.get",
"system.execApprovals.set",
]);
expect(result.effectiveCommands).toEqual([]);
expect(requestPairing).toHaveBeenCalledWith(
expect.objectContaining({
commands: [
"system.run.prepare",
"system.run",
"system.which",
"system.execApprovals.get",
"system.execApprovals.set",
],
}),
);
});
it.each([
["conflicts with device family", { deviceFamily: "iPhone" }],
["omits device family", {}],