Skip to content

fix(exec): PowerShell /c and bash -l -c can be saved as allow-always rules - #1511

Open
SebTardif wants to merge 19 commits into
openclaw:mainfrom
SebTardif:fix/f108-inline-shell-allow-rules
Open

SebTardif wants to merge 19 commits into
openclaw:mainfrom
SebTardif:fix/f108-inline-shell-allow-rules

Conversation

@SebTardif

@SebTardif SebTardif commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes: Allow always can save PowerShell /c, Bash -l -c and combined -ec inline commands as reusable rules, allowing later commands to run without a fresh prompt.

User Impact

Intended impact: inline commands stay one-time, while direct programs, direct scripts and Bash noclobber -C remain reusable. Previously saved rules for newly recognized inline commands remain on disk but no longer authorize those runs; operators must approve them individually.

October 1 assessment at c470a0c2: HOLD_FOR_AUTHOR for current reproduced regressions, not the old September 30 literal cases. The long WorkingDirectory/File-value case, positional pwsh scripts, Command prefixes and literal fish init commands are repaired. Double-dash value aliases and -of still hide inline pwsh commands; explicit Windows PowerShell -fi/-fil scripts are newly misclassified. The affected Windows PowerShell positional branch also still misses a sole command-string argument.

Why This Change Was Made

The classifier is the existing reusable-command binder's shell eligibility gate. Correct classification enforces the already-documented one-time shell policy without changing stored records. The literal Bash cluster, post-script and explicit PowerShell File cases identified on September 25 are addressed in this head.

This head repairs those three classifier holes. A value of -WorkingDirectory named -File is an operand, so a later -c stays inline. A positional script stops switch scanning, so pwsh script.ps1 /c value stays a direct script. fish -C and fish --init-command stay init commands. bash -C stays noclobber.

Follow-up 53014a78c4b4c9116117e9d9121ca99b63d5374b also consumes PowerShell working-directory aliases and unique prefixes before the file check. pwsh -wd -File -c, -wo -File -c, and -wor -File -c stay inline. A real directory followed by -File script.ps1 stays a script. Documented value aliases -w and -ep consume their operand the same way.

Evidence

Frozen contributor head assessed: c470a0c2ae53047b764704f48de26d33c7ad8aac. It is source-identical to c562cad9956f7924d30a9b242f715ee2ac2992c8; the last commit only retriggered CI.

Normal unpushed candidate 799090aa4a6fd1ede0b51479a383b195d1ce0926 integrates this author head with main f4122a8927e7cd452d166a23c0d5e30299f94368. Classifier and tests match the frozen author head exactly. Original e2d175c0 and earlier unpushed integration 6559a590 are preserved in local history; their September 30 evidence is historical, not proof for this update. No contributor push, force-push, supersession or merge was performed.

Native PowerShell 5.1/7 and Git Bash probes re-ran the exact prior cases and the new alias/File-prefix cases. The repaired literals pass; pwsh --if Text -c, --wd <owned-directory> -c and -of Text -c execute inline but bind, while Windows PowerShell -fi/-fil with a script argument executes a direct script but returns ShellWrapper.

The authoritative current-project identical-prompt pair returned actual model metadata claude-opus-5.5 and gpt-6-astra (long_context). Both identify the alias regression, lone Windows PowerShell positional-command gap and File-prefix compatibility regression. No nested reviewers, model fallback or extra panel ran. Each accepted issue was checked against native host behavior and the live binder/normalizer/matcher/runner chain.

Historical September 30 autoreview at e2d175c0 exited 1 for the File-value and implicit-script cases. Those literal cases were rechecked and repaired in this update; that old verdict is not presented as a current review. The current direct pair and native reproductions still do not clear source.

Change Type

  • Bug fix
  • Feature
  • Refactor
  • Docs or instructions
  • Tests or validation
  • Security hardening
  • Chore or infrastructure

Scope

  • Tray or WinUI UX
  • Windows node capability
  • Local MCP or winnode
  • Gateway, connection, or pairing
  • Setup or onboarding
  • Permissions, privacy, or security
  • Tests, CI, or docs

Required proof pools

  • windows-wsl-mxc: exec approval eligibility controls system.run; non-skipping MXC Gateway-to-node proof is mandatory.
  • windows-wsl-gateway-e2e: prove saved-rule denial before process I/O and an allowed direct-command positive control through the real Gateway.
  • windows-winui-interactive: verify the changed Allow always availability and operator-visible one-time/denial outcome in an isolated current-head app.

These declarations replace the invalid prior none declaration. They schedule proof, not claim execution. No shared lifecycle lane was reserved or started because source review did not clear.

Validation

October 1 current-candidate floor: 799090aa, frozen author c470a0c2 integrated with main f4122a89. Test projects were built/restored before the no-restore runs. Process-local task-owned C: TEMP/TMP and isolated tray data were used; no global environment, source ACL or unrelated process was modified.

Exact command Current integration result
.\build.ps1 Passed, exit 0
dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore Passed 4252, Failed 0, Skipped 33, Total 4285
dotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore Passed 3859, Failed 0, Skipped 0
dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore --filter "FullyQualifiedName~ExecApprovalV2NormalizationTests|FullyQualifiedName~ExecReusableCommandBinderTests|FullyQualifiedName~ExecApprovalsCoordinatorTests|FullyQualifiedName~SystemCapabilityV2|FullyQualifiedName~ExecArgPatternTests" Passed 345, Failed 0, Skipped 0
.\scripts\validate-mxc-e2e.ps1 without -AllowSkip Not run: current source blockers; no shared fixture reservation or launch

No local suite failure occurred in this floor, including no global-MCP capture or FileStream failure. The immediate prior hosted c562 run 36906744667 failed LocalAiApiCredentialStoreTests.ConcurrentCreationConvergesOnOneCredential with an IOException on its own credential fixture file; CI Gate was derivative. That is diagnosed historical evidence, not an unexplained flake or current-head pass. The exact c470 run 36909036529 subsequently completed successfully, including Setup/Connect E2E and CI Gate. These green hosted gates do not override the independently reproduced source regressions or satisfy missing custom authority-chain/MXC proof.

Historical contributor and September 30 validation, retained for attribution

Windows x64, isolated process-local TEMP/TMP and tray data; OPENCLAW_REPO_ROOT points to this worktree. Shared/Tray were first restored and built before relying on --no-restore.

