Skip to content

Commit 486d285

Browse files
committed
fix(gateway): advertise exec approval node commands
1 parent e7ef051 commit 486d285

3 files changed

Lines changed: 77 additions & 0 deletions

File tree

src/gateway/node-command-policy.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
isNodeCommandAllowed,
1212
normalizeDeclaredNodeCommands,
1313
resolveNodeCommandAllowlist,
14+
resolveNodePairingCommandAllowlist,
1415
} from "./node-command-policy.js";
1516

1617
describe("gateway/node-command-policy", () => {
@@ -139,12 +140,34 @@ describe("gateway/node-command-policy", () => {
139140
expect(allowlist.has("system.run")).toBe(false);
140141
expect(allowlist.has("system.run.prepare")).toBe(false);
141142
expect(allowlist.has("system.which")).toBe(false);
143+
expect(allowlist.has("system.execApprovals.get")).toBe(false);
144+
expect(allowlist.has("system.execApprovals.set")).toBe(false);
142145
expect(allowlist.has("browser.proxy")).toBe(false);
143146
expect(allowlist.has("screen.snapshot")).toBe(false);
144147
expect(allowlist.has("system.notify")).toBe(true);
145148
}
146149
});
147150

151+
it("allows exec approval commands only through desktop node pairing approval", () => {
152+
const cfg = {} as OpenClawConfig;
153+
const desktopNode = { platform: "windows", deviceFamily: "Windows" };
154+
155+
const pairingAllowlist = resolveNodePairingCommandAllowlist(cfg, desktopNode);
156+
expect(pairingAllowlist.has("system.execApprovals.get")).toBe(true);
157+
expect(pairingAllowlist.has("system.execApprovals.set")).toBe(true);
158+
159+
const unapprovedRuntimeAllowlist = resolveNodeCommandAllowlist(cfg, desktopNode);
160+
expect(unapprovedRuntimeAllowlist.has("system.execApprovals.get")).toBe(false);
161+
expect(unapprovedRuntimeAllowlist.has("system.execApprovals.set")).toBe(false);
162+
163+
const approvedRuntimeAllowlist = resolveNodeCommandAllowlist(cfg, {
164+
...desktopNode,
165+
approvedCommands: ["system.execApprovals.get", "system.execApprovals.set"],
166+
});
167+
expect(approvedRuntimeAllowlist.has("system.execApprovals.get")).toBe(true);
168+
expect(approvedRuntimeAllowlist.has("system.execApprovals.set")).toBe(true);
169+
});
170+
148171
it("keeps defaults for first-party native platform labels with matching families", () => {
149172
const cfg = {} as OpenClawConfig;
150173

src/gateway/node-command-policy.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { OpenClawConfig } from "../config/types.openclaw.js";
22
import {
33
NODE_BROWSER_PROXY_COMMAND,
4+
NODE_EXEC_APPROVALS_COMMANDS,
45
NODE_SYSTEM_NOTIFY_COMMAND,
56
NODE_SYSTEM_RUN_COMMANDS,
67
} from "../infra/node-commands.js";
@@ -47,11 +48,13 @@ const IOS_SYSTEM_COMMANDS = [NODE_SYSTEM_NOTIFY_COMMAND];
4748

4849
const SYSTEM_COMMANDS = [
4950
...NODE_SYSTEM_RUN_COMMANDS,
51+
...NODE_EXEC_APPROVALS_COMMANDS,
5052
NODE_SYSTEM_NOTIFY_COMMAND,
5153
NODE_BROWSER_PROXY_COMMAND,
5254
];
5355
const DESKTOP_HOST_COMMANDS = new Set<string>([
5456
...NODE_SYSTEM_RUN_COMMANDS,
57+
...NODE_EXEC_APPROVALS_COMMANDS,
5558
NODE_BROWSER_PROXY_COMMAND,
5659
...SCREEN_COMMANDS,
5760
]);

src/gateway/node-connect-reconcile.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,57 @@ describe("reconcileNodePairingOnConnect", () => {
105105
);
106106
});
107107

108+
it("preserves Windows exec approval commands in the paired node surface", async () => {
109+
const requestPairing = vi.fn(async (input: NodePairingRequestInput) => ({
110+
status: "pending" as const,
111+
request: { ...input, requestId: "req-windows", ts: 1 },
112+
created: true,
113+
}));
114+
115+
const result = await reconcileNodePairingOnConnect({
116+
cfg: {} as never,
117+
connectParams: makeNodeConnectParams({
118+
client: {
119+
id: GATEWAY_CLIENT_IDS.NODE_HOST,
120+
version: "test",
121+
platform: "windows",
122+
deviceFamily: "Windows",
123+
mode: GATEWAY_CLIENT_MODES.NODE,
124+
},
125+
caps: ["system"],
126+
commands: [
127+
"system.run.prepare",
128+
"system.run",
129+
"system.which",
130+
"system.execApprovals.get",
131+
"system.execApprovals.set",
132+
],
133+
}),
134+
pairedNode: null,
135+
requestPairing,
136+
});
137+
138+
expect(result.declaredCommands).toEqual([
139+
"system.run.prepare",
140+
"system.run",
141+
"system.which",
142+
"system.execApprovals.get",
143+
"system.execApprovals.set",
144+
]);
145+
expect(result.effectiveCommands).toEqual([]);
146+
expect(requestPairing).toHaveBeenCalledWith(
147+
expect.objectContaining({
148+
commands: [
149+
"system.run.prepare",
150+
"system.run",
151+
"system.which",
152+
"system.execApprovals.get",
153+
"system.execApprovals.set",
154+
],
155+
}),
156+
);
157+
});
158+
108159
it.each([
109160
["conflicts with device family", { deviceFamily: "iPhone" }],
110161
["omits device family", {}],

0 commit comments

Comments
 (0)