株式会社ホコサキ

AIエージェントのターミナル操作をdcgで守る防御設計の現実

天京祐輔
天京祐輔
AIエージェントのターミナル操作をdcgで守る防御設計の現実

AIエージェントにターミナルを触らせるのは、もはや実験的な話ではなくなってきました。
Claude CodeやCursor、Codex CLIといったツールが実務に入り込み、「エージェントにコマンドを叩かせる」という構成が普通に選択肢に上がる時代です。

ただ、そこには見落としやすい怖さがあります。
DesktopCommanderMCPのようなターミナル操作MCPとdcg(Destructive Command Guard)を組み合わせたときの実際の挙動を追いながら、「エージェントに渡すターミナル権限をどこまで絞るか」という設計の問いに向き合います。
dcgが何をどう守り、どこで限界を迎えるかを正直に評価した上で、採用判断の軸を持ってもらうことがゴールです。

エージェントがターミナルを叩くとき、何が怖いのか

エージェントは慎重に確認してくれる、と思っていませんか。
実際のところ、それは構成次第で簡単に崩れます。

DesktopCommanderMCPは execute_command というツールとして広いシェルアクセスをエージェントに提供します。
これ自体は便利な設計なのですが、問題はそこにClaude Codeの --dangerously-skip-permissions フラグや自動実行モードが組み合わさったときです。
確認ダイアログが消え、エージェントが直接シェルを叩く構造になります。

dcgのREADMEは、この状況を「real pain(本当の痛み)」と表現しています。
git reset --hard や rm -rf ./src といった破壊的なコマンドをエージェントが実行してしまうケースを想定して作られたツールだ、という説明がREADMEの動機として書かれています。
「そんな命令をするはずがない」と思うかもしれませんが、ハルシネーションや文脈の誤認はもっと静かな形で起きます。
「古いテストデータを削除して」という指示に対して、エージェントが本番DBのテーブルを「古いテストデータ」と誤認するケースは、概念として十分ありえます。
エージェントは確率的に動くモデルであり、文脈の解釈を間違えることがある、というのは設計上の前提として持っておくべきことです。

DesktopCommanderMCPの execute_command は、まさに「何でも叩ける窓口」です。
OWASPのAI Agent Security Cheat Sheetでも、allowed_commands を空にした execute_command の設定は「危険な構成」として明示されています。
この権限をそのままエージェントに渡すことの危うさは、MCPの便利さとセットで理解しておく必要があります。

dcgの仕組み――4段階パイプラインとプロトコル検知の設計

dcgはRustで書かれたシェルラッパーで、AIエージェントの PreToolUse フックとして動作します。
エージェントがシェルコマンドを実行しようとする直前に割り込み、stdinでJSONを受け取って判定を下す、という構造です。

判定は4段階のパイプラインで行われます。
最初にキーワードベースのプレフィルタリングが走ります。
50以上のパック(git、docker、k8sなど)が登録されていますが、全パックのパターンを毎回チェックするのはコストが高い。
そこでまずキーワードで関係のないパックを高速に除外し、マッチしそうなパックだけを次の正規表現マッチングに渡します。
これによってサブミリ秒の判定速度を実現しています。

プロトコル検知の部分が、実務上もっとも重要な設計ポイントです。
Claude CodeとCodex CLIはどちらも似たようなフックペイロードを送ってきますが、dcgは src/hook.rs の detect_protocol 関数で両者を区別します。
判定ロジックはシンプルで、意図的に狭く設計されています。

シェルツール(Bash、bash、launch-process)で turn_id が空でなければ Codex として扱います。
シェルツールで tool_use_id はあるが turn_id がなければ、Claude互換のJSONパスに乗せます。
シェルツール以外のツールは、turn_id があっても Codex 扱いにはしません。
CopilotやGeminiのエンベロープ検知はCodexチェックより先に走るので、それぞれのプロトコル固有の処理が優先されます。

ブロック時の挙動はプロトコルによって異なります。
Claude互換パスでは、stdoutにJSON形式の拒否レスポンスを返し、stderrに人間が読めるカラフルな警告を出します。
Codex CLI(0.125.0以降)では、stdoutには何も出さず、exit code 2でstderrに理由を返します。
Codexがそれをモデルに見せる形です。
ブロックではなく確認プロンプトを出す「問い合わせモード」も用意されており、完全に止めるか人間に判断を委ねるかを使い分けられます。

Claude Codeへの登録は、設定ファイルに最小限の記述を追加するだけです。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "dcg"
          }
        ]
      }
    ]
  }
}

これで Bash ツールが呼ばれるたびに dcg が割り込む構成になります。
matcher に指定するツール名がエージェントの実際のツール名と一致していることが前提なので、この点は次の節で重要になってきます。

DesktopCommanderMCPとdcgを組み合わせて検証する

dcgがどう機能するかを理解するには、フックに渡ってくるペイロードの形を押さえておく必要があります。
dcgのプロトコル検知が想定するClaude互換ペイロードは、おおよそ次のような形です。

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf ./tmp"
  },
  "tool_use_id": "toolu_01XxXxXx"
}

tool_use_id があり、turn_id がない。
dcgの detect_protocol はこれをClaude互換パスと判定し、正常に割り込みます。
このシナリオでは dcg は有効に機能します。

ブロックが発動した場合、dcgのドキュメントが説明する動作としては、stderrに人間向けの警告、stdoutにClaude向けのJSON拒否レスポンスが出力されます。
エージェントはそのJSONを受け取り、コマンドを実行せずに処理を止める、という流れです。

問題は、すり抜けが起きるシナリオです。

Codexが unified_exec や command_execution 経由でコマンドを発行するケースでは、PreToolUseフック自体が発火しません。
dcgのドキュメントはこれを fail-open と明記しています。
「dcgが介入できない」のではなく「dcgが存在すら知らない」状態でコマンドが実行されます。
これは構造上の限界であり、dcg側でどうにかできる問題ではありません。

もう一つのすり抜けパターンは、tool_name の文字列がマッチしないケースです。

{
  "tool_name": "execute_command",
  "tool_input": {
    "command": "git reset --hard HEAD~1"
  },
  "tool_use_id": "toolu_02YyYyYy"
}

Claude Codeの設定で matcher に "Bash" を指定していた場合、tool_name が "execute_command" のペイロードはマッチしません。
dcgのフックは発火せず、コマンドはそのまま通過します。
DesktopCommanderMCPが実際にどのtool_nameを使うかは、MCPの実装によって異なります。
"Bash" 以外の名前が来る可能性がある以上、matcher の設定が実際のツール名と一致しているかを事前に確認することが必須です。

整理すると、dcgが有効に機能するのは「Claude互換のフックペイロードが来て、tool_nameがmatcherと一致するケース」です。
それ以外のルーティングでは、dcgは介入の機会すら得られません。
この構造的な限界を把握した上で使うことが、過信を防ぐ第一歩です。

dcgだけに頼らない――多層防御の中での位置づけと現時点の限界

dcgは「最後の砦」ではありません。
「気づきの層」として位置づけるのが正確です。

前の節で見たように、dcgはフックが発火しなければ何もできません。
パターンに登録されていないコマンドも素通りします。
docs.rsにはheredoc検知モジュールや難読化への対応を示す記述がありますが、すべての難読化パターンを網羅しているかどうかは公開情報からは確認できません。
「dcgが入っているから難読化も防げる」という前提は持たないほうが安全です。
メンテナンスの継続性も、オープンソースプロジェクトとして将来にわたって保証されているわけではありません。

これらの限界を踏まえると、dcgの上流に別の制約を置くことが重要です。

たとえば、エージェントが触れるディレクトリを読み取り専用マウントで絞る方法があります。
Dockerやdevcontainerを使っているなら、ボリュームマウントのオプションで書き込み権限を持つパスを明示的に限定できます。
DesktopCommanderMCPのような操作系MCPを使う場合は、MCPサーバー側のallowlistでツールを絞ることも有効です。
OWASPが示すように、allowed_commands を空にしたまま execute_command を渡すのは避けるべき構成です。

サンドボックスの考え方も重要です。
本番環境への直接アクセス権限がある状態でエージェントを動かすのは、dcgの有無にかかわらずリスクが高い。
エージェントが触れる環境を本番から切り離すことが、防御設計の土台になります。

dcgはその土台の上に乗せる追加の層です。
「dcgが警告を出せる状況を設計で作る」という順番が正しくて、dcgを入れた後も「どの層で何を守るか」という問いは消えません。

実務での採用判断――どんな環境なら入れる価値があるか

dcgを入れるかどうかの判断は、環境と運用の実態によって変わります。
正直なところ、「とりあえず入れておけば安心」というツールではありません。
ただ、ハマる環境では確実に価値があります。

有効なのは、ローカル開発環境でエージェントに広いターミナル権限を渡していて、かつCI/CDとは分離されているケースです。
自動実行モードを使っているが本番には繋がっていない、チームで設定ファイルを共有管理できる、そういう状況なら dcg は「うっかりミスへの気づき層」として機能します。
一度でも意図しない git reset --hard を経験したことがあるなら、試してみる価値は十分あります。

逆に過信が危険なのは、エージェントが本番DBやストレージに直接アクセスできる権限を持っているケースです。
そういう環境でdcgだけを頼りにするのは、上流の制約が何もないまま気づき層だけを置くことになります。
またCodexの unified_exec 経由でコマンドが発行される構成や、tool_name が Bash 以外の文字列で来るMCPを使っている場合は、そもそもフックが発火しないので dcg の恩恵を受けられません。

導入自体はシンプルです。
cargo install destructive_command_guard でビルドするか、リリースバイナリを取得してPATHに置きます。
次に、Claude Codeの設定ファイル(~/.claude/settings.json)にPreToolUseフックとして dcg を追加します。
このとき matcher に指定するツール名が、実際にエージェントが使うツール名と一致しているかを必ず確認してください。
ここがずれると、フックは登録されているのにまったく発火しない、という状態になります。
パック選択については、全パックを有効にするか git・filesystem など必要なものだけを選ぶかを決めます。
パックを絞ることで誤検知を減らせるので、最初は広めに入れて様子を見ながら調整するのが現実的です。

試してみる価値があるかどうかの判断軸は、「エージェントに渡している権限の広さ」と「上流の制約の厚さ」のバランスです。
権限が広くて上流の制約が薄いほど、dcgのような気づき層の価値は上がります。
逆に、サンドボックスや読み取り専用マウントがしっかり効いている環境なら、dcgは「あれば嬉しい」程度の位置づけになります。

どちらにせよ、「エージェントがターミナルを叩く」という構成を選んだ時点で、権限設計を一度きちんと見直す価値はあります。
dcgはその見直しのきっかけとして、まず試してみるには十分な入り口です。


株式会社ホコサキは、山口県宇部を拠点にAI活用支援・業務システム開発・DX推進に取り組んでいます。
AIエージェントの実務導入や、MCPを使った開発環境の設計支援についてもご相談を受け付けています。
ご興味があれば お問い合わせページ からお気軽にどうぞ。

    AIエージェントのターミナル操作をdcgで守る防御設計の現実 | 株式会社ホコサキ