Exact command Original contributor head e2d175c0 final result Local integration 6559a590 result
.\build.ps1 Passed, exit 0 Passed, exit 0
dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore Passed 4129, Failed 0, Skipped 32, Total 4161 Passed 4191, Failed 0, Skipped 33, Total 4224
dotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore Passed 3072, Failed 0, Skipped 0 Passed 3363, Failed 0, Skipped 0
dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore --filter "FullyQualifiedName~ExecApprovalV2NormalizationTests|FullyQualifiedName~ExecReusableCommandBinderTests|FullyQualifiedName~ExecApprovalsCoordinatorTests|FullyQualifiedName~SystemCapabilityV2|FullyQualifiedName~ExecArgPatternTests" Passed 339, Failed 0, Skipped 0 Passed 339, Failed 0, Skipped 0
.\scripts\validate-mxc-e2e.ps1 without -AllowSkip Not run: source blockers, no coordinated fixture reservation Not run: same source blockers

Head 7b42fe012e17973770fe544e6cbad1f607ebfadf: ./build.ps1 exit 0. Shared passed 4132, failed 0, skipped 32, total 4164. ExecApprovalV2NormalizationTests passed 120. Tray passed 3067, failed 5, total 3072. Those five are the LF source-contract checks in #1518 (setup-code SSH fields, diagnostics copy glyph, voice dialog routing, and two capabilities-review checks). This commit does not change those files.

Head 53014a78c4b4c9116117e9d9121ca99b63d5374b: ./build.ps1 exit 0. Shared passed 4133, failed 0, skipped 32, total 4165. ExecApprovalV2NormalizationTests passed 121. Tray passed 3067, failed 5, total 3072. The same five LF source-contract checks failed. This commit does not change those files. .\scripts\validate-mxc-e2e.ps1 was not run.

One earlier original-head Shared run failed McpHttpServerTelemetryTests.ShutdownWhileWaitingForHandler_RecordsShutdownNotBusyOrTimeout at its two-second telemetry observation deadline. Its exact filtered retry passed 1/1, then the entire build/Shared/Tray closeout above passed. No parser change or telemetry workaround was made. The earlier contributor-reported Tray failures did not reproduce here; they are not represented as current failures.

Hosted CI at the actual PR head is separate from these local results: run 36278257616 failed Setup/Connect at wizard restart, GATEWAY_RESTART_PREPARATION_REFUSED, because another OpenClaw process owned SQLite state-lifecycle; CI Gate failed derivatively. This was diagnosed from the failed log and setup journal, not classified as an unexplained flake. No hosted gate was bypassed or made green by local validation.

Real Behavior Proof

  • Environment tested: native Windows PowerShell 5.1, PowerShell 7.6.6 and installed Git Bash; profiles disabled, harmless markers, task-owned cwd/scripts and a 15-second per-process bound. No default distro, real Gateway configuration, user approval store, desktop or other agent profile/output was touched.
  • PR head or commit tested: frozen c470a0c2, runtime on identical-parser integration 799090aa.
  • Exact steps or command run: pwsh -NoProfile -NonInteractive -NoLogo --if Text -c "Write-Output pr1511-inline"; also -of Text and --wd <owned-directory> before the same -c. For File controls, powershell.exe -NoProfile -NonInteractive -NoLogo -fi <owned-script.ps1> value and -fil.
  • Evidence after fix: each inline alias case exits 0, stdout pr1511-inline, but current Extract returns IsWrapper=false and TryBind returns non-null/BindFailure.None. Each abbreviated explicit File case exits 0, prints pr1511-direct-script and its value argument, but Extract returns IsWrapper=true and binder returns ShellWrapper. Full -File remains direct.
  • Observed result: current source is still unsafe to land. The original long -WorkingDirectory -File -c now correctly fails binding with ShellWrapper; positional pwsh script.ps1 /c value now correctly binds; /co on both PowerShell hosts is now recognized. A sole Windows PowerShell positional command string still executes two markers but binds, so argument count is not a correct mode boundary.
  • Bash and fish scope: literal login/combined-command flags stay inline; Bash -C/-eC, -- and post-script operands stay direct. Existing -o/-O/+e and -euo pipefail before -c remain missed. The escaped-semicolon/slash-semicolon argument-pattern difference still reproduces at helper/native level. Literal fish init-command classification is repaired, but no native fish run is claimed.
  • Containment prerequisite: current shipped wxc-exec.exe version 0.8.0+7dac1a9, fresh --probe returns tier=base-container, needsDaclAugmentation=false, warnings empty. This is host capability only, not non-skipping containment validation.
  • Screenshot or artifact links verified? (Yes/No/N/A): N/A; bounded copied native output described above, no current UI screenshot or public artifact claimed.
  • Not verified or blocked: strict non-skipping MXC E2E, real Gateway-to-node saved-rule denial before process I/O with allowed-direct positive control, MCP discovery/invocation and current approval-state proof. Source prerequisite failed, so no shared Gateway/WSL/desktop fixture or proof slot was consumed.
Earlier contributor native proof, retained as historical evidence only
  • Environment tested: native Windows PowerShell 5.1 and PowerShell 7.6.6, profiles disabled. No default distro, real Gateway configuration, user approval store, or desktop was touched.
  • PR head or commit tested: 53014a78c4b4c9116117e9d9121ca99b63d5374b.
  • Exact steps or command run: pwsh -NoProfile -wd -File -c "Write-Output pr1511-wd" and pwsh -NoProfile -wo -File -c "Write-Output pr1511-wo", then the same argv through Extract, TryBind, and ExecApprovalV2Normalizer.Normalize.
  • Evidence after fix:
pwsh -NoProfile -wd -File -c Write-Output pr1511-wd
Set-Location: Cannot find path '-File' because it does not exist.
pr1511-wd
exit 0

pwsh -NoProfile -wo -File -c Write-Output pr1511-wo
Set-Location: Cannot find path '-File' because it does not exist.
pr1511-wo
exit 0

