1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

毎日137件のCVE、Claudeにコードを読ませて「本当に対応が必要な2件」まで絞るツールを作った

1
Posted at

この記事の対象読者

  • Dependabotなどの脆弱性通知が溜まりすぎて、うんざりしている方
  • CVE対応の割り込みで、本来の開発が進まず疲弊している方
  • 通知が多すぎて、正直もう無視するようになってしまっている方
  • 破壊的変更が怖くてメジャーバージョンアップを塩漬けにしたまま、後ろめたさを抱えている方
  • 専任のセキュリティ担当がおらず、開発者が脆弱性対応まで兼ねている現場の方
  • レガシー案件を保守していて、package.jsonに載らない依存(CDN読み込み・手動配置のライブラリ・ミドルウェア)に不安がある方

はじめに

先日、毎日137件公開されるCVE、正直ぜんぶ無視していませんか? という記事を書きました。ありがたいことに多くの反応をいただいたのですが、その中にこういう声がありました。

重要なアラートだけ通知する仕組みを作ったことがあるが、結局形骸化してしまった

これ、本当にその通りだと思います。仕組みを作ること自体は難しくない。難しいのは、1年後、2年後も回り続けていることです。作った直後は誰でも回せます。仕組みが静かに死ぬのは、担当者が変わり、例外が積み重なり、判断した当時のコードが別物になった頃です。

今回はこの「形骸化する問題」への自分なりの答えとして、CVEトリアージツールを実装したので紹介します。

リポジトリはこちら: high-tech-r/quiet-cve(MITライセンス)

何を作ったか

quiet-cve — プロジェクトが抱えるCVEを「本当に対応が必要なものだけ」に絞り込む、Claude Code用のツールです。

導入は、調べたいプロジェクトの中にディレクトリごと置くだけ。

your-project/
├── src/
├── package.json
└── quiet-cve/      ← これを置くだけ
cd your-project
git clone --depth 1 https://github.com/high-tech-r/quiet-cve.git
rm -rf quiet-cve/.git

実行は、Claude Codeにこう頼むだけです。

quiet-cve で CVE チェックして

サーバー不要、APIキー不要(OSV.dev・NVDとも認証なしで使えます)、Python 3.11+があれば追加の依存もありません。

動かすとこうなる

同梱のテストプロジェクト(16パッケージ)での実測値です。

段階 件数
OSV.dev が返した脆弱性 151
重複統合後(GHSAとPYSECの同一CVEをまとめる) 89
🔴 要対応 2
🟡 様子見 42
⚪ 影響なし 45

89件すべてに判断理由が付いたうえで、人間が読むべきものが2件まで絞られる、という状態です。

「アラートを減らす」だけなら、しきい値を上げれば誰でもできます。ポイントは、捨てた45件の側にも根拠が残っていることです。

どんなレポートが出るのか

reports/2026-08-07.md として生成されるレポートの抜粋です。

🔴 要対応の例

CVE-2020-1747 — PyYAML@5.3

深刻度 CVSS 9.8 (CRITICAL)
依存 直接依存(backend/requirements.txt)
修正版 5.3.1

何が起きるか: yaml.load()Loader 指定なしで呼ぶと、YAML内に埋め込まれたPythonオブジェクト構築命令がそのまま実行されます。攻撃者がYAMLの中身を制御できる場合、アプリケーション権限での任意コード実行になります。

判断の根拠:

  • backend/loader.py:1import yaml
  • backend/loader.py:6yaml.load(request.data)Loader 引数なしで呼び出し
  • backend/loader.py:6 — 引数が request.data(HTTPリクエストボディ)であり、攻撃者が内容を完全に制御できる

推奨アクション: バージョンを上げるだけでは不十分です。loader.py:6yaml.safe_load() に変更すれば、PyYAMLのバージョンに関係なくこの攻撃経路は塞がれます。

判定にはすべて、根拠となるファイル名と行番号(file:line 形式)が付きます。「なんとなく危なそう」ではなく、どのファイルの何行目がなぜ危ないのかまで出ます。

⚪ 影響なしの例

捨てたほうにも同じだけ理由が付きます。

CVE パッケージ CVSS 影響なしと判断した理由
CVE-2021-23337 lodash@4.17.20 8.1 脆弱なのは _.templateapp.js:1 でimportしているが、使用しているのは _.merge のみで _.template の呼び出しは0件
CVE-2021-44906 minimist@1.2.0 9.8 推移的依存。直接import 0件、かつCLI引数解析を行うコードが存在しない

CVSS 9.8でも、使っていなければ「影響なし」。逆にCVSSが低くても実際に外部入力を渡していれば「要対応」。スコアではなく、自分のコードでの実態で決まるのがこのツールの中核です。

