Skip to content

fix(tools): Block MSYS coreutils under Windows sandbox#476

Open
euxaristia wants to merge 6 commits into
Gitlawb:mainfrom
euxaristia:fix/windows-msys-sandbox
Open

fix(tools): Block MSYS coreutils under Windows sandbox#476
euxaristia wants to merge 6 commits into
Gitlawb:mainfrom
euxaristia:fix/windows-msys-sandbox

Conversation

@euxaristia

@euxaristia euxaristia commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #458

Summary

Git for Windows ships POSIX coreutils (head, grep, cat, and similar) as MSYS PE binaries on PATH. Under Zero's write-restricted Windows sandbox they fail at startup with ACCESS_DENIED, CreateFileMapping, signal-pipe, and cygheap errors instead of running the command. This leaves agents retrying broken shell pipelines.

Detect MSYS-prone commands before execution, surface MSYS runtime failures from command output, steer the agent toward native Zero tools or cmd/PowerShell alternatives, and exclude MSYS-prone command names from known-safe segmented-command tails on Windows.

Note: #458 may still need the issue-approved label per CONTRIBUTING.md before merge.

Changes

internal/tools/shell_runtime.go

  • Add regex detection for MSYS binary paths on Windows, plus a quote-aware, segment/word-anchored scan for MSYS-prone command names (bare, .exe, or directory-prefixed).
  • Export MsysProneCommandName() for shared guard logic, now backed by a single canonical name set shared by every detection path.
  • Return windows_msys_sandbox issues from detectShellCommandIssue() with guidance toward native tools or require_escalated.
  • Detect MSYS runtime failures in stderr via detectShellOutputIssue() (CreateFileMapping, signal pipe, cygheap).
  • Expand shell guidance on Windows to warn about Git usr\bin MSYS coreutils.

internal/tools/bash.go, internal/tools/exec_command.go

  • Apply output-issue hints when a command fails so MSYS startup errors get actionable suggestions.

internal/agent/command_prefix.go

  • Treat MSYS-prone command names as not known-safe on Windows so segmented pipelines cannot bypass the guard.

internal/agent/system_prompt.go

  • Add Windows environment guidance against piping to or invoking Git for Windows POSIX coreutils under the sandbox.

internal/tools/shell_runtime_test.go

  • New tests for MSYS path detection, bare utility names, stderr failure patterns, quote-awareness, and expr/ls consistency.

internal/tools/bash_tool_test.go, internal/tools/exec_command_test.go, internal/agent/command_prefix_test.go, internal/agent/loop_test.go

  • Update and extend tests for MSYS blocking, escalation bypass, and prompt guidance on Windows.