Extract returns a wrapper whose payload is Get-Date for -wd, -wo, -wor, and /wd when the operand is -File and -c follows. TryBind returns BindFailure.ShellWrapper. Normalize leaves ReusableCommand null and AllowAlwaysPatterns empty. pwsh -wd C:\temp -File script.ps1 stays a script. The earlier -WorkingDirectory trace on 7b42fe012e17973770fe544e6cbad1f607ebfadf still applies to the long name.

  • Observed result: -wd and -wo run the inline command in native pwsh, and this head refuses a reusable allow-always identity for that argv. Native fish was not installed. Approval UI, a saved-rule replay through Gateway system.run, and non-skipping MXC were not run.
  • Additional matching evidence: an escaped Bash semicolon payload and a slash-before-semicolon payload produce different native output yet match the same separator-normalized helper argPattern. This is not persisted-rule Gateway-to-node final-I/O proof.
  • Cross-shell evidence limit: official fish documentation defines -C as an init command. This head classifies fish -C and fish --init-command as wrappers. Native fish was not installed and was not run.
  • Containment prerequisite: current shipped MXC 0.8.0+7dac1a9, native wxc-exec.exe --probe, reports tier=base-container, needsDaclAugmentation=false, no warnings on original and integration output. A capability probe is not containment E2E validation.
  • Screenshot or artifact links verified? (Yes/No/N/A): N/A, copied bounded native output above; no screenshot or public artifact link claimed.
  • Not verified or blocked: approval UI, MCP tools/list/tools/call or winnode discovery/invocation, real isolated Gateway-to-node saved-rule denial before process I/O/direct positive control, and non-skipping MXC E2E. All remain required after author repair and serialized proof-lane reservation.

Security Impact

  • New permissions or capabilities? (Yes/No): No
  • Secrets or tokens handling changed? (Yes/No): No
  • New or changed network calls? (Yes/No): No
  • Command or tool execution surface changed? (Yes/No): Yes
  • Data access scope changed? (Yes/No): No
  • If any answer is Yes, explain the risk and mitigation: the tested literal WorkingDirectory, fish and positional pwsh-script repairs work, but current alias operands still make inline pwsh reusable and abbreviated explicit WinPS File mode loses direct-script eligibility. Hold for author-owned classifier repair and exact-head authority-chain proof. No saved records are deleted or rewritten.

Compatibility and Migration

  • Backward compatible? (Yes/No): No for previously saved newly recognized inline-shell rules; those records remain present but inert.
  • Config or environment changes? (Yes/No): No
  • Migration needed? (Yes/No): No automatic migration, deletion or rewrite is introduced.
  • If yes, list the exact upgrade steps: operators approve recognized inline commands individually. This enforces the existing one-time shell policy; no new authorization contract or alternative unsafe compatibility path has been accepted from a bot recommendation. Direct-script compatibility still needs the source repairs described above.

Review Conversations

  • I replied to or resolved every bot review conversation addressed by this PR.
  • I left unresolved only conversations that still need maintainer judgment.

Allow-always could store powershell.exe /c and bash -l -c as reusable
rules because those spellings were not treated as inline shells.

- Treat PowerShell /c, /command, and colon-attached forms as wrappers
- Treat POSIX -c, --command, and -lc in any argument after the shell name
- Keep bash script.sh and bash -l script.sh bindable

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper

clawsweeper Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Sep 24, 2026
@clawsweeper

clawsweeper Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed October 1, 2026, 4:47 PM ET / 20:47 UTC (Revision 23).

ClawSweeper review

What this changes

The branch expands PowerShell, Bash, and fish command detection so inline shell commands require individual approval while direct script invocations remain reusable.

Merge readiness

⛔ Blocked before merge - 4 items remain

This PR remains useful: current main and the latest release retain the older classifier. The pinned head resolves the concrete earlier parser findings, but production-boundary approval proof remains incomplete.

Priority: P1
Reviewed head: ddcf698fd5d1cee6d7f27077897775348b6cb28a

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The focused patch resolves the earlier concrete findings, but security-boundary and upgrade proof limit confidence in landing it.
Proof confidence 🦐 gold shrimp (3/6) Needs stronger real behavior proof before merge: Authority-chain proof required: historical native Windows shell and binder traces support the repaired classifier cases, but do not demonstrate current-head Gateway system.run rejection of old inline-rule replay or revoked delayed authority before process I/O, with an allowed direct-script control. The hash-matched complete body explicitly leaves strict MXC, MCP, and visible approval-state proof unrun. No stored-data format or writer changes are introduced. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🦐 gold shrimp (3/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Needs proof Needs stronger real behavior proof before merge: Authority-chain proof required: historical native Windows shell and binder traces support the repaired classifier cases, but do not demonstrate current-head Gateway system.run rejection of old inline-rule replay or revoked delayed authority before process I/O, with an allowed direct-script control. The hash-matched complete body explicitly leaves strict MXC, MCP, and visible approval-state proof unrun. No stored-data format or writer changes are introduced. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 10 items Pinned introduced change: The verified merge-base-to-head delta changes only the shell classifier and normalization tests. PowerShell aliases and File prefixes are handled before positional input, and all Windows PowerShell positional input is now classified as command text.
Earlier concerns resolved: The current classifier consumes normalized value aliases, including of, recognizes File prefixes, preserves positional pwsh scripts, and removes both the argument-count and script-suffix exceptions for implicit Windows PowerShell command text. The first prior rank-up move is satisfied; the remaining moves concern validation and production proof.
Still necessary on main: Fetched main still checks only the first POSIX option and lacks the new PowerShell slash-command, operand-aware, and positional-command handling.
Findings None None.
Security None None.

How this fits together

Windows execution approvals classify incoming command arguments before matching saved rules or presenting an approval dialog. The resulting authorization is rechecked before the Windows node launches a process.

flowchart TD
  A[Gateway command arguments] --> B[Shell command classification]
  B --> C{Reusable command?}
  C -->|Yes| D[Saved approval rules]
  C -->|No| E[Individual approval]
  D --> F[Policy revalidation]
  E --> F
  F --> G[Windows process execution]
Loading

Before merge

  • Add real behavior proof - Needs stronger real behavior proof before merge: Authority-chain proof required: historical native Windows shell and binder traces support the repaired classifier cases, but do not demonstrate current-head Gateway system.run rejection of old inline-rule replay or revoked delayed authority before process I/O, with an allowed direct-script control. The hash-matched complete body explicitly leaves strict MXC, MCP, and visible approval-state proof unrun. No stored-data format or writer changes are introduced. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Resolve merge risk (P1) - Current-head production proof does not yet show that an agent replaying an old inline-command rule, or holding delayed authority after revocation, is rejected before process I/O.
  • Resolve merge risk (P1) - Fresh and existing approval-store behavior still need current-head direct-script positive controls, strict non-skipping MXC validation, MCP discovery/invocation, and visible approval-state evidence.
  • Complete next step (P2) - Post redacted current-head Gateway approval-boundary and upgrade controls, strict MXC, MCP, visible approval-state proof, and required build/Shared/Tray results. Remove stale repaired-finding claims from the PR body. Terminal output and logs count; screenshots or recordings can show the approval UI. Redact private endpoints and credentials. Updating the body should trigger re-review; if needed, ask a maintainer to comment @clawsweeper re-review.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production and test LOC production +222/-11 (net +211), tests +280/-7 (net +273) Production growth implements host-specific option classification within the existing owner, with corresponding binder and normalization regressions.

Merge-risk options

Maintainer options:

  1. Complete approval-boundary proof (recommended)
    Record current-head rejection of old inline-rule replay and revoked delayed authority before process I/O, with allowed direct-script controls and the required Windows proof pools.
  2. Hold the candidate
    Keep this PR open without landing until an isolated Windows proof environment can exercise the required approval and containment paths.

Technical review

Best possible solution:

Keep classification in the existing approval binder, preserve direct-script reuse, and verify the accepted inert-rule upgrade behavior through the real execution boundary.

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

Yes, source establishes that current main misses PowerShell /c and Bash -l -c before reusable binding; supplied native traces corroborate host semantics. This review did not execute a current-main reproduction.

Is this the best way to solve the issue?

Yes, repairing the existing classifier is the narrowest maintainable path, and the concrete earlier findings are resolved. Production-boundary and upgrade proof are still needed to establish merge readiness.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 76ab839973aa.

Labels

Label changes:

No label changes.

Label justifications:

  • P1: The work repairs durable approval eligibility for agent-executed inline shell commands.
  • merge-risk: 🚨 security-boundary: Changed classification controls whether saved authority can reach process execution, and final-effect rejection proof remains incomplete.
  • merge-risk: 🚨 compatibility: Existing inline-command rules intentionally become inert under an accepted tradeoff, while current-head upgrade and direct-script controls remain unverified.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: Authority-chain proof required: historical native Windows shell and binder traces support the repaired classifier cases, but do not demonstrate current-head Gateway system.run rejection of old inline-rule replay or revoked delayed authority before process I/O, with an allowed direct-script control. The hash-matched complete body explicitly leaves strict MXC, MCP, and visible approval-state proof unrun. No stored-data format or writer changes are introduced. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

What I checked:

Likely related people:

  • Caleb Eden: Raw commit c9608c8 adds src/OpenClaw.Shared/ExecApprovals/ExecReusableCommandBinder.cs:144 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: medium; commits: c9608c8e54e6; files: src/OpenClaw.Shared/ExecApprovals/ExecReusableCommandBinder.cs)
  • AlexAlves87: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • shanselman: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Prove old inline-rule replay and delayed approval revoked before execution cannot reach process I/O, alongside an allowed direct-script control.
  • Record corrected-head full build, Shared and Tray results, strict MXC, MCP discovery/invocation, and visible approval-state evidence.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (22 earlier review cycles; latest 8 shown)
  • reviewed 2026-10-01T18:07:47.407Z sha b3c60bd :: needs real behavior proof before merge. :: [P1] [P1] Normalize supported double-dash PowerShell value options | [P1] [P1] Preserve Windows PowerShell InputFormat operand handling | [P1] [P1] Distinguish positional command text from pwsh scripts
  • reviewed 2026-10-01T18:33:41.018Z sha c562cad :: needs real behavior proof before merge. :: [P1] [P1] Normalize value aliases before matching PowerShell operands | [P1] [P1] Recognize File abbreviations before positional command handling
  • reviewed 2026-10-01T18:56:20.631Z sha c470a0c :: needs real behavior proof before merge. :: [P1] [P1] Normalize value aliases before matching PowerShell operands | [P1] [P1] Recognize File abbreviations before positional command handling
  • reviewed 2026-10-01T19:13:13.155Z sha 74fb0d5 :: needs real behavior proof before merge. :: [P1] Consume the OutputFormat alias before scanning its operand
  • reviewed 2026-10-01T19:29:53.855Z sha 13441c6 :: needs real behavior proof before merge. :: none
  • reviewed 2026-10-01T19:37:58.492Z sha 13441c6 :: needs real behavior proof before merge. :: none
  • reviewed 2026-10-01T19:48:49.156Z sha a7da850 :: needs real behavior proof before merge. :: none
  • reviewed 2026-10-01T20:28:39.550Z sha 413a4c2 :: needs real behavior proof before merge. :: none

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@karkarl

karkarl commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Global triage: HOLD_FOR_AUTHOR. Take confidence 5%; recommendation confidence 99%; effort medium; risk high.

Three verified parsing gaps block this security-boundary change:

  1. Valid combined Bash option clusters such as -ec still permit durable inline-command approval. Together with existing slash/backslash argument normalization, an approved escaped separator can be replayed as an executable separator.
  2. POSIX scanning continues after the script operand, so bash script.sh -c value incorrectly treats the script argument as a shell switch and disables reusable approval.
  3. PowerShell scanning likewise continues after -File script.ps1, misclassifying script arguments such as /c or -c:value.

Please implement operand-aware, shell-specific option parsing and add saved-rule replay coverage for combined flags, post-script arguments, PowerShell -File, and existing-rule upgrades.

The PR must declare and run windows-wsl-mxc with validate-mxc-e2e.ps1 without -AllowSkip, plus real system.run approval and MCP discovery/invocation proof. The current none declaration is not valid for exec-approval behavior.

The red hosted lanes are not linked to this patch: Core failed an unrelated bounded-cancellation test, the E2E shards failed during shared setup initialization, and CI Gate is derivative. They do not replace the missing MXC proof.

bash script.sh -c value and pwsh -File script.ps1 /c value are direct script invocations. Scanning past the script operand classified those arguments as inline shell commands and blocked reusable approval. Inline -c and /c before the script operand still stay one-time.

Shared tests: 4125 passed, 32 skipped. Tray tests: 3067 passed, 5 failed on the LF source-contract mismatch tracked in openclaw#1518.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. label Sep 26, 2026
A single-dash letter cluster that contains c, such as -ec or -ce, is an inline shell command. The next argument is the payload. Scanning still stops at the script name, so bash script.sh -ec value stays a direct script.

Shared tests: 4128 passed, 32 skipped. Tray tests: 3067 passed, 5 failed on the LF source-contract mismatch tracked in openclaw#1518.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
A combined flag is inline only when it contains a lowercase c. Uppercase C is noclobber, so bash -C and bash -eC script.sh stay direct scripts. bash -ec remains inline. Exact POSIX flags are case-sensitive, so -C does not match -c.

