From 5b5cbd7c6c37c7dc4f017dd38144dac994a98509 Mon Sep 17 00:00:00 2001 From: Doron Greenspan Date: Tue, 18 Aug 2026 09:42:01 -0400 Subject: [PATCH] =?UTF-8?q?fix(guard):=20cd=20is=20a=20read=20verb=20?= =?UTF-8?q?=E2=80=94=20a=20leading=20cd=20defeated=20the=20git-read=20exem?= =?UTF-8?q?ption=20(AEAB-24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `is_pure_read_command` splits a command on `| ; & \n ( ) ` `, takes the FIRST token of each segment as that segment's verb, and returns false as soon as one verb is not in READ_ONLY_VERBS. `cd` was not in that list. `cd` cannot modify anything, so this is not a judgement call. Measured on the shipped function rather than reasoned about: cd /tmp -> is_pure_read=false (wrong) cd /tmp && cat f.md -> false (wrong) cd /repo && git show origin/main:file -> false (wrong) git show origin/main:file -> true (works alone) The last two are the point. The AMUX-3128 follow-up added GIT_READ_SUBCMDS precisely so a peer READING a file is not minted as its co-author, and its comment states the harm plainly: "the harder a peer checks your output, the more it blocks you, which trains toward GN=1 where the guard stops protecting anything." That exemption works for a bare `git show` and is defeated by a leading `cd` — the most common prefix in this repo's own workflow. It shipped and was silently inert in the majority of real invocations. It blocked a real commit, measured not inferred. 2026-08-17 19:17 and again 19:57: amux blocked commit in /Users/dorongreenspan/Developer/amux — per-path: frustrations.md ... owner=amux-errors-and-bugs ... in_firsthand=false `in_firsthand=false` is the tell: the ownership that blocked that session was INFERRED, never a firsthand Edit/Write. Five blocks in 40 minutes. 191 distinct inferred-edit records in 24h (220 raw, 29 near-duplicate pairs from the two server processes), 117 of them verb=cd. The warning was self-diagnosing the whole time and nobody read it: "A READ verb here means is_pure_read_command missed a reader and it may be minting false co-authorship." Not all of it is a false positive, and saying so matters: verb=cat (56) is CORRECT — those are `cat > f < f`, `cd x && cat > f < Claude-Session: https://claude.ai/code/session_01B4vuEScunv4K6RMwQoT9xx Amux-Session: amux-errors-and-bugs --- crates/amux-server/src/api/git_guard.rs | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/crates/amux-server/src/api/git_guard.rs b/crates/amux-server/src/api/git_guard.rs index 6468f815..6e1b5d69 100644 --- a/crates/amux-server/src/api/git_guard.rs +++ b/crates/amux-server/src/api/git_guard.rs @@ -414,6 +414,18 @@ fn is_pure_read_command(cmd: &str) -> bool { "cut", "column", "od", "xxd", "hexdump", "tree", "du", "basename", "dirname", "realpath", "readlink", "sha256sum", "md5sum", "nl", "tac", "pwd", "echo", "printf", + // `cd` cannot modify anything, and its ABSENCE silently undid the + // git-read exemption directly below: `git show f` was correctly a read, + // while `cd /repo && git show f` was not, because the FIRST segment's + // verb decided the whole command. `cd` is the most common prefix in this + // repo's own workflow, so the exemption was defeated in the majority of + // real invocations — 117 of 191 inferred-edit records in 24h had + // verb=cd (AEAB-24). Safe because a real mutation still trips either the + // output-redirection check above or its own non-read verb in a later + // segment; `cd x && rm f`, `cd x && sed -i`, `cd x && git commit` and + // `cd x && cat > f <` is how a careful VERIFIER reads a file — `git show`, // `git log --stat`, `git diff`, `git grep`, `git blame` to chase a specific @@ -1809,3 +1821,54 @@ mod tests { assert!(window_secs() >= WINDOW_FLOOR); } } + +#[cfg(test)] +mod cd_is_not_a_mutation { + use super::*; + + /// AEAB-24. `cd` was missing from READ_ONLY_VERBS, and its absence silently + /// undid the git-read exemption that AMUX-3128 added right above it. + /// + /// `is_pure_read_command` splits on `| ; & \n ( ) \``, takes the FIRST token of + /// each segment as that segment's verb, and returns false the moment one verb + /// is not read-only. So a leading `cd` — which cannot modify anything — + /// decided the whole command, and `cd /repo && git show f` minted an inferred + /// edit record naming the reader as co-author of a file they only inspected. + /// + /// That is the exact harm the git-read exemption exists to prevent, and its + /// own comment says so: "the harder a peer checks your output, the more it + /// blocks you, which trains toward GN=1 where the guard stops protecting + /// anything." The exemption worked for a bare `git show` and was defeated by + /// the most common prefix in this repo's workflow — 117 of 191 inferred-edit + /// records in one 24h window had verb=cd. + /// + /// The SAFETY half of this test is the load-bearing half. Adding a verb to + /// READ_ONLY_VERBS widens what the guard treats as harmless, and a widening + /// that goes too far stops the guard protecting anything while leaving every + /// other test green. + #[test] + fn cd_is_a_read_verb_but_never_launders_a_mutation() { + // Reads that a leading `cd` used to misclassify. + for cmd in [ + "cd /tmp", + "cd /tmp && cat f.md", + "cd /repo && git show origin/main:frustrations.md", + "cd /repo && grep -n needle src/lib.rs", + ] { + assert!(is_pure_read_command(cmd), "should be a pure read: {cmd}"); + } + + // SAFETY — every one of these still mutates, and must still be attributed. + // `cd` must not launder the mutation that follows it. + for cmd in [ + "cd /tmp && echo hi > f.md", // output redirection + "cd /tmp && cat > f.md <<'EOF'\nx\nEOF", // heredoc write + "cd /tmp && rm f.md", // non-read verb + "cd /tmp && sed -i '' s/a/b/ f.md", // in-place edit + "cd /repo && git commit -am x", // git WRITE subcommand + "cd /repo && git add -A", + ] { + assert!(!is_pure_read_command(cmd), "must NOT be a pure read: {cmd}"); + } + } +}