guard-gh-release.py does not see a release command wrapped in bash -c "…" or fed to a shell through a heredoc, so both run unchecked.
Measured with the guard at 23dca2d on the branch of #155. The same holds at b410748, so this predates that PR:
bash -c "gh api -X PATCH repos/o/r/releases/1 -f name=v1" -> exit 0 (allowed)
bash <<'EOF'
gh api -X PATCH repos/o/r/releases/1 -f name=v1
EOF -> exit 0 (allowed)
The unwrapped gh api -X PATCH repos/o/r/releases/1 -f name=v1 is blocked (exit 2).
Cause
split_invocations in scripts/_invocations.py treats the argument of bash -c as one quoted word, so the guard never parses the command inside it.
- It treats every heredoc body as data. That is correct for
cat <<EOF, but wrong when the heredoc is stdin to bash, sh, zsh or sh -s.
- The same gap applies to every check that goes through
split_invocations: gh release create/delete/edit, the gh api release check, and the tag guard that shares the parser.
What closing it needs
- When an invocation is
bash|sh|zsh|dash with -c <string>, check <string> as a command in its own right, recursively.
- When a heredoc is fed to one of those shells (with or without
-s), check the heredoc body as commands instead of discarding it.
- Tests for both forms, in
guard-gh-release-invocations.test.sh and guard-tag-invocations.test.sh.
references/recovery-procedures.md (from #155) already tells the agent not to read the guard's silence on a wrapped command as permission.
Assisted by claude-code:claude-opus-5-5 — Session
guard-gh-release.pydoes not see a release command wrapped inbash -c "…"or fed to a shell through a heredoc, so both run unchecked.Measured with the guard at
23dca2don the branch of #155. The same holds atb410748, so this predates that PR:The unwrapped
gh api -X PATCH repos/o/r/releases/1 -f name=v1is blocked (exit 2).Cause
split_invocationsinscripts/_invocations.pytreats the argument ofbash -cas one quoted word, so the guard never parses the command inside it.cat <<EOF, but wrong when the heredoc is stdin tobash,sh,zshorsh -s.split_invocations:gh release create/delete/edit, thegh apirelease check, and the tag guard that shares the parser.What closing it needs
bash|sh|zsh|dashwith-c <string>, check<string>as a command in its own right, recursively.-s), check the heredoc body as commands instead of discarding it.guard-gh-release-invocations.test.shandguard-tag-invocations.test.sh.references/recovery-procedures.md(from #155) already tells the agent not to read the guard's silence on a wrapped command as permission.Assisted by claude-code:claude-opus-5-5 — Session