Shared tests: 4129 passed, 32 skipped. Tray tests: 3067 passed, 5 failed on the LF source-contract mismatch tracked in openclaw#1518.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@shanselman shanselman added the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Sep 30, 2026
@shanselman

Copy link
Copy Markdown
Collaborator

HOLD_FOR_AUTHOR at e2d175c0ad5afc9e38717adc5fcba5fe90b3c27a. The old September 25 findings about literal -ec, post-script Bash arguments and explicit PowerShell -File have been fixed. The remaining blocker is not that old review: the new File guard introduces an operand-arity bypass.

I ran harmless native commands with profiles disabled, bounded completion and task-owned scripts/directories. In an owned working directory containing a directory literally named -File:

pwsh -NoProfile -NonInteractive -NoLogo -WorkingDirectory -File -c "Write-Output pr1511-inline"
native: exit 0, stdout pr1511-inline
current Extract: IsWrapper=false
current TryBind: non-null, BindFailure.None

The old scanner reached -c and refused reusable binding. The new scanner stops on -File, although that token is the value of -WorkingDirectory. This is a new weakening, not a preexisting alias gap.

Both independent reviewers used the same prompt and agree:

Issue Opus 5 Codex 5.3 Fix confidence
PowerShell option value -File terminates the scan HIGH HIGH 90%
Bash option operands / + flags hide a later -c HIGH HIGH 90%
Newly recognized /c after a positional PowerShell script is misclassified MEDIUM MEDIUM 90%

Single-reviewer findings and scope disposition:

Issue Opus 5 Codex 5.3 Fix confidence
PowerShell -cwa inline form, native-confirmed but preexisting — HIGH 95%
Binder controls depend on Bash/pwsh installation on PATH MEDIUM — 95%
EncodedCommand checks live only in the unused legacy resolver HIGH — 80%

The last two are portability/adjacent-contract follow-up evidence, not a request to expand this lane. The reported WSL proxy, Unicode dash, log-only upgrade explanation and unused payload-tail findings are also preexisting or outside the live changed boundary, so no speculative redesign or patches were made for them.

Additional native controls: bash -o errexit -c 'printf "%s\n" pr1511-inline', bash -O extglob -c ... and bash +e -c ... print the inline marker but remain bindable. These are remaining gaps in the same Bash option grammar, not newly introduced ones. pwsh script.ps1 /c value and pwsh -fi script.ps1 /c value execute the owned script with /c as an argument, but the new classifier rejects reusable binding. Both PowerShell 5.1 and 7 accept /co as Command; it still binds. PowerShell 7 -cwa is likewise an existing missed form. The tested colon-attached -c:Write-Output ... is not a working Command alias on these native hosts; classifier assertions alone should not establish that contract.

One separate introduced cross-shell regression is source-backed, not native-fish proof: the case-sensitive flag set applies to fish as well as Bash. fish -C COMMANDS means init-command, not Bash noclobber; it was classified as a wrapper before and is no longer classified at this head. Preserve Bash -C without generalizing Bash's grammar to every shell.

Please keep the repair in this PR's classifier owner, with shell-specific switch position, operand arity, termination and PowerShell prefix rules backed by actual CLI semantics, rather than another special-case token patch. Add binder/saved-rule regressions for the newly weakened case and direct-script controls. I confirmed that a missed Bash wrapper allows slash/backslash-normalized argument patterns to match two payloads with different actual semicolon behavior. That observation is native shell plus helper-level matching evidence, not Gateway-to-node final-I/O proof.

The intended upgrade behavior enforces the existing documented one-time shell policy: recognized inline wrappers have no reusable identity or saved-rule matches. Existing records remain untouched; this is not permission to delete, rewrite or keep unsafe rules.

Validation is green but does not override these source blockers. Original head: full build passed, Shared 4129 passed / 32 skipped, Tray 3072 passed, focused exec 339 passed. Unpushed normal integration 6559a590df776d7fdd354b0c92c2e8bcfdd5209e with main 04a880fe9488dbbd68ef786c1ebdc03c240c26c6: full build passed, Shared 4191 passed / 33 skipped, Tray 3363 passed, focused exec 339 passed. One earlier Shared run hit a 2-second shutdown-telemetry observation timeout; its exact retry and a complete build/Shared/Tray rerun passed. Structured autoreview also reports the new PowerShell arity bypass and implicit-script regression.

Live MXC 0.8.0 probe still reports tier=base-container, needsDaclAugmentation=false; this is not containment E2E proof. No shared desktop/Gateway/WSL fixture was started because source did not clear. Non-skipping validate-mxc-e2e.ps1, real saved-rule denial/direct positive control, approval UI and MCP discovery/invocation remain blocked. Current-head hosted Setup/Connect failed specifically at wizard restart with GATEWAY_RESTART_PREPARATION_REFUSED and SQLite state-lifecycle contention; CI Gate is derivative. No CI/check bypass, contributor push, force-push or merge was performed.

@shanselman shanselman removed the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Sep 30, 2026
@clawsweeper clawsweeper Bot added P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Sep 30, 2026
A PowerShell option value named -File stopped classification before a
later -c, so the binder could save that inline command. A positional
script made a later /c look like a host switch. fish -C is an init
command and stays a wrapper. bash -C stays noclobber.

Validation: ./build.ps1 exit 0. Shared 4132 passed, 32 skipped.
ExecApprovalV2NormalizationTests 120 passed. Tray 3067 passed and 5
failed, the LF source-contract mismatch in openclaw#1518.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Sep 30, 2026
Core and CLI failed only
RunAsync_OutputDrainIsBoundedAndPreservesFinalFragments_WhenDescendantRetainsHandles.
The drain took 1547 ms against a 1 second bound. That test is not in this change.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
pwsh binds -wd as the WorkingDirectory alias and unique prefixes such as
-wo and -wor the same way. An operand named -File was classified as file
mode, so a later -c could be saved as an allow-always rule. Consume those
forms, and the other documented value aliases, before the file check.

Normalization tests cover -wd, -wo, -wor, /wd, -w, and -ep, including the
binder and the allow-always identity. Native pwsh -wd -File -c and
-wo -File -c still print the inline command.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Oct 1, 2026
-i and -in match Interactive, which takes no argument. The value-option
prefix matcher treated them as InputFormat and skipped the following -c,
so the inline command could be saved as an allow-always rule. A prefix
that also matches a no-value switch stays a switch. -inp still consumes
its format value.

Normalization tests cover -i, -in, and -int with the binder and the
allow-always identity, and -inp Text -c still stays inline.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper clawsweeper Bot added the rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. label Oct 1, 2026
@clawsweeper clawsweeper Bot removed the rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. label Oct 1, 2026
Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
pwsh --InputFormat and --c still hid a later command. Windows PowerShell -i and -in are InputFormat, not Interactive, and a positional token with more arguments is command text rather than a script. A lone powershell script.ps1 stays a script. EncodedCommand operands stay consumed so they are not resolved as executables.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@shanselman shanselman added the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Oct 1, 2026
@shanselman

Copy link
Copy Markdown
Collaborator

October 1 reassessment of frozen head c470a0c2ae53047b764704f48de26d33c7ad8aac: the earlier literal repairs work, but source is not clear yet.

The long -WorkingDirectory -File -c case, pwsh script.ps1 /c value, /co on PowerShell 5.1/7, and fish's literal -C classifier are repaired. Bash literal -l -c/-ec/-ce stays one-time; noclobber -C/-eC, -- and post-script arguments remain direct. Native fish was not installed; that statement is classifier/source-contract evidence only.

The current repair still introduces these two concrete regressions. I ran the real installed PowerShell hosts with profiles disabled, harmless markers, bounded completion and task-owned files:

pwsh -NoProfile -NonInteractive -NoLogo --if Text -c "Write-Output pr1511-inline"
pwsh -NoProfile -NonInteractive -NoLogo -of Text -c "Write-Output pr1511-inline"
pwsh -NoProfile -NonInteractive -NoLogo --wd <owned-directory> -c "Write-Output pr1511-inline"
native: each exits 0 and prints pr1511-inline
Extract: IsWrapper=false; TryBind: non-null, BindFailure.None

The previous flat PowerShell scan reached the later -c. The new operand-aware scan reaches an unconsumed alias operand, then returns NotWrapper before -c. Normalize aliases from the same switch body as canonical names, including of; do not treat these real host inputs as unsupported script mode.

powershell.exe -NoProfile -NonInteractive -NoLogo -fi <owned-script.ps1> value
powershell.exe -NoProfile -NonInteractive -NoLogo -fil <owned-script.ps1> value
native: each exits 0; stdout pr1511-direct-script / value
Extract: IsWrapper=true; TryBind: null, BindFailure.ShellWrapper

These are explicit File-mode invocations, not positional command text. Recognize the actual host's File prefixes before positional handling. The full -File direct-script positive control still binds correctly.

Two already-reported affected grammar gaps also remain and are not newly attributed to this delta: Bash -o errexit, -O extglob, +e, and -euo pipefail before -c execute inline but bind; its escaped-semicolon and slash-semicolon payloads still produce different actual output yet match one helper-generated saved argument pattern. Windows PowerShell with a single positional token Write-Output pr1511-inline; Write-Output pr1511-second executes both statements, but the new argument-count heuristic still treats it as non-wrapper. A single argument is not necessarily a script path. PowerShell 7 -cwa remains an existing inline form outside the new Command-prefix recognition.

Keep the repair in the classifier's host-specific operand/termination contract, with binder/normalization regression cases and allowed direct-script controls. No maintainer parser patch, branch push, storage migration or speculative downstream authorization redesign was made.

Both models agree: HIGH consensus (actual returned model metadata, identical frozen prompt):

Issue Opus 5.5 GPT-6 Astra Fix confidence
Raw value-alias lookup / missing of hides later inline command MEDIUM HIGH 95%
Lone Windows PowerShell positional command string remains missed HIGH HIGH 90%
Explicit Windows PowerShell fi/fil script mode is misclassified LOW MEDIUM 95%

One-model severity findings: LOW consensus:

Issue Opus 5.5 GPT-6 Astra Fix confidence
Existing Bash scan plus separator-normalized helper matching Existing gap noted, no severity CRITICAL 85%
Existing PowerShell 7 cwa inline form MEDIUM Not flagged 95%

The Bash native/helper result is independently reproduced, but no persisted-rule Gateway/process-I/O authority proof was run, so the CRITICAL model label is not repeated as a newly verified production exposure. Extra proposed token-table entries, Unicode/unknown-dash cases and attached fish forms are not accepted as new blockers without scoped evidence, and are not permission for a speculative parser or storage rewrite. This was a direct claude-opus-5.5 + gpt-6-astra (long_context) pair, with no nesting, fallback or redundant panel.

Current main is f4122a8927e7cd452d166a23c0d5e30299f94368. Normal unpushed integration 799090aa4a6fd1ede0b51479a383b195d1ce0926 preserves the original author and prior local integration history; parser/tests are identical to c470a0c2. Exact commands on that candidate:

  • .\build.ps1: passed, exit 0.
  • dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore: Passed 4252, Failed 0, Skipped 33, Total 4285.
  • dotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore: Passed 3859, Failed 0, Skipped 0.
  • Focused normalization/binder/coordinator/SystemCapabilityV2/argument-pattern filter: Passed 345, Failed 0, Skipped 0.

Test projects were built/restored first; these are complete floor results, not focused retries. The local floor had no global-MCP capture or FileStream failures. The immediate prior hosted c562cad9 failure was specifically LocalAiApiCredentialStoreTests.ConcurrentCreationConvergesOnOneCredential, IOException on its task credential fixture file, with CI Gate derivative. That historical diagnosis is separate from live c470a0c2 checks; no blanket flake verdict or bypass.

Shipped MXC 0.8.0+7dac1a9 currently probes BaseContainer with needsDaclAugmentation=false and no warnings. Source has not cleared, so no shared Gateway/WSL/desktop fixture or reservation was started. Strict non-skipping validate-mxc-e2e.ps1, Gateway-to-node saved-rule denial before process I/O plus direct positive control, MCP discovery/invocation and current approval-state evidence remain required, not passed. The existing no-reusable-inline policy makes corrected old records inert without deleting or rewriting them; no new product authorization was inferred.

pwsh strips one dash before alias lookup, so --wd is -wd and must be consumed before -File. Windows PowerShell accepts -fi and -fil as -File. Those prefixes stay script invocations, including when another argument follows.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@shanselman shanselman added status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. and removed status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. labels Oct 1, 2026
@shanselman

Copy link
Copy Markdown
Collaborator

Narrow native follow-up at current 74fb0d5bd34b3c233fb19e9df3aec27c2716be96: --if/--wd and -fi are fixed. One introduced alias gap still reproduces.