Updates addressing jatmn's review

  1. Escalation path now actually bypasses the MSYS block. bash and exec_command were calling detectShellCommandIssue() before resolving sandboxPermissions into a command engine, so rerunning a blocked command with sandbox_permissions: "require_escalated" was still hard-blocked by the same preflight check it was supposed to escalate past. Both tools now resolve the command engine first and only bypass the windows_msys_sandbox issue kind when that resolves to true unsandboxed execution (an approved escalation). Any other issue kind (a real cmd.exe syntax problem) still blocks regardless of escalation, since running outside the sandbox does not fix a syntax error. Covered by TestBashToolRequireEscalatedMsysGuard and TestExecCommandRequireEscalatedBypassesMsysGuardAfterApproval.
  2. One MSYS-prone command set for every detection path. windowsMsysProneNames is now the single source of truth: MsysProneCommandName(), the preflight command scan, and (transitively) the known-safe-segment guard in command_prefix.go all derive from it. This fixes two drift bugs: expr was in the name set but missing from the old regex alternations (so it was never proactively flagged), and ls hit the older windows_shell_syntax branch before ever reaching the MSYS check (so it got different metadata/guidance than every other MSYS-prone name). Covered by TestDetectShellCommandIssueFlagsExprAndLsConsistently.
  3. MSYS detection is now quote-aware. The old regexes matched MSYS-prone names anywhere in the command text, so quoted argument text mentioning a coreutil name (a commit message, a PR comment body, echo "log | head is broken") could be misdetected as an actual invocation. Detection now splits the command into cmd.exe-operator-separated segments (respecting double-quote grouping) and only checks the first word of each segment, the actual command position, not the raw text anywhere. Covered by TestDetectShellCommandIssueIgnoresQuotedMsysMentions.
  4. A second round applied the same command-position treatment to the explicit MSYS path check, and closed a prefix-approval gap. The binary-path detector (usr\bin\, mingw64\bin\, ...) still scanned raw command text, so a quoted path mention had the same false-positive risk item 3 fixed for coreutil names; it now runs against the same per-segment first word. The segment splitter also stopped treating ; as a separator (cmd.exe does not split on it, unlike bash) and now respects the ^ escape character, so echo ^| head is no longer misread as invoking head. Separately, command_prefix.go's proposedCommandPrefix could offer to approve a prefix (e.g. ps aux in ps aux | head -5) without checking that the remaining segments were independently safe, letting an MSYS-prone tail ride along uncovered once the whole command was escalated. It now only proposes a prefix when every other segment is known-safe on its own.
  5. Command redirection is now a command-word boundary, and the post-execution heuristic no longer treats the command line as evidence. firstCommandWord split only on whitespace, so head>out.txt or cat<in.txt (cmd.exe accepts redirection with no separating space) were read as one word and missed entirely; it now also stops at </>. Separately, detectShellOutputIssue() scanned command + output together, so a command whose own argument text merely quoted an MSYS failure string (e.g. a gh pr comment --body describing the bug) could be mislabeled after running, even when the real stdout/stderr was unrelated. It now takes only output (the command parameter is removed from its signature and both call sites), so the command line can never be used as evidence again. Covered by TestDetectShellCommandIssueFlagsRedirectionAttachedToCommand and TestBashToolIgnoresMsysMarkersInCommandArgumentsAfterFailure.

Test plan

  • go build ./...
  • go test ./internal/tools/... (pass)
  • go test ./internal/agent/... (pass)
  • go vet ./internal/tools/... ./internal/agent/... (clean)
  • Manual: head.exe / git log | head blocked or surfaced with MSYS sandbox guidance on Windows

Summary by CodeRabbit

  • Bug Fixes
    • Improved Windows shell safety: more accurate MSYS/Git-for-Windows detection for segmented commands and operator parsing, plus better classification of MSYS runtime failures from command output with clearer blocking metadata and hints.
    • Updated Windows system prompt guidance to steer users toward native Windows/Zero tools and avoid POSIX coreutils from MSYS/Git-for-Windows, with escalation guidance only when host-level execution is needed.
    • Ensured approved require_escalated flows bypass the MSYS sandbox guard when execution becomes host-level.
  • Tests
    • Expanded Windows coverage for MSYS detection, hint behavior, segmented-command handling, and escalation bypass.

Git for Windows usr\bin coreutils (head, grep, cat, ...) are MSYS PE binaries that fail with ACCESS_DENIED and cygheap/signal-pipe errors when Zero runs them in the write-restricted Windows sandbox. Detect and block these commands before execution, surface MSYS runtime failures from stderr, steer agents toward native Zero tools or require_escalated, and exclude MSYS-prone names from known-safe command tails.

Refs Gitlawb#458
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds Windows MSYS/Cygwin sandbox detection, threads the new windows_msys_sandbox issue through bash/exec handling, updates Windows command-prefix and prompt guidance, and expands tests for detection, blocking, and require_escalated bypass behavior.

Changes

MSYS/Cygwin Sandbox Detection

Layer / File(s) Summary
Windows MSYS/Cygwin issue detection core
internal/tools/shell_runtime.go
Adds the windows_msys_sandbox issue kind, MSYS-prone command matching, and MSYS runtime-failure output detection for Windows shell checks.
Exec and bash MSYS guard wiring
internal/tools/bash.go, internal/tools/exec_command.go
Moves sandbox-resolution earlier, gates bypasses through msysGuardBypassed, and records shell-output issues in exec results.
Windows command-prefix and prompt policy
internal/agent/command_prefix.go, internal/agent/system_prompt.go, internal/agent/loop_test.go
Rejects MSYS-prone command names on Windows, expands Windows shell guidance, and updates Windows-facing prefix and prompt tests.
Windows MSYS detection tests
internal/tools/shell_runtime_test.go, internal/tools/bash_tool_test.go, internal/tools/exec_command_test.go, internal/agent/command_prefix_test.go
Expands Windows-only coverage for MSYS-prone commands, quoted arguments, output failures, blocked results, and require_escalated bypass behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • Gitlawb/zero#322: Shares the segmented command-prefix logic and Windows-safe segment handling.
  • Gitlawb/zero#412: Directly overlaps with Windows shell-runtime heuristics and shell-issue handling.
  • Gitlawb/zero#79: Touches the bash tool execution path and metadata plumbing used by the new guard/bypass flow.

