NVD APIが2002年のCVEを返してくる — 日付フィルタ無しの検索は「登録順の先頭」を返す
脆弱性情報サイト cve-watch は、CISA KEV と NVD から High/Critical の CVE を毎日取ってきて、AWS/GCP/Azure/Linux それぞれの対応状況を整理している。
そのデータ取得スクリプトが、ある日から2002年の CVE を「新規候補」として返すようになった。
1 | |
24年前の OpenSSH の脆弱性である。今日の新着ではない。
原因はクエリに日付が無かったこと
問題のURLはこうなっていた。
1 | |
一見すると「CRITICAL な CVE を40件」で正しそうに見える。実際これは正しく動いている。「CRITICAL な CVE の、登録順で先頭40件」を返しているのだ。
NVD のデータベースには CVE-1999 系から現在までが全部入っている。日付で絞らなければ、当然いちばん古いところから返ってくる。「最近のものが返るはずだ」というのは、こちらが勝手に置いた前提でしかなかった。
新規性の判定は、取得後に「既に掲載済みのCVE番号を除く」処理でやっていた。だから一度も掲載していない2002年のCVEは、永遠に「新規」判定を通り続ける。除外リストが増えるほど、さらに古いものが繰り上がってくるという、じわじわ悪化する種類のバグだった。
pubStartDate と pubEndDate はセットで渡す
NVD API 2.0 には公開日で絞るパラメータがある。ここに2つ制約がある。
pubStartDateとpubEndDateは両方指定しなければならない。片方だけだとエラーになる- 指定できる範囲は最大120日
そして日付は ISO 8601 形式で、ミリ秒を含まない形にする必要がある。Date#toISOString() はミリ秒付きの 2026-07-23T01:30:00.000Z を返すので、そのままでは通らないことがある。
実際に書いた関数がこれだ。
1 | |
Math.min(Math.max(days, 1), 120) で 120日制限に自分で丸めている。API 側のエラーで気づくより、こちらで先に潰しておくほうが運用が静かになる。
.replace(/\.\d{3}Z$/, ".000") の部分は、末尾のミリ秒とタイムゾーン指示子 Z を .000 に置き換えている。NVD が受け付ける形式に合わせるための処理だ。
URLSearchParams を使うとエンコードを手で書かずに済む。日付にはコロンが含まれるので、素朴な文字列連結より安全だ。
もうひとつの罠:返ってくる順序は保証されない
日付で絞っても、まだ足りなかった。NVD の既定の並び順は公開日の降順ではない。120日ぶんの CRITICAL を40件取ると、その120日の中での「登録順の先頭」が返る。つまり120日前あたりのものが先に来る。
なので取得後に自分で並べ替える。
1 | |
published は ISO 8601 の文字列なので、localeCompare による辞書順比較がそのまま時系列の比較になる。new Date() に変換してから引き算する必要はない。ISO 8601 が辞書順と時系列順を一致させる設計になっているのは、こういうときに効く。
.slice() を挟んでいるのは、元の配列を破壊しないためだ。この関数の外で data を再利用する可能性がある以上、sort() の破壊的な性質は封じておく。
直った結果
修正後、同じコマンドの出力はこうなった。
1 | |
2026年のものが、公開日の新しい順に並ぶようになった。
手元で試す
APIキー無しでも叩ける(レート制限は厳しくなる)。直近7日の CRITICAL を見るだけなら、こうなる。
1 | |
sort -r を付けているのは、上に書いたとおり API の返す順序が新しい順ではないからだ。付けずに実行すると、期間内で古いものが先に来るのが確認できる。
教訓
「フィルタを指定しなかったとき、APIは何を返すのか」を確かめないまま使うと、こういうことになる。エラーは出ない。レスポンスは 200 で、形式も正しい。中身だけが期待と違うという、いちばん気づきにくい壊れ方をする。
今回それに気づけたのは、出力に published を含めていたからだ。CVE番号だけを表示していたら、CVE-2002-0083 を見ても「見慣れない番号だな」で流していた可能性が高い。日付を持つデータは、日付を表示する。 デバッグ出力の設計としてはそれだけの話だが、効き目は大きかった。
この修正を入れた cve-watch の実際のデータは cve.autoarticles.net で見られる。KEV 登録済みの脆弱性と、クラウド各社での対応状況を並べている。