pwsh -NoProfile -NonInteractive -NoLogo -of Text -c "Write-Output pr1511-inline"
native: exit 0, stdout pr1511-inline
Extract: IsWrapper=false
TryBind: non-null, BindFailure.None

of is PowerShell's OutputFormat alias, but it is absent from the alias set and is not a prefix of OutputFormat. Scanning stops at the unconsumed Text operand before -c. This is introduced relative to the original flat PowerShell scan, which reached that later -c and refused reusable binding. The new normalized-alias helper correctly fixes --if/--wd; it does not fix a missing alias.

The already-identified Windows PowerShell positional branch also still reproduces:

powershell.exe -NoProfile -NonInteractive -NoLogo "Write-Output pr1511-inline; Write-Output pr1511-second"
native: exit 0, stdout pr1511-inline / pr1511-second
Extract: IsWrapper=false
TryBind: non-null, BindFailure.None

That false-negative predates the PR, but the newly authored branch/comment still infers script mode from one remaining argument. One argument can be command text.

The unchanged Bash -o errexit / -O extglob / +e paths still execute inline and bind. The escaped-semicolon versus slash-semicolon controls still produce different native output while the helper saved argument pattern matches. These remain preexisting affected paths, not newly attributed regressions or final Gateway/process-I/O proof.

Positive controls on this exact head: pwsh --if Text -c ... and --wd <owned-directory> -c ... now return ShellWrapper; Windows PowerShell -fi <owned-script.ps1> value executes the direct script and binds; Bash noclobber -C stays direct. Profiles were disabled; commands were harmless, bounded and confined to task-owned paths. Native fish was not run.

Please finish the remaining classifier-owned alias/default-command repair and post a stable candidate for the next validation/proof pass. No downstream authorization or stored-rule redesign is requested. This comment replaces those repaired c470 findings as current status rather than competing with your active branch edits.

Only dotnet build .\src\OpenClaw.Shared\OpenClaw.Shared.csproj --no-restore --verbosity quiet (exit 0) and the selected native/binder controls ran for this follow-up. Parser/tests match 74fb0d5b in normal unpushed integration 9a2e2d60cc8bccb2057f0a50692332e7341c94b3; the original e2d/6559/c470/799 history is preserved. New-head full build/Shared/Tray, a new dual review and Gateway/MXC/MCP/UI proof were deliberately not started on this unsafe source. Earlier full-green c470 evidence is not relabeled as validation of 74fb0d5b. No patch, push, merge or shared lifecycle reservation was made.

@shanselman shanselman removed the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Oct 1, 2026
of is the OutputFormat alias and is not a prefix of OutputFormat. pwsh -of Text -c runs the inline command, but the scanner stopped at Text and allowed a reusable identity. Consume -of and --of before the next token.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Oct 1, 2026
@shanselman shanselman added the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Oct 1, 2026
@shanselman

Copy link
Copy Markdown
Collaborator

Narrow current-head check at 13441c63776e51be53e0982c0cd15306037bc21c: -of is now fixed. The lone Windows PowerShell command-string case remains.

powershell.exe -NoProfile -NonInteractive -NoLogo "Write-Output pr1511-inline; Write-Output pr1511-second"
native: exit 0
stdout:
pr1511-inline
pr1511-second
Extract: IsWrapper=false
TryBind: non-null, BindFailure.None

ExecShellWrapperNormalizer.cs:194-196 still says a lone positional stays a script and requires another token before recognizing Windows PowerShell command mode. Installed Windows PowerShell 5.1 executes this single argv element as command text. This false-negative predates the PR, but it remains in the positional-mode branch authored by this repair and prevents the relevant one-time-inline policy from being satisfied. It is not a new regression attributed to the -of commit.

Exact-head fixed controls: pwsh -of Text -c ..., --if Text -c ... and --wd <owned-directory> -c ... now return ShellWrapper. Windows PowerShell explicit -File and abbreviated -fi direct-script controls run and remain bindable. These repaired findings are closed for this snapshot.

Please finish this classifier-owned default-command boundary and provide a stable candidate for the next floor/proof pass. No Bash/fish expansion, stored-rule redesign or downstream authorization changes are requested.

This pass ran only the Shared dependency build (exit 0) and six harmless native/binder controls, with profiles disabled, task-owned files/cwd and bounded completion. Runtime used normal unpushed integration a3ccf1b46497159bf214f8aa119e559d88be5cd4; parser/tests match 13441c63, and all previous author/local history is preserved. Full suites, another review pair and Gateway/MXC/MCP/UI lifecycle were not started because source still fails this case. No earlier head's green validation is claimed for 13441c63, and no patch, push, merge or fixture reservation was made.

@shanselman shanselman removed the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Oct 1, 2026
@shanselman

Copy link
Copy Markdown
Collaborator

I am preparing one local, uncommitted correction against 13441c6 for the sole Windows PowerShell implicit-command argument-count guard, with explicit File and pwsh positional-script controls. No alias grammar, Bash/fish, storage or protocol expansion, and no branch push or fixture launch. Please keep this candidate stable while I validate an applyable two-file patch; if you are already repairing this boundary, reply and I will stop rather than compete. Native Windows PowerShell treats implicit positional text as evaluated Command mode; explicit File mode remains the reusable script control.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@shanselman shanselman added the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Oct 1, 2026
@shanselman

Copy link
Copy Markdown
Collaborator

Validated two-file maintainer patch for the sole Windows PowerShell positional-command guard. No commit or push was made.

The patch is against 13441c63776e51be53e0982c0cd15306037bc21c. Your new a7da850e496c5a79271a4405de1b5f560cf25e75 is a CI-only commit with identical source/tests, so the same patch applies. The new head was checked only for that delta, not relabeled as fully tested.

The only production change removes the second-argument requirement. Native Windows PowerShell 5.1 evaluates a single positional command string, including an implicit script-path string. Both now remain runnable one-time but cannot obtain durable approval. Explicit Windows PowerShell -File/-fi, including value options before File, and positional pwsh scripts still execute and bind. No .ps1 suffix exception, new option grammar, Bash/fish change, storage mutation or saved-rule deletion was added.