Suggested reviewers: Vasanthdev2004, kevincodex1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds MSYS-prone command detection, output failure handling, guidance updates, and tests matching #458's requirements.
Out of Scope Changes check ✅ Passed The changes stay focused on Windows MSYS sandbox detection, guidance, and tests, with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: blocking MSYS coreutils in the Windows sandbox.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/tools/shell_runtime.go (1)

57-96: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

expr is MSYS-prone but never actually detected.

MsysProneCommandName (Line 61) lists expr as an MSYS-prone command name, but neither windowsMsysPosixExecutablePattern (Line 31) nor windowsPosixUtilityPattern (Line 35) include expr in their alternations. So detectShellCommandIssue will never flag a bare expr ... invocation on Windows, even though knownSafeCommandSegment (in command_prefix.go) will reject it as unsafe. The model gets no proactive guidance for this specific command even though the rest of the system treats it as MSYS-prone.

🔧 Proposed fix
-	windowsMsysPosixExecutablePattern = regexp.MustCompile(`(?i)(?:^|[&|;\s"])(?:[\w .:\\-]+\\)?(head|tail|grep|cat|cut|wc|nl|paste|tr|uniq|rev|seq|stat|uname|which|id|awk|sed|xargs)\.exe(?:\s+|$|")`)
+	windowsMsysPosixExecutablePattern = regexp.MustCompile(`(?i)(?:^|[&|;\s"])(?:[\w .:\\-]+\\)?(head|tail|grep|cat|cut|wc|nl|paste|tr|uniq|rev|seq|stat|uname|which|id|expr|awk|sed|xargs)\.exe(?:\s+|$|")`)
 	windowsPosixUtilityPattern = regexp.MustCompile(`(?i)(^|[&|;]\s*)(head|tail|grep|cat|cut|wc|nl|paste|tr|uniq|rev|seq|stat|uname|which|id|awk|sed|xargs)(?:\s+|$)`)
