Repository navigation
Approval gate: scope cd to subshells, read heredocs as the shell does; README timing wording - #298
Merged
Merged
Conversation
…edocs as the shell does From review of #297: - [P1] `(cd scratch); rm -f owner.txt` passed: the subshell's cd was taken to apply to the rm. The tokenizer now marks `( … )`, and the checker restores the directory after one. A cd is followed only where it surely applies: a literal existing directory, not in a pipeline, the background or before `||`; otherwise later paths are unknown and ask. - [P1] `echo '<<EOF'` + a newline + `rm -f owner.txt` passed: a pre-pass regex took quoted text for a heredoc and skipped the rm. Heredocs are now recognized in the tokenizer (unquoted `<<WORD`, `<<-`, quoted delimiters), their bodies skipped from the next line to the delimiter. - Tests for both and their variants; on the previous gate they fail on exactly the two reported bypasses. - [P2] README: model and tools do not overlap (the loop awaits each); one response's tool calls run concurrently, so their durations do not add. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
yuanhao
added a commit
that referenced
this pull request
Oct 11, 2026
…#299) From review of #298: an unquoted heredoc body is expanded by the shell, so `cat <<EOF` / `$(rm -f owner.txt)` / `EOF` ran the deletion without approval. The same held anywhere a substitution runs: `echo $(rm …)`, `"$(rm …)"`, backquotes, `<( … )` / `>( … )`, an assignment's value, inside `$(( … ))`, and `eval`. The tokenizer now collects what each command's substitutions run (also inside double quotes and unquoted heredoc bodies; a quoted or escaped delimiter keeps the body literal) and the checker reads it like any command, in that command's directory. `eval` of literal text is read the same way; of unknown text it asks. Everyday forms stay free: `$(date)`, `$(git rev-parse HEAD) > rev.txt`, `$(( 1 + 2 ))`. Tests: 22 cases; on the previous gate they fail on the reported heredoc. Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes from review of #297.
[P1] Subshell directory changes bypassed approval. With an exempt scratch folder,
(cd scratch); rm -f owner.txtwas allowed: the checker assumed the subshell'scdapplied to therm.( … ), and the checker restores the directory after one.cdis followed only where it surely applies: a literal directory that exists, not in a pipeline, the background or before||.[P1] Quoted heredoc text hid real commands.
echo '<<EOF', thenrm -f owner.txton the next line, thenEOF, was allowed: a regex pre-pass took the quoted text for a heredoc and skipped the deletion. Heredocs are now recognized inside the tokenizer:<<WORD,<<-WORDor quoted delimiter starts one;<<<) is data.Tests: new cases for both bugs and their variants:
cdin a pipe, in the background, before||, or to a missing folder;Run against the gate on main, the new tests fail on exactly the two reported bypasses; with the fix, all pass. The pi dialog test and
coding_agent's CI check also pass.[P2] README timing wording. Removed "tools overlap the model's time". The loop awaits each model response and then that response's tools, so they don't overlap. One response's tool calls run concurrently, so their durations can't simply be added.
🤖 Generated with Claude Code
https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG