Follow-up from #222 (PR adding the runtime PreToolUse forbidden-bash hook).
The hook added in #222 gates only the Bash tool (matcher: "Bash" in src/core/executor.ts). The same destructive effects remain reachable through tools the hook never inspects:
- Write/Edit into the cloned
.git/. The agent has filesystem write under cwd (the clone). Writing .git/config (e.g. changing the push URL or adding a mirror refspec), overwriting .git/refs/heads/<branch> / packed-refs, or planting .git/hooks/pre-push rewrites repo state with no git push --force string ever passing through Bash; a later plain git push (allowed) then publishes the rewritten ref. This is the more realistic path since the agent already has .git/ write access under bypassPermissions.
- MCP server tools. The hook does not inspect MCP tool calls. Today the wired GitHub MCP servers are scoped to comments/reviews (no merge), but a future server exposing a merge/branch-mutation tool would bypass the Bash gate.
Also note (documented in #222): the regex denylist on free-form shell is inherently incomplete (variable indirection, command substitution, base64|sh, write-script-then-run). The hook is defense-in-depth for the naive/literal path, not a sandbox.
Suggested directions
- Consider denying Write/Edit tool calls whose resolved target path is inside
.git/.
- Re-audit the matcher set if any MCP server gains merge/branch-mutation tools.
- The durable, vendor-independent backstop is server-side branch protection on the remote (force-push + direct-merge rejected regardless of the local command) plus a least-privilege installation token. Evaluate enforcing branch protection as the primary control, with the runtime hook as one layer.
Surfaced by the senior-review panel during #222.
Follow-up from #222 (PR adding the runtime PreToolUse
forbidden-bashhook).The hook added in #222 gates only the
Bashtool (matcher: "Bash"insrc/core/executor.ts). The same destructive effects remain reachable through tools the hook never inspects:.git/. The agent has filesystem write undercwd(the clone). Writing.git/config(e.g. changing the push URL or adding a mirror refspec), overwriting.git/refs/heads/<branch>/ packed-refs, or planting.git/hooks/pre-pushrewrites repo state with nogit push --forcestring ever passing through Bash; a later plaingit push(allowed) then publishes the rewritten ref. This is the more realistic path since the agent already has.git/write access underbypassPermissions.Also note (documented in #222): the regex denylist on free-form shell is inherently incomplete (variable indirection, command substitution, base64|sh, write-script-then-run). The hook is defense-in-depth for the naive/literal path, not a sandbox.
Suggested directions
.git/.Surfaced by the senior-review panel during #222.