Skip to content

fix(exec): fail closed on Windows shell wrappers in allowlist mode#67655

Open
plgonzalezrx8 wants to merge 2 commits intoopenclaw:mainfrom
plgonzalezrx8:fix-allowlist-bypass-for-powershell-wrappers-upstream-2026-04-16
Open

fix(exec): fail closed on Windows shell wrappers in allowlist mode#67655
plgonzalezrx8 wants to merge 2 commits intoopenclaw:mainfrom
plgonzalezrx8:fix-allowlist-bypass-for-powershell-wrappers-upstream-2026-04-16

Conversation

@plgonzalezrx8
Copy link
Copy Markdown
Contributor

@plgonzalezrx8 plgonzalezrx8 commented Apr 16, 2026

Issue

In Windows allowlist mode, the wrapper guard only failed closed for cmd.exe-style invocations. That left a gap where other shell-wrapper transports such as powershell -Command could still be analyzed like a normal allowlisted command, which is the wrong security posture for inline shell payloads and command chaining.

Fix

  • block any Windows shell-wrapper transport in allowlist mode when shellWrapperInvocation is true, instead of limiting the guard to cmdInvocation
  • update the denial guidance so it explicitly calls out powershell -Command alongside cmd.exe /c
  • add regression coverage for the Windows PowerShell-wrapper path in src/node-host/exec-policy.test.ts

How to test

  • run: pnpm test src/node-host/exec-policy.test.ts src/node-host/invoke-system-run.test.ts -t "PowerShell|shell wrapper|cmd.exe wrappers|allowlist"
  • verify the new PowerShell-wrapper regression covers isWindows: true, shellWrapperInvocation: true, and cmdInvocation: false
  • confirm the decision is denied, windowsShellWrapperBlocked is set, and the error guidance mentions powershell -Command

@plgonzalezrx8 plgonzalezrx8 changed the title fix(exec): block Windows shell wrappers from allowlist bypass fix(exec): fail closed on Windows shell wrappers in allowlist mode Apr 16, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 16, 2026

Greptile Summary

This PR closes an allowlist-bypass for Windows shell wrappers other than cmd.exe (e.g. PowerShell) by removing the cmdInvocation guard from windowsShellWrapperBlocked, and updates the denial message to mention powershell -Command.

  • The new regression test for the PowerShell path is correct, but the existing formatSystemRunAllowlistMissMessage unit test (lines 67–74 of exec-policy.test.ts) was not updated: it asserts .toContain(\"Windows shell wrappers like cmd.exe /c require approval\"), which is no longer a substring of the broadened message, so this test will fail as submitted.

Confidence Score: 4/5

Not safe to merge as-is — a stale test assertion will fail in CI.

The security fix itself is correct and the new PowerShell regression test is well written, but the existing formatSystemRunAllowlistMissMessage test was left with a substring that no longer appears in the updated message, which will cause a test failure. That P1 must be fixed before landing.

src/node-host/exec-policy.test.ts lines 67–74 — stale .toContain() assertion needs to match the new message text.

Comments Outside Diff (2)

  1. src/node-host/exec-policy.test.ts, line 67-74 (link)

    P1 Stale assertion will fail after message change

    The .toContain("Windows shell wrappers like cmd.exe /c require approval") assertion no longer matches because exec-policy.ts line 38-39 now inserts " or powershell -Command" between "cmd.exe /c" and " require approval", breaking the substring match. This test will fail as written.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/node-host/exec-policy.test.ts
    Line: 67-74
    
    Comment:
    **Stale assertion will fail after message change**
    
    The `.toContain("Windows shell wrappers like cmd.exe /c require approval")` assertion no longer matches because `exec-policy.ts` line 38-39 now inserts `" or powershell -Command"` between `"cmd.exe /c"` and `" require approval"`, breaking the substring match. This test will fail as written.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. src/node-host/exec-policy.ts, line 61 (link)

    P2 cmdInvocation is now an unused parameter

    After dropping params.cmdInvocation from the windowsShellWrapperBlocked expression, this required parameter has no effect on the function's output. If the intent is to preserve the public API surface (callers still populate it), add a brief comment explaining that; otherwise remove it and update call sites to avoid dead code and potential lint warnings from no-unused-vars.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/node-host/exec-policy.ts
    Line: 61
    
    Comment:
    **`cmdInvocation` is now an unused parameter**
    
    After dropping `params.cmdInvocation` from the `windowsShellWrapperBlocked` expression, this required parameter has no effect on the function's output. If the intent is to preserve the public API surface (callers still populate it), add a brief comment explaining that; otherwise remove it and update call sites to avoid dead code and potential lint warnings from `no-unused-vars`.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/node-host/exec-policy.test.ts
Line: 67-74

Comment:
**Stale assertion will fail after message change**

The `.toContain("Windows shell wrappers like cmd.exe /c require approval")` assertion no longer matches because `exec-policy.ts` line 38-39 now inserts `" or powershell -Command"` between `"cmd.exe /c"` and `" require approval"`, breaking the substring match. This test will fail as written.

```suggestion
  it("adds Windows shell-wrapper guidance when blocked by Windows shell wrapper policy", () => {
    expect(
      formatSystemRunAllowlistMissMessage({
        shellWrapperBlocked: true,
        windowsShellWrapperBlocked: true,
      }),
    ).toContain("Windows shell wrappers like cmd.exe /c or powershell -Command require approval");
  });
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/node-host/exec-policy.ts
Line: 61

Comment:
**`cmdInvocation` is now an unused parameter**

After dropping `params.cmdInvocation` from the `windowsShellWrapperBlocked` expression, this required parameter has no effect on the function's output. If the intent is to preserve the public API surface (callers still populate it), add a brief comment explaining that; otherwise remove it and update call sites to avoid dead code and potential lint warnings from `no-unused-vars`.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(exec): block Windows shell wrappers ..." | Re-trigger Greptile

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d523e57e62

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node-host/exec-policy.ts Outdated
return (
"SYSTEM_RUN_DENIED: allowlist miss " +
"(Windows shell wrappers like cmd.exe /c require approval; " +
"(Windows shell wrappers like cmd.exe /c or powershell -Command require approval; " +
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve message compatibility with existing allowlist tests

The updated denial text in formatSystemRunAllowlistMissMessage inserts or powershell -Command between cmd.exe /c and require approval, which no longer matches the existing assertions that still expect the old contiguous phrase (src/node-host/exec-policy.test.ts:67-74 and src/node-host/invoke-system-run.test.ts:25-31). This commit therefore introduces deterministic test regressions unless the dependent checks are updated in the same change or the message keeps a backward-compatible substring.

Useful? React with 👍 / 👎.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 27, 2026

Codex review: needs real behavior proof before merge.

Summary
This PR broadens node-host exec allowlist policy to deny all Windows shell-wrapper transports, adds PowerShell/pwsh denial copy and regression coverage, and updates node docs plus changelog.

Reproducibility: yes. Current main is source-reproducible by evaluating allowlist policy with isWindows=true, shellWrapperInvocation=true, and cmdInvocation=false; the guard still requires cmdInvocation today, so the PowerShell wrapper path is not blocked there.

Real behavior proof
Needs real behavior proof before merge: The PR body and maintainer update provide test/build/check evidence, but no redacted Windows/node runtime output, screenshot, recording, or log showing the changed behavior after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
The remaining blocker is human/contributor-provided real behavior proof and exact-head CI completion, not a narrow code repair for ClawSweeper to make.

Security
Cleared: The diff tightens exec allowlist behavior and does not add CI, dependency, package-resolution, install-script, secret-handling, or supply-chain risk.

Review details

Best possible solution:

Land the prepared fail-closed Windows shell-wrapper policy after adding redacted real runtime proof and letting the exact-head CI complete cleanly.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main is source-reproducible by evaluating allowlist policy with isWindows=true, shellWrapperInvocation=true, and cmdInvocation=false; the guard still requires cmdInvocation today, so the PowerShell wrapper path is not blocked there.

Is this the best way to solve the issue?

Yes for the code direction. The prepared head is narrow and aligns policy, denial copy, docs, changelog, and regression coverage, but merge should wait for real behavior proof and completed CI.

What I checked:

  • Current main guard is cmd-specific: Current main only sets windowsShellWrapperBlocked when security is allowlist, shellWrapperInvocation is true, isWindows is true, and cmdInvocation is true, so a PowerShell wrapper path with cmdInvocation=false is not fail-closed by this policy. (src/node-host/exec-policy.ts:68, 86a9b3fcb1c0)
  • PowerShell wrappers are recognized upstream: The shell-wrapper resolver includes powershell and pwsh wrappers and extracts PowerShell inline commands through POWERSHELL_INLINE_COMMAND_FLAGS, so the policy can receive shellWrapperInvocation=true for non-cmd Windows wrappers. (src/infra/shell-wrapper-resolution.ts:20, 86a9b3fcb1c0)
  • Invoke path separates wrapper and cmd facts: The invoke path derives cmdInvocation from isCmdExeInvocation but passes shellWrapperInvocation from parsed.shellPayload, which supports the current-main source reproduction with cmdInvocation=false and shellWrapperInvocation=true. (src/node-host/invoke-system-run.ts:424, 86a9b3fcb1c0)
  • Prepared head broadens the guard: The prepared head changes windowsShellWrapperBlocked to depend on allowlist security, shellWrapperInvocation, and isWindows, removing the cmdInvocation requirement while preserving POSIX behavior comments. (src/node-host/exec-policy.ts:68, 3baf42a36bcd)
  • Prepared head adds regression coverage: The prepared head updates the Windows denial-copy assertion and adds a PowerShell wrapper policy test with isWindows=true, cmdInvocation=false, and shellWrapperInvocation=true. (src/node-host/exec-policy.test.ts:150, 3baf42a36bcd)
  • Prepared head updates docs and changelog: The prepared head updates node docs/troubleshooting to mention powershell/pwsh wrapper approval and adds the required user-facing changelog fix entry with contributor attribution. (CHANGELOG.md:190, 3baf42a36bcd)

Likely related people:

  • steipete: Recent commits touched exec policy and invoke routing, including shell-wrapper allowlist security hardening and node-host export refactors around this surface. (role: recent maintainer and security-policy owner; confidence: high; commits: 3f0b9dbb36c8, 076fa5eae6e1, bd0e10a2f68f; files: src/node-host/exec-policy.ts, src/node-host/invoke-system-run.ts)
  • ngutman: Authored the recent fix that aligned node shell allowlist wrappers, directly adjacent to this PR’s policy behavior. (role: prior exec allowlist contributor; confidence: medium; commits: d008e2d01529; files: src/node-host/exec-policy.ts)
  • vincentkoc: Authored the recent remote approval regression fix touching exec policy semantics and has recent node-doc maintenance history. (role: exec approval and node docs maintainer; confidence: medium; commits: 2d53ffdec1da, d71518b1eb9d; files: src/node-host/exec-policy.ts, docs/nodes/troubleshooting.md)
  • pgondhi987: Authored recent shell-wrapper parsing and approval-binding hardening in the shared shell-wrapper resolver used by this policy path. (role: adjacent shell-wrapper hardening contributor; confidence: medium; commits: fc065b2693bf; files: src/infra/shell-wrapper-resolution.ts)

Remaining risk / open question:

  • The PR still lacks after-fix real behavior proof from a Windows node/exec setup; the exact head’s Real behavior proof check is failing.
  • Several CI check runs for the prepared head were still queued during this review, so merge should wait for the exact head to finish required checks.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 86a9b3fcb1c0.

Re-review progress:

@BradGroux BradGroux force-pushed the fix-allowlist-bypass-for-powershell-wrappers-upstream-2026-04-16 branch from d523e57 to 3baf42a Compare May 8, 2026 17:31
@BradGroux
Copy link
Copy Markdown
Member

Maintainer update: rebased this onto current main and prepared the branch at 3baf42a36bcdef3a304f90e741fe13df09f03685.

What changed in the prepared head:

  • Kept the fail-closed Windows shell-wrapper allowlist behavior.
  • Aligned denial copy and tests for cmd.exe /c, powershell, and pwsh -Command wrapper runs.
  • Updated node docs/troubleshooting guidance for the broadened Windows wrapper approval behavior.
  • Added the required changelog entry with contributor attribution.

Verification run locally:

  • pnpm test -- src/node-host/exec-policy.test.ts src/node-host/invoke-system-run.test.ts -t "PowerShell|shell wrapper|cmd.exe wrappers|allowlist" (13 tests passed, 26 skipped by filter)
  • pnpm build
  • pnpm check

Waiting on fresh CI/ClawSweeper for this prepared head before merge.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation plugin: file-transfer triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation plugin: file-transfer size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants