Skip to content

Commit 46cdb45

Browse files
authored
fix(approval): contextual exemption for safe compound command patterns (#65)
* chore: add .worktrees to gitignore * fix(approval): add < and > to compound operator detection File redirection operators were missing from COMPOUND_OPERATORS_RE, allowing patterns like '| jq . > /tmp/exfil' to bypass the compound operator check. Single-quoted content is already stripped before scanning, so expressions like --jq '.id < 5' are not affected. * feat(approval): add extractCoreCommand to strip safe shell wrappers Strips known-safe shell constructs (2>&1, trailing pipes to read-only filters, leading cd prefix) from commands before compound operator checking. Unrecognized constructs remain and trigger fail-closed escalation to Haiku. * feat(approval): integrate extractCoreCommand into contextual exemption isContextualGhCommand now strips safe shell wrappers before checking for compound operators. Commands like 'gh api ... 2>&1 | head -5' are contextually exempted when targeting a related repo. Distinct logging tag 'core-extracted' enables forensic differentiation. * test(approval): add issue #58 reproduction case tests Verifies that the exact commands from issue #58 are handled correctly: 2>&1 cases are contextually exempted, process substitution cases still go to Haiku. * fix(approval): reject file-path args in safe pipe filters Safe pipe filters (head, tail, jq, etc.) can read files when given filename arguments, bypassing the stdin-only assumption. Reject filter args that look like file paths (starting with /, ~, or .) to prevent this vector. Also clarifies doc comments and adds an explanatory comment for the intentional extractCoreCommand duplication in the logging block. * fix(approval): tighten extractCoreCommand per PR review findings - Remove `cat` from SAFE_PIPE_FILTERS to prevent relative filename bypass - Update hasPathArg regex to catch double-quoted paths (e.g., "/etc/passwd") - Replace greedy pipe regex with quote-aware scan to handle `|` inside single-quoted jq expressions (e.g., jq '.items | .id') - Add 5 new tests covering all edge cases from PR review * docs: expand CLI best practices for gh body/payload handling Clarify that all arbitrary text content must go through temp files, not inline arguments. Add explicit patterns for gh high-level commands (--body-file) and gh api calls (--input with JSON payload). Prohibit -F body=@file which still triggers the classifier. * fix(approval): close single-quoted path bypass in pipe filter args Run hasPathArg on original filterArgs instead of single-quote-stripped version so paths like '/etc/passwd' are still caught. Refine path regex to require . be followed by / to distinguish ./config (path) from .field (jq expression). Remove duplicate test. * fix(approval): use --input instead of --body-file in gh api test case gh api uses --input for request bodies, not --body-file. Aligns test with actual gh api usage and CLAUDE.md CLI best practices.
1 parent 489d9cd commit 46cdb45

4 files changed

Lines changed: 484 additions & 11 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ approval/node_modules/
22
.DS_Store
33
*.bun-build
44
.worktrees/.worktrees/
5+
.worktrees/

CLAUDE.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,12 @@ PR bodies must include `Closes #N` (or equivalent GitHub closing keyword) so the
273273

274274
### CLI Best Practices
275275

276-
When using `gh` commands that accept a body (e.g., `gh issue comment`, `gh pr create`), prefer `--body-file` with a temporary file over inline `--body` strings. Inline bodies with embedded code blocks or special characters can trigger the command approval classifier unnecessarily.
276+
Never pass arbitrary text content inline on a `gh` command line. Inline `--body` strings, `-F field=value` arguments, and heredoc constructs containing special characters trigger the command approval classifier unnecessarily. The goal is to keep user-authored text out of the command string by any means necessary.
277277

278-
When writing content to a file and then using it in a subsequent command (e.g., writing a temp file then passing it to `gh issue comment --body-file`), use **separate Bash tool invocations** rather than combining them into a single compound command. Compound commands (using `&&`, `;`, heredoc chains) are more likely to trigger the command approval classifier. Two simple commands pass through faster than one compound command.
278+
**Always:** Write content to a temp file first (using the Write tool), then reference it from the `gh` command in a **separate** Bash invocation. Use whichever mechanism fits the command:
279+
280+
- `--body-file /tmp/body.md``gh issue comment`, `gh pr create`, `gh pr edit`, etc.
281+
- `--input /tmp/payload.json``gh api` calls
282+
- `--body "$(cat /tmp/body.md)"` — fallback for any command that lacks a file flag
283+
284+
These can be combined as needed. The only rule is: no literal text in the command string.

0 commit comments

Comments
 (0)