refactor(claude): 権限設定の棚卸しとBash実行ガードのフック切り出し - #78
Merged
Conversation
settings.json に埋め込んだワンライナーだった PreToolUse フックを dot_claude/hooks/bash-guard.sh に独立させ、検知内容を強化した。 切り出した理由は、フラグの位置に依存しない検知を書けるようにするため。 permissions.deny のパターンは前方一致なので、Bash(git push --force *) では フラグを末尾に置いた形(git push origin master --force)を捕まえられない。 破壊的操作の防止はフック側でしか実装できない。 - deny: force push(位置を問わない)、reset --hard、clean -f/--force、 filter-repo/filter-branch、gh repo delete - ask: サンドボックス外実行、gh auth token、gh secret/variable の変更、 gh api の書き込みメソッド、git -c、GIT_SSH_COMMAND=/GIT_CONFIG_*=、 --upload-pack/--receive-pack/--exec-path - 通す: --force-with-lease、clean -n、gh api の GET、git -C - jq 不在時や入力 JSON の解析失敗時は ask を返して fail-closed にする git -C を ask 対象から外したのは、作業ディレクトリを変えるだけで無害な一方、 従来 deny に入っていたために同種の許可エントリがプロジェクト設定に31件も 溜まっていたため。任意コマンドを仕込める git -c は ask のまま残す。 誤検知を2つ潰した。どちらも実際にこのコミット自身を拒否したもの。 - ヒアドキュメントの本文を検査対象から外した。含めたままだと、この コミットメッセージのように本文で破壊的コマンドに言及しただけで拒否される - フラグの探索範囲を git 呼び出しの引数部分に限定した。区切り(; | & 改行)を またがないので、後続コマンドの -f を force フラグと誤認しない tests/bash-guard.test.sh で37ケースを検証する。CI にも同名のジョブを追加し、 shellcheck.sh の対象に dot_claude/hooks/*.sh を含めた。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
全プロジェクトの許可エントリを棚卸ししたところ、355件のうち実効性が あったのは71件だけだった。sandbox.autoAllowBashIfSandboxed が true なので サンドボックス内で完結する Bash は許可エントリなしで通り、Bash(go test *) や Bash(gofmt *) のようなエントリは一切効いていない。8/8 にサンドボックスを 導入して以降、Bash 実行の22%がサンドボックス外に出て毎回プロンプトになり、 「常に許可」で書かれたエントリだけが積み上がっていた。 - allow: git/gh の粒度別エントリを Bash(git *) / Bash(gh *) に集約する。 破壊的な操作の判定は bash-guard.sh 側に持たせたので、パターンを細かく 並べても意味がない - deny: .env 系のグロブが逆転していたのを修正する。**/.env.* は アンダースコア区切りの .env_prod / .env_stg にマッチせず本物の秘密が 読める一方、無害な .env.example を塞いでいた。実在するファイル名に 合わせて明示列挙に変える - deny: ~/.ssh/id_* を ~/.ssh/** に広げ、~/.config/gh/hosts.yml を追加する - deny: git filter-repo/filter-branch と gh repo delete を追加する - deny: 位置依存で機能していなかった git の force push 系パターンを削除する (bash-guard.sh が位置を問わず deny する) - sandbox.allowWrite: golangci-lint / Yarn / mise のキャッシュを追加する。 golangci-lint はキャッシュに書けず58回失敗していた - CLAUDE.md: 一時ファイルは /tmp ではなく $TMPDIR に置く、git/gh は ビルドやテストと同じコマンドに混ぜない、昇格は実際に失敗してから行う、 という運用ルールを明記する git/gh をビルドやテストと混ぜない理由は、sandbox.excludedCommands が コマンド全文に対するマッチだから。gh をループやパイプの中で呼ぶと除外が 効かずサンドボックス内で走り、~/.config/gh を読めずに失敗する(履歴上26件)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 20, 2026
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.
概要
Claude Code の権限設定とサンドボックス設定を全プロジェクト分棚卸しし、実効性のないエントリを削って、実際に機能していなかった防御をフック側に移した。
~/.claude/projects/*/*.jsonlの全トランスクリプト(Bash 実行 7,969 件)を集計した結果が出発点。サンドボックスを導入した 8/8 以降、実行の22%がサンドボックス外に出て毎回承認プロンプトになっていた。プロンプトのたびに「常に許可」で書かれたエントリが積み上がり、許可リストは355件に達していたが、実効性があったのは71件だけだった。
変更点
許可リストの整理
sandbox.autoAllowBashIfSandboxed: trueなので、サンドボックス内で完結する Bash は許可エントリなしで通る。Bash(go test *)Bash(gofmt *)Bash(go vet *)のようなエントリは一度も効いていなかった。git/gh の粒度別エントリもBash(git *)/Bash(gh *)に集約した(破壊的操作の判定はフックが持つため、パターンを細かく並べても意味がない)。プロジェクト側の設定(このリポジトリの管理外)も同時に 355 → 71 件に整理した。内訳は、一回限りの具体コマンド 142件、サンドボックス内で自動許可されるもの 60件、フックが ask を強制するため効かない
git -C31件。実際に開いていた穴を塞ぐ
.env系グロブの逆転:**/.env.*はアンダースコア区切りの.env_prod/.env_stgにマッチせず、本物の秘密が読める状態だった。一方で無害な.env.exampleは塞いでいた。実在するファイル名に合わせて明示列挙に変更Bash(git push --force *)は前方一致なので、フラグを末尾に置いたgit push origin master --forceを捕まえられなかった~/.sshの読み取り:~/.ssh/id_*のみ deny だったためconfig/known_hostsは読めた。~/.ssh/**に拡大し、~/.config/gh/hosts.ymlも追加awsの広すぎるワイルドカード(プロジェクト側):Bash(aws ec2 *)Bash(aws lambda *)Bash(aws ses *)がterminate-instances/delete-function/send-emailまで通していたため、読み取り専用の動詞に絞ったフックの切り出しと強化
settings.jsonに埋め込んだワンライナーをdot_claude/hooks/bash-guard.shに独立させた。フラグの位置に依存しない検知は permissions のパターンでは書けないため、破壊的操作の防止はここに集約する。reset --hard、clean -f/--force、filter-repo/filter-branch、gh repo deletegh auth token、gh secret/variableの変更、gh apiの書き込みメソッド、git -c、GIT_SSH_COMMAND=/GIT_CONFIG_*=、--upload-pack/--receive-pack/--exec-path--force-with-lease、clean -n、gh apiの GET、git -Cgit -Cを ask から外したのは、作業ディレクトリを変えるだけで無害な一方、従来 deny に入っていたために同種の許可エントリが31件も溜まっていたため。任意コマンドを仕込めるgit -cは ask のまま。サンドボックスの書き込み先を追加
golangci-lintがキャッシュに書けず58回失敗していた。~/Library/Caches/golangci-lint/Yarn/miseをallowWriteに追加。運用ルールを CLAUDE.md に明記
サンドボックス外に出た637件の Go コマンドの内訳は、
/tmp直下への書き込み128件、git/gh との複合96件、読み取りのみで昇格の必要がなかったもの26件。設定ではなく運用で減らせるものが多いため、以下をルール化した。/tmpではなく$TMPDIRに置くgit/ghはビルドやテストと同じコマンドに混ぜない(sandbox.excludedCommandsはコマンド全文に対するマッチなので、ghをループやパイプの中で呼ぶと除外が効かず~/.config/ghを読めずに失敗する。履歴上26件)潰した誤検知
書いたフックが実際にこの PR のコミット自身を2回拒否した。どちらもテストケースに落としてある。
git push origin master --forceに言及しただけで deny された。本文は実行されるコマンドではないので検査対象から外したgit push origin main && grep -f patterns.txt logのgrep -fを force フラグと誤認した。区切り(; | &改行)をまたがないよう、git 呼び出しの引数部分に限定したテスト
tests/bash-guard.test.shに37ケース。既存のtests/*.test.shの作法に合わせ、CI(ubuntu / macOS)にもジョブを追加した。shellcheck.shの対象にdot_claude/hooks/*.shを含めた。shellcheck.shは終了コード 0。確認してほしいこと
.env系の deny を明示列挙にしたので、将来.env.<新しい環境名>を作ったときに列挙漏れが起きる。グロブの否定が書けないためこの形にしたが、運用として許容できるかBash(git *)/Bash(gh *)への集約は、破壊的操作の判定をフック1点に寄せる前提。フックが壊れたときの影響範囲が広がるトレードオフがあるallowWriteの追加は Claude Code の再起動後に効く未対応
~/.claude/hooks/herdr-agent-state.shは chezmoi 管理外のまま。hooks/が管理下に入ったので、新しいマシンでchezmoi applyすると SessionStart フックだけ欠ける。別 PR で追加する。🤖 Generated with Claude Code