Validation of uncommitted patch SHA-256 707D8464E98B10664DBC3D683CA3D5C1759E253944259CBC232F136AC8292779 on preserved local integration a3ccf1b46497159bf214f8aa119e559d88be5cd4:

  • .\build.ps1: passed, exit 0.
  • dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore: final full default run Passed 4259, Failed 0, Skipped 33, Total 4292.
  • dotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore: Passed 3859, Failed 0, Skipped 0.
  • Focused normalization/binder/coordinator/SystemCapabilityV2/argument-pattern filter: Passed 352, Failed 0, Skipped 0.
  • Eleven bounded native/binder controls: all execution outcomes and command/File-mode distinctions passed.
  • Identical-prompt direct claude-opus-5.5 / gpt-6-astra (long_context) review: both reported no actionable findings in this local delta. This is not a repeated full-PR review.
  • Exact-author patch applicability checked using an isolated Git index; no author branch/index changes.

The first full Shared run failed McpHttpServerTests.Dispose_DuringInFlightHandler_DoesNotSurfaceObjectDisposedException:412: its process-global unobserved-task capture plus forced GC recorded disposed SafeFileHandle/StreamReader.ReadToEndAsync exceptions, without semaphore/server frames. The producing task was not established from that stack. The exact test passed 1/1 in isolation, then the entire default build/Shared/Tray floor passed unchanged. No MCP test/assertion changes, serialization, process kill or blanket flake claim was used.

Apply the following patch locally with git apply, then publish a stable author candidate and reply with its SHA. Stable-head real Gateway saved-rule denial/direct positive control, MCP/approval-state evidence and strict non-skipping MXC still gate landing. No fixture or lifecycle reservation was started for this local repair.

diff --git a/src/OpenClaw.Shared/ExecApprovals/ExecShellWrapperNormalizer.cs b/src/OpenClaw.Shared/ExecApprovals/ExecShellWrapperNormalizer.cs
index 14d0f2c4..ac0a1c48 100644
--- a/src/OpenClaw.Shared/ExecApprovals/ExecShellWrapperNormalizer.cs
+++ b/src/OpenClaw.Shared/ExecApprovals/ExecShellWrapperNormalizer.cs
@@ -190,9 +190,9 @@ internal static class ExecShellWrapperNormalizer
 
             if (!t.StartsWith('-') && !t.StartsWith('/'))
             {
-                // Windows PowerShell joins a positional token and everything
-                // after it into command text. A lone positional stays a script.
-                if (windowsPowerShell && i + 1 < command.Count)
+                // Windows PowerShell evaluates positional input as command text,
+                // even one token. Explicit File mode was handled above.
+                if (windowsPowerShell)
                     return t;
                 return null;
             }
diff --git a/tests/OpenClaw.Shared.Tests/ExecApprovalV2NormalizationTests.cs b/tests/OpenClaw.Shared.Tests/ExecApprovalV2NormalizationTests.cs
index 5fe408b2..cc0ac650 100644
--- a/tests/OpenClaw.Shared.Tests/ExecApprovalV2NormalizationTests.cs
+++ b/tests/OpenClaw.Shared.Tests/ExecApprovalV2NormalizationTests.cs
@@ -235,6 +235,51 @@ public class ExecApprovalV2NormalizationTests
         Assert.NotNull(bound);
     }
 
+    [Theory]
+    [InlineData("Write-Output marker")]
+    [InlineData("Write-Output marker; Write-Output second")]
+    [InlineData("script.ps1")]
+    public void Normalizer_WindowsPowerShellSinglePositional_IsOneTimeCommand(string command)
+    {
+        string[] argv = ["powershell.exe", "-NoProfile", command];
+        AssertWrapper(argv, command);
+        Assert.Null(ExecReusableCommandBinder.TryBind(argv, cwd: null, env: null, out var failure));
+        Assert.Equal(ExecReusableCommandBinder.BindFailure.ShellWrapper, failure);
+
+        var outcome = ExecApprovalV2Normalizer.Normalize(Req(argv));
+        Assert.True(outcome.IsResolved);
+        Assert.Null(outcome.Identity!.ReusableCommand);
+        Assert.Empty(outcome.Identity.AllowAlwaysPatterns);
+        Assert.Empty(outcome.Identity.AllowlistResolutions);
+    }
+
+    [Theory]
+    [InlineData("powershell.exe", "-File")]
+    [InlineData("powershell.exe", "-fi")]
+    [InlineData("powershell.exe", "-InputFormat", "Text", "-File")]
+    [InlineData("pwsh.exe")]
+    public void Normalizer_SingleScriptInFileMode_RemainsReusable(string executable, params string[] options)
+    {
+        var directory = Directory.CreateTempSubdirectory("exec-single-script");
+        try
+        {
+            var tool = Path.Combine(directory.FullName, executable);
+            File.WriteAllBytes(tool, [0x4D, 0x5A]);
+            var argv = new List<string> { tool, "-NoProfile" };
+            argv.AddRange(options);
+            argv.Add("script.ps1");
+
+            Assert.False(ExecShellWrapperNormalizer.Extract(argv).IsWrapper);
+            var bound = ExecReusableCommandBinder.TryBind(argv, cwd: null, env: null, out var failure);
+            Assert.Equal(ExecReusableCommandBinder.BindFailure.None, failure);
+            Assert.NotNull(bound);
+        }
+        finally
+        {
+            directory.Delete(recursive: true);
+        }
+    }
+
     [Fact]
     public void Normalizer_FishInitCommand_IsWrapper()
     {
@@ -1078,15 +1123,14 @@ public class ExecApprovalV2NormalizationTests
     }
 
     [Fact]
-    public void ResolveForAllowlist_DirectPowerShellScriptFile_NotFailClosed()
+    public void ResolveForAllowlist_WindowsPowerShellPositionalScript_ResolvesCommandText()
     {
-        // Direct exec path: ["powershell", "script.ps1"] — no inline flag, no -EncodedCommand.
-        // DirectExecUsesEncodedCommand must not trigger; must resolve as a single resolution.
+        // This historical resolver inspects the command text, not durable eligibility.
         var resolutions = ExecCommandResolver.ResolveForAllowlist(
             ["powershell", "script.ps1"],
             evaluationRawCommand: null, cwd: null, env: null);
         Assert.Single(resolutions);
-        Assert.Contains("powershell", resolutions[0].ExecutableName, StringComparison.OrdinalIgnoreCase);
+        Assert.Equal("script.ps1", resolutions[0].ExecutableName);
     }
 
     [Fact]

@shanselman shanselman removed the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Oct 1, 2026
powershell Get-Date runs as command text, but a lone positional was saved as a script. Command text is now inline, including when nothing follows it. A lone script.ps1 path stays a script.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
A lone script name is still implicit -Command. Guessing from a .ps1 suffix left that command bindable. Explicit -File remains the reusable script form.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants