Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion emrg/tools/bash_tool.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,10 @@ def _translate_windows_heredocs(cmd: str) -> tuple[str, str | None]:
r"submodule|worktree)\b"
)
_GIT_DELETE_RE = re.compile(r"\bgit\s+(?:branch|tag)\s+-[dD]\b")
# `git stash list` / `git stash show` are READ-ONLY stash inspection (reviewing
# WIP) — the mutator regex above would false-positive on them because it matches
# the bare `git stash` token. Exempt a pure stash read (no chain to a mutator).
_GIT_STASH_READ_RE = re.compile(r"\bgit\s+stash\s+(?:list|show)(?:\s|$|\|)")


def _extract_write_targets(cmd: str) -> list[str]:
Expand DownExpand Up@@ -263,8 +267,15 @@ def _check_sandbox(cmd: str, mode: str, workdir: str | None = None) -> tuple[boo
# (stash / checkout . / reset --hard / clean) write no file targets
# and escaped the target scan (community issue #979). Also blocks
# working-tree writers: apply / am / archive / submodule / worktree.
# Pure `git stash list` / `git stash show` (read-only WIP inspection)
# are exempt — but a chain to a mutator (e.g. `&& git stash drop`)
# stays blocked.
m = _GIT_MUTATOR_RE.search(cmd) or _GIT_DELETE_RE.search(cmd)
if m:
if m and not (
_GIT_STASH_READ_RE.search(cmd)
and not re.search(r"\bgit\s+stash\s+(?:drop|pop|clear|apply|push|branch|create)\b", cmd)
and not re.search(r"&&|;", cmd)
):
return False, (
f"read-only sandbox: blocked git mutating command {m.group(0)!r} "
"(dirty-tree guard, community issue #979)"
Expand Down
8 changes: 7 additions & 1 deletion tests/test_bash_tool_sandbox.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,7 +106,9 @@ def test_check_read_only_allows_dev_null_redirect():


def test_check_read_only_allows_read_commands():
for cmd in ("ls -la", "git status", "cat file.txt", "pwd", "echo hi"):
for cmd in ("ls -la", "git status", "cat file.txt", "pwd", "echo hi",
"git stash list", "git stash show -p",
"git stash show stash@{0}", "git stash list | grep foo"):
allowed, _, _ = _check_sandbox(cmd, "read-only")
assert allowed is True, cmd

Expand All@@ -119,6 +121,10 @@ def test_check_read_only_blocks_git_mutators():
for cmd in (
"git stash",
"git stash list && git stash drop",
"git stash push -m wip",
"git stash drop stash@{0}",
"git stash pop",
"git stash clear",
"git checkout .",
"git checkout -- src/main.py",
"git restore .",
Expand Down
Loading