ワイルドカードではなく意味解析を選ぶ
コーディングエージェントは、git reset --hard に対するワイルドカードルールのように、ワイルドカード照合による deny ルールに対応しています。ワイルドカードはコマンド文字列とパターンをそのまま比較するため、空白の入れ方、フラグの順序、コマンドの包み方が少し変わるだけで、ブロックが黙って効かなくなることがあります。フラグの並べ替え(rm -r -f /)、シェルによるラップ(sh -c "rm -rf /")、インタープリターの背後への隠蔽は、いずれも文字列照合をすり抜けます。
そこで CC Safety Net は、各コマンドをパースしたうえで、git、rm、Remove-Item、find、xargs、parallel の実際のオプション文法を理解するアナライザーに渡します。これにより判定は、コマンドの見た目ではなく実際の動作に基づいて行われます。
その分、実装は複雑になります。パーサーはシェルの構文を正しく扱う必要があり、対応するコマンドごとに専用のアナライザーが要ります。ただし、重要なコマンドほど単純な書き換えでは回避しにくくなります。処理の流れはアーキテクチャで、各アナライザーの詳しい挙動は解析エンジンで規定しています。
常時有効な保護を先頭に置く、1 つの固定順序
どの連携でも、ツール呼び出しは同じ順序のステージを通ります。そのうち 2 つのステージは、意図的にポリシースナップショットの読み込みより前に実行されます。ユーザーポリシーファイルとプロジェクトポリシーファイルの保護、そして Git メタデータの保護です。 この順序こそが要点です。設定を読み込んだ後に評価される保護は、結局その設定の強度までしか保証できません。そして、侵害されたエージェントが真っ先に編集しようとするのは、まさにその設定です。ポリシーファイルを読む前に拒否することで、この 2 つのガードは設定の状態を一切持たず、preset や override で緩められず、自分が守っているファイル自身によって無効化されることもありません。その代償として、これらの拒否は安全レベルもフォールバックの理由も報告できません。どちらもまだ判明していないからです。無条件の保証と引き換えに診断情報を減らす、意図的なトレードオフです。 一方、機密パスの保護は境界の反対側、スナップショットの後にあります。こちらは意図的にポリシーで制御できるようにしています。どのパスを機密とみなすかは本質的にローカルな判断であり、自分で deny path を追加できなければ役に立たないからです。 具体的なステージの一覧はアーキテクチャにあります。解析を完了できない場合は fail closed
CC Safety Net は、解析を完了できない場合にブロックする側へ倒します。- ガード内のどこかで例外が投げられた場合、どのエントリーポイントであっても、例外を投げたステージに紐づく拒否になります。
- ツール入力の上限やパーサーの処理上限を超えた場合は、どの安全レベルでも拒否になります。
- strict モードは fail closed の範囲をさらに広げ、パーサーが完全には理解できないコマンドと、検証できない破壊的対象までを対象にします。
エージェントが作業を続けられる拒否
拒否はエラー状態ではありません。実行中のセッションに、通常のツール結果としてエージェントへ届きます。この性質が、ブロックメッセージの書き方を決めています。 素っ気ない「permission denied」だけでは、エージェントは似たコマンドを試し続けるか、タスク全体を止めてしまいかねません。書き方を変えて試し続ければ、保護されていない形にたどり着いてしまうこともあり、エージェントのターンも浪費します。タスク全体を止めてしまえば、1 回の安全措置が作業の停止に変わってしまいます。 そこで各メッセージは、エージェントが次に取るべき安全な操作を示します。理由の欄では、そのコマンドが何をするはずだったのかを平易な言葉で示し、より安全な代替手段があればその名前を挙げます。各ルールには、末尾の指示を決める intent もあります。ブロックを報告して残りのタスクを続ける、示された代替手段に切り替える、対象を絞って明示的に指定し直す、操作をユーザーに委ねる、書き方を総当たりせずコマンドを組み立て直す、のいずれかです。内部エラーによる fail closed の拒否にも intent(再試行せず組み立て直す)があるため、ツール自身が予期せず失敗した場合も、エージェントは次の操作を判断できます。 この指示は、意図的に助言にとどめています。エージェントに従うことを強制するものではありません。強制はガードが担い、指示に従わない再試行も同じようにブロックします。メッセージの役割は、素直に従う道を最も簡単な選択肢にすることです。その結果、たいていの場合はセッションがブロックを吸収してそのまま進みます。メッセージの構成と intent の一覧は、仕組みにあります。依存関係を最小限に保つ
CC Safety Net は、実行時の依存関係を、遅延読み込みされる 1 つのパッケージだけに抑えています。構造情報はすべて、サードパーティの文法定義ではなく自前の上限付き POSIX パーサーと PowerShell パーサーから得ています。セグメントの分割、クオート、リダイレクト、コマンド置換、動的な語の出どころなどです。- 依存関係が小さいほど、サプライチェーンの攻撃対象領域も小さくなります。しかもパーサーは、攻撃者が最も混乱させたがる構成要素です。
- この解析には、汎用のトークナイザーが保持しない情報が必要です。どの語が展開に由来するか、どの対象が作業ディレクトリに固定されているか、どのクオート形式が使われたか、といった情報です。自前のパーサーなら、これらを解析結果として保持できます。
- パーサーの処理上限を設定項目ではなく固定値にできるため、リソース枯渇は際限のない問題ではなく、上限が決まっていてテスト可能な失敗モードになります。
- CC Safety Net を hook subprocess として実行するエージェントでは、シェルのツール呼び出しのたびにプロセスが起動し直されるため、起動時間が重要です。依存関係が少ないほどコールドスタートが速くなります。
rulebook という仕組み
以前のバージョンでは、カスタムルールを 1 つのプロジェクトファイル内にインライン JSON として保存していました。CC Safety Net がこれを rulebook の仕組みに置き換えたのは、次の 4 つの理由からです。- 共有できる:
rule addとrule updateは、指定した ref の GitHub リポジトリから rulebook を取得し、利用側自身のrules/<name>/rulebook.jsonに取り込みます。チームは JSON を手作業でコピーせずにブロックのポリシーを共有できます。取り込まれたコピーは利用側リポジトリ内の 1 つのファイルです。人間がそのまま読めますし、プルリクエストでレビューでき、更新の差分もそのまま確認できます。 - 完全性を検証できる:
rule addとrule updateは、ref をコミットまで解決し、リダイレクトを禁止したうえでバイト数と時間の上限を設けて HTTPS で取得し、スキーマ検証を行い、nameが設定元と一致することを確認します。rulebook_version: 2では、rulebook 自身のフィクスチャをその rulebook のルールに対して実行もします。いずれかに失敗した設定元は、何も書き込まれる前に拒否されます。取り込み後に上流の内容と再照合する仕組みはありません。そこから先は、リポジトリ内の他のファイルと同じくレビューで担保します。 - スコープを分けられる:rulebook は、設定ディレクトリが別々のユーザー(グローバル)スコープとプロジェクトスコープに対応します。
- 検証できる:rulebook の内容は、ブロックの判定に影響する前にスキーマ検証を受けます。
1 つの設定ではなく段階的なレベルにする
保護は単一のオン/オフではありません。standard、strict、paranoid という 3 つの preset として提供しています。「検証できないコマンドはブロックすべきか」という問いへの正直な答えは、そのコマンドがどこから来たかによって変わるからです。 standard は、人間が見ているセッションに最適化してあります。認識できる破壊的コマンドと機密情報へのアクセスはブロックしつつ、パースできないだけの無害なテキストは通すため、日々の作業を妨げません。ただし、敵対的な入力や動的な入力に対しては明示的にベストエフォートであり、コマンドがプロンプトインジェクションなど信頼できない経路から来る可能性がある場面には向きません。 strict と paranoid は、多少の手間と引き換えに確実性を買う設定です。strict は検証できないものをブロックし、paranoid はさらに、普段は問題ないが時に致命的になりうる種類の操作までブロックします。この設計では、standard 側にパーサーのヒューリスティックを足して穴をふさぐ、という方向はあえて採っていません。静的に解決できない対象は、推測を重ねても安全にはならないからです。新たな穴が見つかったときの標準的な対応は、strict または paranoid の fail closed なフィクスチャを追加することです。だからこそ、standard モードに残るリスクの類型を隠さず記録しています。 個々の機能は引き続き別々に設定でき、ルール単位の override を使えば standard でも strict 相当のルールを強制的に有効にできます。一方で、致命的な操作のルールと常時有効な保護は、どの override をもってしても弱められません。各レベルについてはモードを、それぞれの境界が具体的にどこにあるかは解析エンジンを参照してください。置き換えではなく多層防御
CC Safety Net は、完全なセキュリティ対策だとは主張しません。多層防御の一つのレイヤーという位置づけです。- 権限の deny ルールは、ユーザー自身が設定できる手軽なブロック手段です。CC Safety Net は権限システムの前に動作するため、deny ルールの設定内容にかかわらず、すべてのコマンドを検査します。
- OS レベルのサンドボックスは、ファイルシステムとネットワークへのアクセスを制限しますが、その境界の内側で行われる操作が破壊的かどうかまでは判断しません。サンドボックス内のディレクトリで実行した
git reset --hardは境界を越えませんが、未コミットの作業は失われます。
worktree での緩和
Git の linked worktree には、使い勝手の問題があります。worktree で作業している開発者は、その worktree のローカルな変更を捨てるためにgit checkout -- . や git reset --hard を実行したくなりますが、既定のルールはこれらをローカル破棄の操作としてブロックします。
そこで、ルールそのものは緩めていません。worktree モードが許可するのはローカル破棄だけで、linked worktree だと確実に検証できたディレクトリ内に限られます。しかも、git のコンテキストが別の場所へ向けられていない場合にかぎります。この設計を支えているのが検証の部分です。worktree のように見えるだけのディレクトリは対象外で、検証を完了できない場合はコマンドをブロックしたままにします。
緩和の範囲は意図的に狭くしてあります。リモートに影響する操作(force push、ブランチの削除、stash の破棄)はすべてブロックのままで、使い捨ての worktree の外にまで及びうるローカル破棄も同様です。正確な条件と緩和されないケースは解析エンジンを、モードの有効化方法はモードを参照してください。
次に読むページ
技術ガイドは、ユーザー向けのライフサイクルの説明から、設計の背景にある考え方へと順に降りていく構成です。このページはその 5 番目、最後にあたります。- 戻る:解析エンジンには、ここで述べたトレードオフから生まれる具体的な分類の挙動が、アーキテクチャには、それが正当化するガードの順序があります。
- 最初から読む:仕組みでは、同じ仕組みをユーザー向けの粒度で説明しています。
ready と degraded の取り決めは設定の復旧にあります。