fix(claude): heredoc本文の説明を破壊的コマンドとして判定しない - #72
Merged
Conversation
hook が改行ごとにコマンド行として字句解析していたため、heredoc 本文に禁止操作を 説明として書いただけで実行コマンドと誤検出し、exit 2 でブロックしていた。 字句解析の前に heredoc 本文と終端行を取り除く。開始は改行まで溜めて宣言順に消費 するので、1 行に複数の heredoc を書いても本文の対応が崩れない。`<<-` は終端行の 先頭タブを剥がして一致させる。区切り語を quote していない heredoc だけは本文中の `$(...)` / `` `...` `` が展開されて実行されるため、置換部分だけを取り出して再帰的 に見る。本文では引用符が展開を止めないので、この経路は quote を無視して拾う。 本文を落とすのは判定対象を減らす操作なので、`<<` が heredoc だと確定できたとき だけに限る。誤認すると後続の実コマンドを本文として捨て、ブロックすべき操作を 素通しする (fail-open) ため、`<<` に見えるだけの構文をすべて入口で分ける: - `<<<` は herestring として 3 文字まとめて消費する。2 文字目から `<<` を読み直す と heredoc 宣言になってしまう - `((` は算術評価コマンドとして読み飛ばす。コマンド位置の判定を直前の 1 文字から 直前の語へ広げ、`if (( x << 2 ))` のように予約語の後ろに来る形も含める - 語頭の `#` から行末まではコメントとして落とす。`<<` を含んでいても宣言ではなく、 コメント本文がコマンドとして解釈されることもなくなる - 区切り語で閉じないまま入力が尽きた場合は誤認を疑い、本文を捨てずにコマンドとして 読み直す コマンド位置の判定に使う末尾は固定長の窓で持つ。出力済み全体を毎回連結すると、 `((` を多用する入力で入力長に対して二次オーダーになる。 heredoc の開始行に同居するコマンド・終端行より後ろのコマンド・heredoc より前の コマンドは従来どおりブロックする。 Co-authored-by: Claude Opus 5 <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.
背景
PR #71 で導入した
claude/hooks/destructive-command-guard.pyは、stdin の PreToolUse JSON を字句解析して破壊的コマンドを判定する。この hook が改行ごとにコマンド行として解析していたため、heredoc 本文に禁止操作を説明として書いただけでブロックされる実測不具合があった。上の入力は本文が実行コマンドと誤検出され exit 2 になっていた。
やったこと
heredoc 本文の除外
字句解析の前に heredoc 本文と終端行を取り除く。開始は改行まで溜めて宣言順に消費するので、1 行に複数の heredoc を書いても本文の対応が崩れない。
<<-は終端行の先頭タブを剥がして一致させる。未引用の区切り語での置換検査
区切り語を quote していない heredoc だけは、本文中の
$(...)/`...`が展開されて実行される。この場合だけ置換部分を取り出して再帰的に判定する。本文では引用符が展開を止めないため、この経路は quote を無視して置換を拾う。cat <<'EOF'の本文に$(git push --force)cat <<EOFの本文に$(git push --force)誤認による fail-open の封じ込め(B1〜B3・未終端)
本文を落とす操作は判定対象を減らすため、
<<を heredoc と確定できたときだけに限る。誤認すると後続の実コマンドを本文として捨て、ブロックすべき操作を素通しする(fail-open)ため、<<に見えるだけの構文を入口で分けた。<<<(herestring)の 2 文字目から<<を読み直して宣言と誤認if (( x << 2 ))など制御構文の後ろの算術評価<<(行頭・行中とも)#から行末までをコメントとして落とすB3 の副産物として
echo ok # 補足; rm -rf /usr/localの誤検出も解消した。語中の#(URL のフラグメント)はコメントとして扱わない。コマンド位置の判定に使う末尾は固定長の窓で保持する。出力済み全体を毎回連結すると
((を多用する入力で入力長に対して二次オーダーになるため(260KB の入力で 1.05s → 0.126s)。維持した契約
quote / 未 quote 本文の扱い、複数 heredoc、
<<-、算術式、--force-with-leaseの allow、exit code は 0(通過)と 2(ブロック)のみ、入力不正・command 不在は pass。heredoc の開始行に同居するコマンド・終端行より後ろのコマンド・heredoc より前のコマンドは従来どおりブロックする。動作確認方法
いずれも pipe で exit code を隠さずに実行し、exit 0 を確認済み。
python3 claude/hooks/destructive-command-guard.test.pybats setup/tests/*.batspython3 -m json.tool claude/settings.json<<-終端・終端なし heredoc・#が;に優先・cat <<<"$x"・if ((1<<2))・! ((...))が実装の前提と一致テストは実際の PreToolUse JSON を stdin で渡す統合テストと、
inspect()の unit テストの両方を持つ。追加分は BLOCKED 11 件(herestring 後・if/while/until/!/forの算術後・コメント行後・行中コメント後・終端なし heredoc の危険本文)、allow 側は heredoc 本文 13 件とコメント 6 件。CI は
.github/workflows/setup-check.ymlがclaude/hooks/**を対象に hook の unit test と bats を実行するため、workflow の変更は不要。レビュー