45395 - シコウサクゴ -

文字列一致のdenyルールはフラグ前置とAPIバリアントで貫通する:『守れている』という誤った安心感を疑う

2026-06-22
AI駆動開発
AI駆動開発
Claude Code
多層防御
セキュリティ
コードレビュー
Last updated:2026-06-21
13 Minutes
2506 Words

危険な操作を未然に止めるために、コマンドを 文字列一致(substring / prefix マッチ) で拒否する deny ルールを書く——よくある防御です。AI エージェントに破壊的な操作をさせないためのガードレールとしても、まず手が伸びる仕組みです。

ところが、ある PR をレビューしていて、この deny ルールに構造的な穴があることに気づきました。ルールは確かに存在し、テストもあり、一見すると「守れている」。しかし実際には、フラグの位置を変えるだけ、あるいは別の API バリアントを使うだけで、いとも簡単にすり抜けられたのです。

本記事は、「文字列一致の deny は多層防御の1層にすぎない」「テストが存在確認しかしていないと誤った安心感を生む」という学びの記録です。

事の発端:denyルールはあるのに、別経路で素通りする

レビュー対象には、危険な操作(保護されたブランチへの破壊的なマージ操作を想定してください)を止めるための deny ルールが設定されていました。設定は「禁止したいコマンド文字列」を列挙し、それに前方一致したら拒否する、という素直な作りです。

1
# 設定イメージ(前方一致で拒否)
2
deny:
3
- "version_control_mutation_cmd merge_target:*" # 保護対象へのマージを拒否
4
- "api_mutation_cmd *resource/*/merge*" # REST経由のマージAPIを拒否
5
allow:
6
- "version_control_mutation_cmd:*" # ただしツールは広く許可
7
- "api_client:*"

PR 本文は「これで危険なマージ経路は封鎖済み」と認識していました。実機検証もしていて、確かに拒否が発火しているように見えました。ところが、deny の網羅性を独立した観点で精査したところ、封鎖済みと認識されている経路の中に穴が見つかったのです。

穴その1:フラグを前置すると前方一致がすり抜ける

最初の穴は、フラグの位置でした。

deny ルール version_control_mutation_cmd merge_target:* は、コマンド文字列の 前方一致です。つまり「merge_target の直後にスペース」という語順を前提にしています。ところが、同じ操作はフラグを先に置いても成立します。

Terminal window
1
version_control_mutation_cmd merge_target # ← deny に一致して拒否される
2
version_control_mutation_cmd --no-fast-forward merge_target # ← フラグ前置で deny 不一致、素通り
3
version_control_mutation_cmd -m "message" merge_target # ← 同上

最初の形は deny にヒットしますが、フラグをターゲット名の前に置いた最も自然な語順は、前方一致から外れて allow 側(広く許可されたツール)で素通りしてしまいます。実機検証でたまたまフラグを後置していたために deny が発火して見え、「守れている」と誤認していた、というのが実態でした。

文字列の前方一致は、引数の語順という、コマンド仕様上いくらでも変えられる要素に依存します。語順を変えるだけでバイパスできるなら、それは防御として穴だらけです。

穴その2:別のAPIバリアントはパスにキーワードを含まない

ふたつめの穴は、API のバリアントでした。

deny ルール api_mutation_cmd *resource/*/merge* は、REST 風のパス(.../resource/123/merge のような形)にマッチさせる設計でした。ところが、同じ「マージする」という操作は、別の API バリアント——たとえば GraphQL の mutation——経由でも実行できます。

Terminal window
1
# REST 経由:パスに "merge" を含む → deny にヒット
2
api_mutation_cmd PUT resource/123/merge
3
4
# GraphQL 経由:パスは汎用エンドポイント、操作名はクエリ本文の中
5
api_variant_mutation -f query='mutation { mergeTarget(...) { ... } }'

GraphQL 経由のマージは、URL パスに resource/.../merge という文字列を含みません。操作の意図はクエリ本文の中にあるからです。結果として、REST パスを狙った deny ルールはこの経路を一切捕捉できず、allow の広いツール許可で素通りします。

「同じ操作に複数の到達経路がある」というのは、本番ガードの設計で何度も出てくる落とし穴です。文字列一致の deny は、列挙した1つの経路の1つの表記しか塞げません。別バリアントは設計者の視界の外で素通りします。

誤った安心感:テストが「存在」しか確認していない

このケースで最も危険だったのは、穴そのものよりも、テストが「守れている」という誤った安心感を生んでいたことでした。

deny ルールには対応するテストがありました。しかしそのテストは、version_control_mutation_cmd merge_target:* という deny ルールが 設定に存在することを確認しているだけでした。--no-fast-forward のようなフラグ前置の変種が実際にすり抜けるかどうかは、一切検証していなかったのです。

テストが緑なら、人は「この経路は守られている」と信じます。ところがテストが見ているのは「ルールが書いてある」ことであって、「ルールが意図した攻撃をすべて止める」ことではありません。この差は決定的です。存在確認のテストは、バイパスを検出できないどころか、バイパスがあることを覆い隠してしまうからです。

これは「exit code 0 = 正常動作ではない」と同じ構図です。テストが通った=守れている、ではありません。テストが何を検証しているかを見ないと、緑のテストがかえって油断を生みます。

学び:文字列一致のdenyは「うっかり防止」の1層にすぎない

このケースを普遍化すると、deny ルールの位置づけがはっきりします。文字列一致ベースの deny は、敵対者を止める主防御ではなく、「うっかり事故」を防ぐ多層防御の1層にすぎない、ということです。

substring / prefix マッチは、次の2つを構造的に網羅できません。

  1. 引数の語順依存:フラグの位置を変えるだけで前方一致が外れる
  2. 複数の到達経路:REST / GraphQL など、同じ操作の別バリアントを捕捉できない

だから、deny ルールに「これで完全に塞いだ」という期待を載せるのは危険です。deny は「普段の操作でうっかり危険コマンドを打つのを止める」程度の1層と割り切り、本当の防御は HITL(人間の承認)・コードレビュー・CI ゲートといった別の層に置くべきです。

防御の層性質止められるもの
文字列一致 deny1層・語順/経路に弱いうっかりの危険コマンド
バイパステスト(mutation test)deny の穴を可視化「守れている」という誤認
コードレビュー / CI ゲート意味的な検査意図的・変種を含む危険操作
HITL(人間の承認)最終防衛上記をすり抜けた全て

運用Tips:denyには必ずバイパステストを併設する

実務に落とすと、deny ルールを書くときの習慣は次のようになります。

  • deny ルールを足したら、「このルールを意図的にすり抜ける入力」をテストとして書く。フラグ前置(--flag target)、別バリアント(GraphQL など)、別経路を、実際に拒否されるかで検証する。「ルールが存在するか」では不十分。
  • すり抜けが見つかったら、塞げるものは塞ぎ、塞げないものは「既知の穴」として明記する。テストやドキュメントに「この変種は現状すり抜ける」と書いておくだけで、誤った安心感は消えます。沈黙が一番危険です。
  • deny を「主防御」と思わない。多層防御の1層と位置づけ、レビューと人間の承認を主防御に据える。AI エージェントにガードレールを与えるときも同じで、文字列ブロックは保険、最終的な歯止めは承認フローに持たせます。
1
## deny ルールを追加するときのチェック
2
3
1. 正常系(禁止したい操作)が拒否されることを確認
4
2. バイパス変種も拒否されることを確認:
5
- フラグを前置した語順(--flag target)
6
- 別の API バリアント(GraphQL / 別エンドポイント)
7
- 別経路(CLI / 直叩き / 別ツール)
8
3. 塞げない変種は「既知の穴」としてテスト/Docに明記
9
4. deny は1層。主防御はレビュー + 人間の承認に置く

まとめ:守るべきは「操作」であって「文字列」ではない

危険な操作を文字列一致で止めるのは手軽ですが、文字列一致が見ているのはコマンドの表記であって、操作の意図ではありません。だから、表記をちょっと変えるだけ(フラグ前置・別バリアント)で、同じ意図の操作がすり抜けます。

今回の学びをまとめます。

  1. 文字列一致の deny は、引数の語順複数経路に構造的に弱い
  2. テストが「ルールの存在」しか確認しないと、誤った安心感を生む
  3. deny ルールには必ずバイパステストを併設し、すり抜ける変種を可視化する
  4. 文字列一致の deny は多層防御の1層。主防御はレビューと人間の承認に置く

「ルールがあるから守れている」ではなく、「その操作の全表記・全経路を止められているか」を問う。文字列一致の deny を過信せず、バイパステストで穴を見える化しておくことが、静かなすり抜けを防ぐ習慣です。

Article title:文字列一致のdenyルールはフラグ前置とAPIバリアントで貫通する:『守れている』という誤った安心感を疑う
Article author:45395
Release time:2026-06-22

記事へのご質問・ご感想をお聞かせください

フィードバックを送る