なぜ「コードを読む」のか

Dependabotをはじめとする既存のSCAツールが下手なのは検知ではありません。検知した後の扱いが無いことです。

アラートが数百件並び、そのうち本当に危ないものを知る手段がなく、dismissには期限がないので一度消したものは二度と戻ってこない。依存関係のCVEを全部通知すると、多くの現場では数週間で全部無視されるようになります。

実は私自身は現在、Dependabotの通知をすべて確認し、対応方法をAIに尋ねる運用でかなり楽にはなっています。検知と調査のコストは、AIのおかげでもう大した問題ではありません。それでも残るのが対応そのもののコストです。

脆弱性を塞ぐためのバージョンアップ、特にメジャーバージョンアップは破壊的変更を伴うことが多い。本来到達不可能な機能の脆弱性のために、毎回その工数をかけるメリットは実際ありません。合理的なのは、到達可能な脆弱性が見つかったタイミングでまとめてバージョンアップすることです。本当に必要なバージョンアップだけに絞れれば、対応工数はプロジェクト規模によっては数百万〜数千万円の単位で変わってきます。

念のため正確に言うと、これで節約されるのは「免除」ではなく延期です。古いメジャーに留まるほど、いつか払うアップグレード費用は膨らみます。変わるのは支払いのタイミングの主導権 —— 「CVEが出るたびに割り込みで強制される」を「自分のリリース計画に載せてまとめて払う」にできることです。

そしてここは、Claude Codeと協働していても埋まらない穴です。Claude Codeにアラート対応を頼むと、提案してくれるのは「どうバージョンアップするか」です。「そもそも到達可能か・今上げる必要があるか」は、聞かれない限り判定しません。つまり「バージョンアップしない」という選択肢が、既定では存在しないのです。

もちろん、バージョンアップするか否かはCVEだけで決まる話ではありません。新機能、EOL、他ライブラリとの互換性 —— 判断材料は複数あります。quiet-cveが担当するのはそのうちの1軸、「CVEの観点では、今上げる必要があるか」の判断材料を根拠付きで出すことです。セキュリティの観点で急ぐ必要がないと分かれば、バージョンアップは他の理由が揃ったタイミングまで安心してまとめられます。

つまり必要なのは、全部に対応する根性ではなく、「これはCVEの観点では今日対応しなくていい」と言い切れる根拠です。

そこでquiet-cveは3つの軸で絞り込みます。

内容
KEV CISAの「実際に悪用が確認された脆弱性」カタログに載っているか
CVSS 深刻度スコア(v3ベクタから計算)
実コード使用状況 ★ このプロジェクトが本当にその脆弱な機能を呼んでいるか

3つ目が中核で、これはLLMがコードを読めるようになって初めて自動化できるようになった部分です。単にパッケージ名をgrepするのではなく、アドバイザリを読んで「どの関数・オプションが危険なのか」を特定してから、その呼び出しが実行経路に存在するかを確かめます。

なお、Dependabotの置き換えではありません。検知はDependabot、取捨選択はquiet-cveという併用を推奨しています。役割が重ならないので競合しません。

SCAツールの死角も見る

作っているうちに気づいたのですが、宣言された依存だけを見るSCAツールには原理的な死角があります。

  • CDN経由で読み込んでいるライブラリ
  • public/js/ に手で置かれたファイル
  • nginx / OpenSSL / PHP本体のようなミドルウェア

これらは package.json にも composer.lock にも決して載りません。歴史の長いコードベースほど溜まっています。

quiet-cveはコードを走査してこれらも拾います。実際、CDN読み込みのまま放置された jQuery 1.12.4 からは、KEV掲載(=実悪用あり)の CVE-2020-11023 が出てきます。ミドルウェアも config.yml にバージョンを宣言すればNVDに照会でき、たとえば php 8.1.0 からは2024年に大規模悪用されたPHP CGIのRCE(CVE-2024-4577)が出ます。

レガシー案件を触ったことがある方なら、この死角の怖さは分かってもらえると思います。

「形骸化」への答え

冒頭の問題に戻ります。仕組みが形骸化する典型パターンと、それぞれへの対策をこう設計しました。

パターン1: 「一度無視したものが永久に見えなくなる」

ignore には理由と期限が必須です。理由の種類はVEX(CycloneDX)準拠の justification で宣言します。

ignore:
  cves:
    - id: CVE-2024-12345
      reason: "該当機能(XMLパーサ)を使っていない。手動確認済み"
      justification: vulnerable_code_not_in_execute_path
      expires: "2026-12-31"
      evidence_files:      # 任意: これが変わったら期限内でも再浮上
        - src/app.js

期限を過ぎた除外は自動的に失効し、「⚠ 除外期限切れ」としてレポートに再浮上します。さらに、判定の根拠にしたファイルを evidence_files に書いておけば、そのファイルが判定時点から変わった瞬間に、期限内でも「根拠ファイル変更あり・再確認が必要」として再浮上します。判断した当時のコードと今のコードのずれを、日付と実際の変更の両方で拾う形です。

なお、誤検知(アドバイザリ撤回)やプラットフォーム非該当のような「コードが変わっても無効にならない理由」に限り、種別を宣言したうえで expires: never の永久ignoreも認めています。逆に「使っていない」系の腐る理由に never を書くと設定エラーで実行が止まります。無視する理由の寿命に合わせて、失効の仕組みを変えているわけです。

パターン2: 「レポートが溜まって誰も見なくなる」

日次レポートは保持期限(既定90日)を過ぎると月次サマリーに集約されて削除されます。溜まる一方のレポートは読まれなくなるので、畳むこと自体がアラート疲れ対策の一部です。判断根拠を含む全レコードは追記専用の logs/triage.jsonl に残り続けるので、消しても監査証跡は失われません。

パターン3: 「会話の中の判断が消える」

「これ使ってないから大丈夫です」という判断は、会話が終われば消えます。3ヶ月後に同じCVEを見て「これ前に調べたっけ?」を繰り返さないために、全判定を1判定1行のJSON Linesで根拠付きで記録します。

「それClaude Codeに直接頼めばよくない?」

その通りです。quiet-cveを使わなくても、Claude Codeに「Dependabotのアラートを見て対応して」と頼めばコードを読んで判断してくれます。頭脳は同じClaudeなので、1回の判定の質はそれで十分なことも多い。

ただ、それだといくつかの視点で不足しがちです。

このフレームワークが足しているのは賢さではなく、その場かぎりの会話を運用に変えるための固定です。

  • 手順の固定 — その場の指示では、どこまで調べるかがその日の頼み方とモデルの裁量で揺れます。SKILL.mdは「grepで有無だけ見て判定しない」「影響なしには根拠2つ以上」「迷ったらunknown」を毎回強制します
  • 記録の固定 — 全判定が追記専用ログに残ります
  • 失効の固定 — ignoreの期限切れ再浮上が、放置を許しません

つまりquiet-cveは「Claudeにうまく頼むためのプロンプト集」ではなく、毎回同じ基準で判定され、判断が残り、放置が失効する運用の器です。

限界も正直に書いておく

セキュリティツールなので、できないことを明記しておきます。

  • 判定はLLM依存です。 誤判定のリスクはゼロにできません。だからこそ判定を used / unused / unknown の3値にして、unused(影響なし)と書けるのは根拠が2つ以上揃ったときだけ、動的ロードなどで追いきれないものは unknown として様子見に残す設計にしています。誤った安心を配るくらいなら、余分に報告するほうがいい。
  • コード実使用判定はトークンを消費します。 毎日回すものではなく、Dependabotや週次CIが何か拾ったときだけ動かす運用を想定しています。コードもCVEも変わっていなければ、毎日回しても同じ結果に課金するだけです。
  • CIでは中核の判定ができません。 同梱のGitHub Actionsテンプレートは検知(OSV/NVD照会)までで、コードを読む判定は手元のClaude Code実行が必要です。CIからは「影響なし」は絶対に出ない設計にしています。根拠なしに安心を配らないという原則をCI側でも守るためです。
  • 狭義のゼロデイは検知できません。 データベースに載っていない脆弱性は対象外です。守っているのは「公表された瞬間から始まる競争」のほうです。

おわりに

AIによって、脆弱性の公表からエクスプロイトが出回るまでの時間は数週間から数日・数時間へと縮み続けています。一方で防御側のボトルネックは検知ではなく、「このうち自分のコードに実害があるのはどれか」を選ぶ判断でした。コードを読まないと決められないため自動化できず、人間の時間が律速だった部分です。

quiet-cveはこの判断をAIに任せることで、公表から対応判断までの時間を、公表から悪用までの時間に追いつかせるための道具です。攻撃側のAIには、防御側のAIで。

前回の記事が「問題提起編」だとすれば、今回が「実装編」です。運用の目安は一行で言えます。安い更新(パッチ・マイナー)は黙ってマージ、高い更新(メジャー・破壊的変更)だけトリアージ。

ディレクトリを置いて一言頼むだけで動くので、まずは手元のプロジェクトで試してみてください。

リポジトリ: high-tech-r/quiet-cve

役に立ちそうだと思ったら、Starをもらえると開発の励みになります。「このエコシステムに対応してほしい」などの要望はIssueでお待ちしています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?