前回の続き
前回は、Cloudflare security-audit skill を Drupal の基盤モジュールに走らせ、confirmed 0 件・needs_validation 18 件・rejected 1 件で終わったところまで書きました。サンドボックスが無い環境では実行を伴う確認を一切しない設計なので、confirmed が出ないのは想定どおりです。その 18 件を実際に潰したら何が起きたかが今回の内容です。
結論から書くと、18 件は「未確認」であって「安全」でも「危険」でもありませんでした。現在のソースで再検証すると、3 件は既に是正済み、15 件は実在の欠陥で、15 件すべてをコード修正しました。翌日 1 日の作業です。
まず現在のソースで 18 件を再検証した
パイロットは意図的に古い commit を対象にしていました。既に是正済みの欠陥を陽性対照として置くためです。したがって skill の指摘をそのまま是正に着手すると、既に直っているものを二度直すことになります。
全 18 件を、現在のソースに対して読み直してから着手しました。 その結果が次の表です。
| 区分 | 件数 | 内容 |
|---|---|---|
| 既に是正済み | 3 | 2 件は陽性対照そのもの(同じ根本原因を 2 つの角度から指摘していた)。1 件は別の監査で 9 月 5 日に撤去済みの設計 |
| 実在の欠陥 | 15 | 現在のソースでも指摘どおりの挙動が残っていた |
分母に注意が要ります。「18 件中 15 件」は skill が needs_validation と名付けた起票の数で、是正 commit は 13 本(関連する 2 件を 1 commit にまとめた組が 2 つ)、追加したテストは別の数です。
15 件の中身を列挙はしません。既刊の記事はどれも「1 記事 1 つの発見」で書いており、この続編に畳み込むと個々の記事が成立しなくなるからです。傾向だけ書くと、暗号化に失敗したとき平文で保存してしまう fail-open が 1 件、緊急停止スイッチの状態がデプロイで静かに戻る・操作画面が表示時点と送信時点の状態の食い違いを見ていない、が 2 件、削除失敗を握り潰して DB の参照だけ消えるストレージ処理が 1 件、監視側の死角(透かしを配信前に進める・許可リストに無いキューを見ない・送っていないのに成功と記録する)が 3 件、失敗した送信を上限無しに再投入する処理が 1 件、匿名到達できる入口の上限抜け(?page・DNS 参照・ログ膨張・画像デコード寸法)が 4 件、残り 3 件がフィルタとキャッシュパージと識別子の精度です。
いずれも「ソース上こう読める」を skill が書き、「実行して確かめる」を自前の否定テスト段階で行い、Kernel/Unit テストを足して固定しました。是正後のカスタムモジュール全体のテストは fail 0・error 0 です。
回収漏れが、規則を書いた本人に再発した
ここからが今回の本題です。
当社の監査手順には、こう書いてあります。
未検証・範囲外・別途とした項目は、回収一覧に fingerprint で起票する
これは 2026 年 9 月 4 日の監査で「未検証点」と書かれた 68 件が回収の走査語に含まれず、どの棚卸しにも一度も載っていなかったという失敗から作った規則です。散文の走査語に頼ると漏れる。だから行の形を固定して機械で列挙する、という設計でした。
skill の出力には、needs_validation 18 件のほかに、候補として最終検証まで到達しなかった領域が 11 件ありました。blocked(決定的な事実がソースの外にあり verifier が部分レビューで止めた)4 件、deferred(critic が指摘したが quick プロファイルでは 2 回目の wave を回さず保留した)4 件、out_of_scope(対象モジュールの外)3 件です。
このセッションは当初、18 件だけを起票し、11 件を落としました。 skill が needs_validation と名付けたものだけを「未検証」と読み、blocked・deferred・out_of_scope を規則の対象外と扱っていたのです。運営者から「これは放置していいものではない、なぜ報告しないのか」と指摘を受けて追加起票しました。
規則の文面は「未検証・範囲外・別途」です。out_of_scope は文字どおり範囲外、deferred は文字どおり別途です。規則の対象そのものなのに、ツールが付けた区分名に引きずられて「これは needs_validation ではない」と読んでいました。 68 件を落とした失敗と構造が同じです。走査語が「未検証点:」だったから漏れたのではなく、「判定に至らなかったもの」を区分名で拾おうとする限り、区分名が変わるたびに同じ穴が開きます。
11 件を実際に調べたら何が出たか
11 件は「脆弱性が無いと確認された領域」ではなく「判定に至らなかった領域」です。skill のサンドボックスは vendor/・Drupal core・contrib モジュール・インフラ設定を見ないので、そこで止まっていただけのものが多く含まれていました。実際のソースと稼働環境(vendor の実読、本番 SSH、AWS CLI、Cloudflare の設定画面)で全件を調べました。
| 区分 | 件数 | 結果 |
|---|---|---|
blocked | 4 | 実在のバグ 1(HTTP クライアントのリダイレクト追跡。次回の記事)、実在の過剰権限 1(オブジェクトストレージの IAM。次々回の記事)、問題なし 2(マルウェアスキャンの fail-closed 契約、オリジン認証ミドルウェアの優先順位と本番シークレット) |
deferred | 4 | 問題なし 2(検索結果の公開状態の再検証、署名付き URL 発行元の所有者確認)、実在の欠陥 2(同じ領域を 2 つの観点で起票。検索エンジン通知用の鍵をローテーションするとルートが追従しない) |
out_of_scope | 3 | 残存リスクを運営者が受容 1(祝日 CSV の自動取り込み)、インフラ構成の変更で対応中 1、判定を差し戻し 1(後述) |
blocked 4 件のうち 2 件が実在で、しかも 1 件は本番の権限の話でした。skill が「ソースの外だから判定しない」と止めたところに、実在の穴がありました。「判定に至らなかった」を「問題なし」と読み替えていたら、この 2 件は見つかっていません。
「判定を差し戻し 1」は、いったん「実在の欠陥として是正」と記録した後、記事にするための裏取りで、根拠にした実測が別の分岐を測っていたと分かったものです。測り直しを起票して、判定を戻しました。是正したつもりの記録を、記事化のために読み直して初めて気付いています。
なぜ気付きにくいのか
skill の出力は形式が整っています。findings.json はスキーマ検査を通り、REPORT.md は JSON から導出され、needs_validation の各件には blocker と検証計画が付いています。整った出力を前にすると、その出力の外側にあるものを数え忘れます。
前回書いたとおり、skill は coverage ledger で「見ていない単位」を機械的に残します。blocked・deferred・out_of_scope はまさにそれです。ledger は正しく残していました。落としたのは、それを自前の回収一覧へ転記する人間側の判断です。
skill が構造で守ってくれるのは skill の内側だけで、skill の出力を自前の手順へ渡す境界は、自前の規律に戻ります。そこに 68 件を落としたのと同じ癖が残っていました。
是正
- 起票の対象が「判定に至らなかったもの全部」であることを、監査記録に明文化しました。 手順書の文面自体は変えていません。「未検証・範囲外・別途」という文面は元から 11 件を含んでいて、足りなかったのは文面ではなく読み方だったからです。ツールの区分名を列挙する形にもしていません。区分名で列挙すると、次に導入するツールが別の名前を使った時点で同じことが起きます
- 11 件は全件を fingerprint で起票し、調査結果を
ok:で閉じました。列挙スクリプトで「起票と解決の差」を機械確認しています - 差し戻した 1 件は、新しい fingerprint で起票し直しました。記録上「是正済み」と「根拠不成立」が両立しないようにするためです
まとめ
needs_validationは「未確認」であって「安全」でも「危険」でもありません。当社の 18 件は、現在のソースで再検証して 15 件が実在でした- skill の対象が古い commit なら、着手前に現在のソースで読み直してください。18 件のうち 3 件は既に直っていました
blocked・deferred・out_of_scopeは「判定に至らなかった」領域です。当社の 11 件からは実在のバグ 1・過剰権限 1・欠陥 2 が出ました。「判定なし」を「問題なし」と読まないでください- 回収漏れの規則を書いた本人が、ツールの区分名に引きずられて 11 件を落としかけました。回収の基準は「判定に至らなかったもの全部」で、区分名で列挙しないでください
- 「是正済み」と記録したものも、記事にするために読み直すと根拠が崩れることがあります。今回 1 件を差し戻しました

