diff --git a/ROADMAP.md b/ROADMAP.md index eb7d8bcc..1fb40b89 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6549,3 +6549,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 496. **`PermissionEnforcer::check_bash` treats `python`/`python3`/`node`/`ruby` as read-only commands, so inline script execution (`python -c ...`, `node -e ...`, `ruby -e ...`) can write files in `read-only` mode** — dogfooded 2026-05-20 from the `#clawcode-building-in-public` 13:00 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with branch/origin `docs/roadmap-workdir-provenance@8382e1e`. Code inspection: `rust/crates/runtime/src/permission_enforcer.rs::is_read_only_command` allowlists `python3`, `python`, `node`, and `ruby`, and only rejects commands containing `-i `, `--in-place`, ` > `, or ` >> `. It does not inspect interpreter flags or inline code. Therefore `python -c 'open("pwned.txt","w").write("x")'`, `node -e 'require("fs").writeFileSync("pwned.txt","x")'`, and `ruby -e 'File.write("pwned.txt","x")'` are classified as read-only and allowed by `check_bash` under `PermissionMode::ReadOnly`, despite arbitrary filesystem writes. The richer `bash_validation` module's semantic list does not include these interpreters, but the runtime enforcer uses this separate local heuristic. **Required fix shape:** (a) remove general-purpose interpreters (`python`, `python3`, `node`, `ruby`) from the read-only allowlist or require explicit safe subcommands only (`python --version`, maybe `python -m pytest` under test gating is not read-only); (b) if kept, detect `-c`, `-e`, here-doc, stdin script, and file path script execution as non-read-only; (c) replace the local heuristic with the canonical `bash_validation` pipeline to avoid future divergence; (d) add regressions proving inline interpreter writes are denied in read-only mode while harmless version/help invocations remain bounded. **Why this matters:** read-only mode is supposed to prevent writes. Any general interpreter with inline code is equivalent to arbitrary shell execution; allowing it because the first token is `python` or `node` is a direct permission bypass and contradicts the safety story for exploratory sessions. Source: gaebal-gajae dogfood response to Clawhip message `1506642678996013207` on 2026-05-20. 497. **`PermissionEnforcer::check_bash` also allowlists `gh` as read-only, so GitHub-mutating commands (`gh pr merge`, `gh issue edit`, `gh repo delete`, etc.) can run in `read-only` mode** — dogfooded 2026-05-20 from the `#clawcode-building-in-public` 13:30 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with branch/origin `docs/roadmap-workdir-provenance@214176d`. Code inspection: `rust/crates/runtime/src/permission_enforcer.rs::is_read_only_command` includes `gh` in the read-only allowlist and only rejects `-i`, `--in-place`, ` > `, or ` >> `. There is no `gh` subcommand classifier analogous to `bash_validation.rs::validate_git_read_only` for `git`. Therefore `gh pr merge 123 --merge`, `gh issue edit 5 --add-label done`, `gh repo delete owner/repo --yes`, and `gh api repos/:owner/:repo/actions/runs/1/approve -X POST` are all classified as read-only by the runtime enforcer, even though they mutate remote GitHub state. **Required fix shape:** (a) remove `gh` from the blanket read-only allowlist or implement a conservative `gh` subcommand classifier; (b) allow only clearly read-only forms (`gh pr view/list`, `gh issue view/list`, `gh run view/list`, `gh api` without mutating method) and require workspace-write/danger or prompt for merge/edit/create/delete/api `-X POST|PATCH|PUT|DELETE`; (c) add regressions proving mutating `gh` commands are denied in `PermissionMode::ReadOnly` while view/list commands remain allowed if desired; (d) preferably replace the local heuristic with the canonical bash-validation pipeline so shell permission logic has one source of truth. **Why this matters:** read-only mode is not just filesystem safety; it should prevent external state mutation. A `gh pr merge` or `gh issue edit` from a supposedly read-only lane is a serious control-plane bypass and can alter public repo state without the permission escalation the mode implies. Source: gaebal-gajae dogfood response to Clawhip message `1506650232937513140` on 2026-05-20. + +498. **`PermissionEnforcer::check_bash` allowlists `cargo` and `rustc` as read-only, bypassing the canonical package/build-state classifier and allowing state-mutating Rust toolchain commands in `read-only` mode** — dogfooded 2026-05-20 from the `#clawcode-building-in-public` 14:00 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with branch/origin `docs/roadmap-workdir-provenance@5a4a8eb`. Code inspection: `rust/crates/runtime/src/permission_enforcer.rs::is_read_only_command` includes `cargo` and `rustc` in the read-only allowlist and only rejects `-i`, `--in-place`, ` > `, or ` >> `. The canonical `bash_validation.rs` pipeline treats `cargo` as package/build-state management (`STATE_MODIFYING_COMMANDS` / `PACKAGE_COMMANDS`) and has a regression that `npm install` is blocked in read-only mode, but the runtime enforcer does not call that pipeline. As a result, commands like `cargo install cargo-edit`, `cargo add anyhow`, `cargo generate-lockfile`, `cargo update`, `cargo fix --allow-dirty`, and `cargo build` (writes `target/`) are classified as read-only by the actual enforcer. `rustc -o out main.rs` likewise writes an output binary without shell redirection and is allowed. **Required fix shape:** (a) remove `cargo` and `rustc` from the blanket read-only allowlist; (b) optionally add a conservative subcommand classifier where only clearly non-mutating forms (`cargo --version`, `cargo metadata --no-deps` if proven side-effect-free, `rustc --version`) are read-only; (c) route `check_bash` through the canonical `bash_validation` pipeline; (d) add regressions for `cargo install`, `cargo add`, `cargo update`, `cargo build`, `cargo test`, and `rustc -o` under `PermissionMode::ReadOnly`. **Why this matters:** build/package commands routinely modify the workspace, global cargo home, lockfiles, and build artifacts. A read-only exploratory lane should not be able to install packages or rewrite lock/build outputs just because the first token is `cargo`. Source: gaebal-gajae dogfood response to Clawhip message `1506657779883184250` on 2026-05-20.