+	windowsPosixUtilityPattern = regexp.MustCompile(`(?i)(^|[&|;]\s*)(head|tail|grep|cat|cut|wc|nl|paste|tr|uniq|rev|seq|stat|uname|which|id|expr|awk|sed|xargs)(?:\s+|$)`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tools/shell_runtime.go` around lines 57 - 96, The Windows shell
detection is missing bare expr even though MsysProneCommandName already treats
it as MSYS-prone. Update the Windows command-issue detection in
detectShellCommandIssue by adding expr to the same matching path used for other
POSIX utilities, likely via the windowsPosixUtilityPattern or the MSYS-prone
executable handling alongside windowsMsysPosixExecutablePattern. Keep the
existing behavior and make sure a bare expr invocation now returns a
windows_msys_sandbox issue with the same kind of guidance as the other
MSYS-prone commands.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tools/shell_runtime.go`:
- Around line 119-134: The final fallback in msysRuntimeFailedInOutput is too
broad and can misclassify non-MSYS failures as windows_msys_sandbox. Tighten the
last return condition in msysRuntimeFailedInOutput by adding a stronger
MSYS-specific anchor, such as requiring usr\\bin, cygheap, or an MSYS-style log
prefix like [main], alongside the existing win32 error 5 and terminating checks.
Keep the earlier more-specific branches unchanged and make the fallback only
match genuine MSYS runtime failures.

---

Outside diff comments:
In `@internal/tools/shell_runtime.go`:
- Around line 57-96: The Windows shell detection is missing bare expr even
though MsysProneCommandName already treats it as MSYS-prone. Update the Windows
command-issue detection in detectShellCommandIssue by adding expr to the same
matching path used for other POSIX utilities, likely via the
windowsPosixUtilityPattern or the MSYS-prone executable handling alongside
windowsMsysPosixExecutablePattern. Keep the existing behavior and make sure a
bare expr invocation now returns a windows_msys_sandbox issue with the same kind
of guidance as the other MSYS-prone commands.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e6d23b5c-35e6-428c-a314-98b418ed13a3

📥 Commits

Reviewing files that changed from the base of the PR and between 949ee43 and 365296f.

📒 Files selected for processing (8)
  • internal/agent/command_prefix.go
  • internal/agent/command_prefix_test.go
  • internal/agent/loop_test.go
  • internal/agent/system_prompt.go
  • internal/tools/bash_tool_test.go
  • internal/tools/exec_command.go
  • internal/tools/shell_runtime.go
  • internal/tools/shell_runtime_test.go

Comment thread internal/tools/shell_runtime.go

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approve — genuine Windows-sandbox hardening; two edge-case false-positives worth a fast-follow

Worth merging: yes. This fixes a real, documented failure (#458): Git-for-Windows MSYS coreutils fail under Zero's write-restricted sandbox token and strand the agent in retry loops. The layered approach — pre-exec block + reactive stderr detection + allowlist tightening + prompt guidance — is well-tested and correct. Thanks @euxaristia.

Reviewed as part of the 6-PR Windows-sandbox cluster, with the detection logic ground-truthed against the actual code and independently re-verified. Note the CHANGES_REQUESTED here is the CodeRabbit bot, not a human — not a merge gate.

Two real but minor edge cases I'd fold in as a fast-follow (non-blocking):

  1. The require_escalated escape hatch it advertises is self-blocked. detectShellCommandIssue fires unconditionally (bash.go:87 / exec_command.go:520) before sandboxPermissions is consulted, so re-running the same command with require_escalated — exactly what the new message suggests — gets hard-blocked identically and never reaches the escalated path that would actually succeed. The advertised recovery isn't actionable through the same tool.
  2. The .exe regex over-matches. windowsMsysPosixExecutablePattern's leading class [&|;\s"] matches a coreutil .exe token appearing anywhere as an argument or inside a quoted string — so git commit -m "fix head.exe crash" is hard-blocked as a false MSYS invocation. Uncommon, but plausible in a repo that literally discusses head.exe/grep.exe. Anchor it to the invoked-program position the way the bare windowsPosixUtilityPattern already is. (Also: expr is missing from the util set.)

Neither blocks — both are recoverable via native tools and the core fix is correct. Merge as-is and file one polish issue, or fold them in.

Sequencing: land after #465 — both touch bash_tool_test.go with different test functions, so keep a single copy of the echo-arg helper on rebase.

CodeRabbit review feedback: the win32-error-5 plus terminating check
had no MSYS-specific anchor, so unrelated access-denied failures could
be mislabeled as windows_msys_sandbox. Require an MSYS marker like the
neighboring checks do.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
euxaristia added a commit to euxaristia/zero that referenced this pull request Jul 4, 2026
Vasanthdev2004 asked for this PR to be split since it bundled several
independent static-analysis fixes. Remove the specialist depth cap, bash
output OOM cap, and persistence error logging: each now has its own PR
(Gitlawb#491, Gitlawb#492, Gitlawb#493).

Also drop the "cat" addition to the Windows POSIX-utility detection in
shell_runtime.go. PR Gitlawb#476 already covers cat detection comprehensively
under the MSYS/sandbox angle, so keeping it here would duplicate that
work.

What remains: the Windows cmd.exe quoting-guidance rewrite, the
Windows-specific interactive-command suggestions (steering away from
Unix head/tail/ps toward native or PowerShell alternatives), the
clipboard PowerShell -Command fix, and the CI test-slack change in
exec_command_test.go.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found a few issues that need to be addressed before this is ready.

Findings

  • [P2] Make the advertised escalation path actually bypass the MSYS block
    internal/tools/bash.go:83
    The new MSYS hint tells the model to rerun with sandbox_permissions: "require_escalated" when host-level execution is truly required, but both bash and exec_command call detectShellCommandIssue before the parsed sandboxPermissions is used to choose the unsandboxed command engine. That means rerunning the exact blocked command with require_escalated is still hard-blocked by the same preflight result and never reaches the escalation path. Please either let approved require_escalated calls bypass this MSYS preflight check or remove the escalation suggestion so the recovery guidance matches the actual behavior.

  • [P2] Use one MSYS-prone command set for every detection path
    internal/tools/shell_runtime.go:31
    CodeRabbit's outside-diff request is still valid, and it points at a broader drift problem: this PR now has a central MsysProneCommandName list, but detectShellCommandIssue still maintains separate regex alternations. expr is listed as MSYS-prone and knownSafeCommandSegment rejects it on Windows, yet bare expr ... and expr.exe miss the proactive windows_msys_sandbox guidance because the regexes omit it. ls has the opposite inconsistency: it is listed as MSYS-prone, but it hits the old windows_shell_syntax branch before the MSYS branch, so it gets different metadata and recovery guidance from the rest of the shared set. Please make the preflight detection derive from the same MSYS-prone command set, or otherwise keep the lists and result kinds explicitly synchronized, and add regression coverage for expr and ls.

  • [P2] Make the MSYS command detection quote-aware
    internal/tools/shell_runtime.go:31
    The new MSYS preflight regexes do not understand shell quoting, so they can treat harmless argument text as an executable command. windowsMsysPosixExecutablePattern matches head.exe/grep.exe after any whitespace or quote, which hard-blocks commands like git commit -m "fix head.exe crash" or gh pr comment --body "grep.exe fails under MSYS". The bare utility regex has the same boundary problem around quoted operators, so echo "try git log | head" can be blocked as if head were actually being invoked. This repo now explicitly discusses those command names, so the false positive is likely during follow-up commits, PR comments, or docs edits. Please anchor detection to parsed command positions or explicit MSYS paths, and add regression tests for quoted argument text.

…uote-aware

Addresses jatmn's review on PR Gitlawb#476:
- bash and exec_command called detectShellCommandIssue before resolving
  sandbox_permissions into a command engine, so a require_escalated retry of
  a blocked command was still hard-blocked by the same preflight check.
  Resolve the command engine first and only bypass the windows_msys_sandbox
  issue kind when that resolves to true unsandboxed execution; other issue
  kinds (real cmd.exe syntax problems) still block regardless of escalation.
- Unify all MSYS-prone command name checks (MsysProneCommandName, the
  preflight scan, and transitively command_prefix.go's known-safe guard)
  behind one windowsMsysProneNames set. This fixes expr (in the name set but
  missing from the old regex alternations, so never proactively flagged) and
  ls (hit the older windows_shell_syntax branch first, so it got different
  metadata than every other MSYS-prone name).
- Make the preflight scan quote-aware: split the command into cmd.exe-
  operator-separated segments respecting double-quote grouping, and only
  check the first word of each segment instead of scanning raw text anywhere.
  Fixes false positives on quoted text like commit messages or PR comment
  bodies that merely mention a coreutil name.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tools/exec_command_test.go`:
- Around line 164-176: The Windows-only exec command test is using a
PATH-dependent command in the RunWithOptions call, so the result can be polluted
by a shell_issue even when the sandbox bypass works. Update the test in
exec_command_test.go to use a guaranteed native command instead of the current
cat invocation, or assert the require_escalated bypass directly from
RunWithOptions/ExecCommandToolName behavior so the check does not depend on
shell availability.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 88ad9884-a6d4-4623-9219-de8285ddd2ba

📥 Commits

Reviewing files that changed from the base of the PR and between 96afeec and b4e58a6.

📒 Files selected for processing (6)
  • internal/tools/bash.go
  • internal/tools/bash_tool_test.go
  • internal/tools/exec_command.go
  • internal/tools/exec_command_test.go
  • internal/tools/shell_runtime.go
  • internal/tools/shell_runtime_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/tools/shell_runtime_test.go

Comment thread internal/tools/exec_command_test.go
TestBashToolRequireEscalatedMsysGuard and
TestExecCommandRequireEscalatedBypassesMsysGuardAfterApproval asserted
shell_issue == "" to prove the require_escalated bypass worked. Once
bypassed, "cat somefile.txt" actually runs unsandboxed, and its real,
PATH-dependent output could trip the unrelated post-execution
detectShellOutputIssue heuristic, failing the assertion for reasons
unrelated to the preflight guard under test.

Assert on the exit_code "-1" sentinel (set only by shellIssueBlockResult)
instead, which directly reflects whether the preflight guard blocked the
command.
@euxaristia

Copy link
Copy Markdown
Contributor Author

Status update on the open findings:

Already fixed on this branch before this push:

  • jatmn's escalation-bypass finding (bash.go/exec_command.go): commandEngine is now resolved before the MSYS preflight check, and msysGuardBypassed lets an approved require_escalated call through (b4e58a6).
  • jatmn's inconsistent-detection-set finding and CodeRabbit's missing-expr finding: shell_runtime.go now has one windowsMsysProneNames map as the single source of truth, used by both the preflight scan and MsysProneCommandName, so expr/ls can't drift out of sync (b4e58a6/365296f).
  • jatmn's quote-awareness finding: windowsCommandSegments now respects double quotes and firstCommandWord handles quoted first tokens, so text inside a quoted argument (e.g. a commit message mentioning head.exe) is no longer mistaken for an invocation (b4e58a6).
  • CodeRabbit's msysRuntimeFailedInOutput fallback finding: the win32-error-5 fallback now requires an MSYS-specific anchor (usr\bin, cygheap, msys-2.0.dll, cygwin1.dll, or [main]) (96afeec).

Fixed in 304a5a5 (this push): CodeRabbit's last finding, that TestExecCommandRequireEscalatedBypassesMsysGuardAfterApproval asserted shell_issue == "" after running the real cat somefile.txt unsandboxed, which is PATH-dependent and could spuriously fail if that command's real output happened to trip the unrelated post-execution detectShellOutputIssue heuristic. Same latent issue existed in the sibling TestBashToolRequireEscalatedMsysGuard, so fixed both: both now assert on the exit_code "-1" preflight-block sentinel instead, which is what they actually mean to test.

go build/vet clean on both linux and windows targets, go test ./internal/tools/... ./internal/agent/... green.

@jatmn ready for re-review.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the changed paths and found a couple of issues that still need to be addressed.

Findings

  • [P2] Apply cmd.exe command-position parsing to every MSYS preflight path
    internal/tools/shell_runtime.go:151
    The quote-aware fix only covers the bare-name scan, but the explicit MSYS path detector still runs against the entire raw command before windowsCommandSegments/firstCommandWord is used. That means commands such as git commit -m "C:\Program Files\Git\usr\bin\head.exe fails" or gh pr comment --body "C:\Git\usr\bin\grep.exe is blocked" are still hard-blocked even though the MSYS path is just quoted argument text, which is the same class of false positive the latest parser was meant to avoid. The segment splitter also does not match cmd.exe tokenization: it treats caret-escaped operators and ; as command separators, so echo ^| head and echo foo; head are classified as actual head invocations even though cmd.exe prints those as argument text. Please route the explicit path check through a real command-position parser, or otherwise make the parser handle quoted/escaped argument text consistently, and add regressions for quoted MSYS paths, caret-escaped operators, and semicolon text.

  • [P2] Do not offer a prefix approval that leaves an MSYS-prone tail uncovered
    internal/agent/command_prefix.go:31
    This PR removes MSYS-prone commands from knownSafeCommandSegment on Windows, but proposedCommandPrefix still returns the first non-safe valid segment before proving the remaining segments are safe. For a command like ps aux | head -5, the prompt can still offer only ps aux as the reusable prefix; if the user chooses that option, shellExecutionArgsForApproval adds sandbox_permissions: "require_escalated" to the entire command and the first execution runs the uncovered head segment unsandboxed. That undercuts the new guard because the current command can still bypass the sandbox through a prefix that does not cover the MSYS-prone segment. Please only offer prefix approval when every other segment is genuinely known-safe for the current OS, or include the unsafe segment in the approval flow instead of escalating the whole command under the earlier prefix.

…x approval gap

Address jatmn's latest review on PR Gitlawb#476:

- shell_runtime.go: the explicit MSYS binary-path check (usr\bin\,
  mingw64\bin\, msys-2.0.dll, cygwin1.dll) ran against the raw command
  string, so a quoted mention (e.g. a commit message describing a
  usr\bin\head.exe failure) was still hard-blocked. It now runs against
  the first word of each operator-separated segment, same as the
  coreutil-name check. windowsCommandSegments also stopped treating ;
  as a command separator (cmd.exe does not split on ;, unlike bash) and
  now respects cmd.exe's ^ escape character, so escaped operators like
  echo ^| head are no longer misread as a real head invocation.

- command_prefix.go: proposedCommandPrefix could offer to approve a
  single segment (e.g. "ps aux" in "ps aux | head -5") without checking
  that every other segment was independently safe. Since approving a
  prefix escalates the whole command to bypass the sandbox, an
  MSYS-prone or otherwise unsafe segment could ride along uncovered.
  It now only proposes a segment as the prefix when every other
  segment in the command is known-safe on its own.

Added regression tests for quoted MSYS path mentions, caret-escaped
operators, semicolon text, and the prefix-approval gap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the changed paths and found a couple of issues that still need to be addressed.

Findings

  • [P2] Treat cmd redirection as a command-name boundary
    internal/tools/shell_runtime.go:177
    The new command-position scan only checks the first whitespace-delimited word of each cmd.exe segment. That still misses real MSYS-prone invocations when cmd redirection is attached directly to the command name, which cmd.exe accepts. For example, some-command | head>out.txt, cat<in.txt, and grep>matches.txt all invoke the same MSYS-prone utilities, but firstCommandWord returns head>out.txt/cat<in.txt/grep>matches.txt, so msysProneCommandWord never matches the bare command name. Please strip or split cmd redirection operators when extracting the invoked command word, and add regressions for attached </> redirection.

  • [P2] Keep post-execution MSYS detection from matching quoted command text
    internal/tools/shell_runtime.go:193
    The preflight path now avoids quoted MSYS-path mentions, but the output heuristic still lowercases and scans command + "\n" + output. That means a failed command whose arguments merely contain an MSYS failure string, such as a gh pr comment --body or commit command quoting C:\Git\usr\bin\head.exe: *** fatal error ..., can be mislabeled as windows_msys_sandbox even when the actual failure output is unrelated. This reintroduces the same false-positive class the command-position parser just fixed, only after execution. Please base the MSYS runtime heuristic on the command's actual output/stderr, or otherwise apply the same command-position/quote-aware treatment before using command text as evidence.

Process note: the linked issue #458 still does not have the issue-approved label, while CONTRIBUTING.md says community PRs require an approved parent issue. Please wait for maintainer confirmation on whether this PR can proceed under that policy.

…nd text as output evidence

Addresses jatmn's latest review on PR Gitlawb#476:
- firstCommandWord only split on whitespace, so cmd.exe redirection
  attached directly to a command name (head>out.txt, cat<in.txt,
  grep>matches.txt) was read as one word and never matched against the
  MSYS-prone name set. It now also stops at < and >.
- detectShellOutputIssue scanned command+output together, so a command
  whose own argument text merely quoted an MSYS failure string (e.g. a
  gh pr comment --body describing the bug) could be mislabeled after
  running even though the real stdout/stderr was unrelated. Removed the
  command parameter entirely from detectShellOutputIssue and both call
  sites (bash.go, exec_command.go), so the command line can never be
  used as post-execution evidence again.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-approving. My earlier approval predates the hardening you and jatmn worked through, so I read the delta since then rather than let a stale approval ride on a sandbox PR.

The part that matters most is the escalation hole in proposedCommandPrefix, and it's closed correctly: since approving a prefix escalates the whole command past the sandbox, it now refuses to propose one unless every other segment is independently known-safe, and knownSafeCommandSegment no longer treats MSYS-prone coreutils as safe on Windows. That's the actual security boundary and it holds.

The rest is advisory-hint quality, and it's careful too — detection is anchored to the first word of each cmd.exe segment (so head.exe quoted in a commit message or PR body doesn't false-trigger), the output scan reads output only instead of the command line, the win32-error-5 fallback is anchored to MSYS markers so an unrelated ACCESS_DENIED isn't mislabeled, and the prone-name set is one source of truth shared by every path so they can't drift. Test coverage across all of it is good.

Solid. Over to kevin.

@kevincodex1

Copy link
Copy Markdown
Contributor

please rebase from main and fix conflicts @euxaristia

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MSYS/Cygwin coreutils (head, grep, tail, ...) fail under the Windows sandbox, separately from the pipe issue in #456

4 participants