From 1d1757b16f48f1a93cd16ab0ad7e2c3c63ce727d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 7 Mar 2026 23:15:37 +0000 Subject: [PATCH] fix(exec): recognize PowerShell encoded commands --- CHANGELOG.md | 1 + src/infra/shell-inline-command.ts | 9 ++++++++- src/infra/system-run-command.test.ts | 6 ++++++ src/node-host/invoke-system-run.test.ts | 11 +++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f41716a5..f501696e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -300,6 +300,7 @@ Docs: https://docs.openclaw.ai - Agents/model fallback visibility: warn when configured model IDs cannot be resolved and fallback is applied, with log-safe sanitization of model text to prevent control-sequence injection in warning output. (#39215) Thanks @ademczuk. - Outbound delivery replay safety: use two-phase delivery ACK markers (`.json` -> `.delivered` -> unlink) and startup marker cleanup so crash windows between send and cleanup do not replay already-delivered messages. (#38668) Thanks @Gundam98. - Nodes/system.run approval binding: carry prepared approval plans through gateway forwarding and bind interpreter-style script operands across approval to execution, so post-approval script rewrites are denied while unchanged approved script runs keep working. Thanks @tdjackey for reporting. +- Nodes/system.run PowerShell wrapper parsing: treat `pwsh`/`powershell` `-EncodedCommand` forms as shell-wrapper payloads so allowlist mode still requires approval instead of falling back to plain argv analysis. Thanks @tdjackey for reporting. ## 2026.3.2 diff --git a/src/infra/shell-inline-command.ts b/src/infra/shell-inline-command.ts index 2d6f8ae77..9e0f33627 100644 --- a/src/infra/shell-inline-command.ts +++ b/src/infra/shell-inline-command.ts @@ -1,5 +1,12 @@ export const POSIX_INLINE_COMMAND_FLAGS = new Set(["-lc", "-c", "--command"]); -export const POWERSHELL_INLINE_COMMAND_FLAGS = new Set(["-c", "-command", "--command"]); +export const POWERSHELL_INLINE_COMMAND_FLAGS = new Set([ + "-c", + "-command", + "--command", + "-encodedcommand", + "-enc", + "-e", +]); export function resolveInlineCommandMatch( argv: string[], diff --git a/src/infra/system-run-command.test.ts b/src/infra/system-run-command.test.ts index 7f7d4fee9..83d64533b 100644 --- a/src/infra/system-run-command.test.ts +++ b/src/infra/system-run-command.test.ts @@ -59,6 +59,12 @@ describe("system run command helpers", () => { test("extractShellCommandFromArgv supports fish and pwsh wrappers", () => { expect(extractShellCommandFromArgv(["fish", "-c", "echo hi"])).toBe("echo hi"); expect(extractShellCommandFromArgv(["pwsh", "-Command", "Get-Date"])).toBe("Get-Date"); + expect(extractShellCommandFromArgv(["pwsh", "-EncodedCommand", "ZQBjAGgAbwA="])).toBe( + "ZQBjAGgAbwA=", + ); + expect(extractShellCommandFromArgv(["powershell", "-enc", "ZQBjAGgAbwA="])).toBe( + "ZQBjAGgAbwA=", + ); }); test("extractShellCommandFromArgv unwraps busybox/toybox shell applets", () => { diff --git a/src/node-host/invoke-system-run.test.ts b/src/node-host/invoke-system-run.test.ts index 75f894aca..3134629af 100644 --- a/src/node-host/invoke-system-run.test.ts +++ b/src/node-host/invoke-system-run.test.ts @@ -847,6 +847,17 @@ describe("handleSystemRunInvoke mac app exec host routing", () => { } }); + 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("denies nested env shell payloads when wrapper depth is exceeded", async () => { if (process.platform === "win32") { return;