From 71910a15627b197ad0f9cd5941ee911163252689 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Tue, 25 Aug 2026 20:05:25 +0800 Subject: [PATCH] emrg: exempt read-only git worktree list / submodule status from dirty-tree mutator block --- emrg/tools/bash_tool.py | 19 ++++++++++++++++++- tests/test_bash_tool_sandbox.py | 15 ++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/emrg/tools/bash_tool.py b/emrg/tools/bash_tool.py index 81d547cd..cbb8c841 100644 --- a/emrg/tools/bash_tool.py +++ b/emrg/tools/bash_tool.py @@ -141,6 +141,11 @@ 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 worktree list` / `git submodule status` are READ-ONLY inspection (same +# false-positive class as `git stash list/show`): the mutator regex above +# matches the bare `git worktree` / `git submodule` tokens. Exempt a pure +# read; any worktree/submodule MUTATOR keyword below still blocks. +_GIT_WT_READ_RE = re.compile(r"\bgit\s+(?:worktree\s+list|submodule\s+status)\b") def _extract_write_targets(cmd: str) -> list[str]: @@ -263,8 +268,20 @@ 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 read-only inspections (`git worktree list`, `git submodule + # status`) are exempt — but a chain to a mutator (e.g. `&& git + # submodule update`) stays blocked. m = _GIT_MUTATOR_RE.search(cmd) or _GIT_DELETE_RE.search(cmd) - if m: + if m and not ( + _GIT_WT_READ_RE.search(cmd) + and not re.search( + r"\bgit\s+(?:worktree\s+(?:add|remove|move|prune|lock|unlock)|" + r"submodule\s+(?:update|add|deinit|set-url|sync|absorbgitdirs|" + r"foreach))\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)" diff --git a/tests/test_bash_tool_sandbox.py b/tests/test_bash_tool_sandbox.py index 9290c731..c634e7e4 100644 --- a/tests/test_bash_tool_sandbox.py +++ b/tests/test_bash_tool_sandbox.py @@ -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 worktree list", "git worktree list --porcelain", + "git submodule status", "git submodule status | head"): allowed, _, _ = _check_sandbox(cmd, "read-only") assert allowed is True, cmd @@ -143,6 +145,17 @@ def test_check_read_only_blocks_git_mutators(): "git archive --output=tree.tar HEAD", "git submodule update --init", "git worktree add ../wt master", + # worktree/submodule MUTATORS stay blocked (cycle 20260825-200038) + "git worktree remove ../wt", + "git worktree move ../wt ../wt2", + "git worktree prune", + "git worktree lock ../wt", + "git worktree unlock ../wt", + "git submodule add https://example.com/repo.git sub", + "git submodule deinit -f .", + "git submodule sync", + "git worktree list && git worktree remove ../wt", + "git submodule status; git submodule update", ): allowed, reason, enforcement = _check_sandbox(cmd, "read-only") assert allowed is False, f"{cmd!r} should be blocked"