Dev Study
← OWASP Top 10 の群 — 何のリストで、どう作られ、どう読むか
レッスン 3 / 12·約 9 分·第27期 OWASP Top 10 の群

検査に写りにくく、調査では1位 — ソフトウェアのサプライチェーン(A03:2025)

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

同じ Top 10 の中でも、データで入った項目と調査で押し上げられた項目があります。今回は、データに少ないのに最上位近くに来た項目を読みます。

まず結論

A03:2025 Software Supply Chain Failures は、提供データでの出現総数も、対応づいた CVE の数も10項目で最少なのに、コミュニティ調査では回答者のちょうど50%が1位に挙げました。原文はデータに少ない理由を「テストの難しさ」と書いています。

テストに写りにくい場所で機械が照合しているものの1つが lockfile の integrity です。手元の実演では、それは「記録した時点と同じ中身か」だけを見ていました。記録前から悪い中身は通り、キャッシュが温かいと差し替えられた中身を読みもしませんでした。

? 読む前に予測

Web Top 10 2025 の3位 A03 Software Supply Chain Failures は、提供データの中でどんな位置にあるでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

この一覧は何を数えたか、そして A03 はどこにいるか

Web Top 10 2025 が提供データに求めたのは、検査したアプリの数と、各弱点(CWE)を1つ以上持つアプリの数です。1つのアプリに何件あったかは計算に入れません。表には CVE から出した exploit・impact の平均も載り、さらに実務者へのコミュニティ調査があります。導入文はこう書きます。

We ranked 12 categories based on the data contributed, and allowed two to be promoted or highlighted by responses from the community survey.

理由も続きます。弱点を大規模に確実に検査できるようになるまでには年単位の時間がかかり、There are also important risks that we may never be able to reliably test and be present in the data. とあります。ただし同じ導入文には「10のうち8をデータから選ぶ」とする段落もあり、数え方の記述は1通りではありません。

A03 の説明はこうです。

This category was overwhelmingly voted a top concern in the community survey. This category has 5 CWEs and a limited presence in the collected data, but we believe this is due to challenges in testing and hope that testing catches up in this area.

A03 本文も、調査で exactly 50% respondents ranking it #1 だったと書きます。表の値を10項目で並べ直すと、A03 の位置は両極端でした。

A03:2025 の Score table の値と、10項目中の順位(大きい順。10表から python3 で算出)表
列 A03 の値 順位
Avg Incidence Rate 5.72% 1
Avg Weighted Exploit 8.17 1
Avg Weighted Impact 5.23 1
Total Occurrences 215,248 10
Total CVEs 11 10
CWEs Mapped 6 9

原文が明記するのは「調査で上位」「データでは少ない」の並置までで、調査の枠で入ったという因果は書いていません。A03 が調査で押し上げた2枠の1つだとも書いていません(確認範囲: 導入文の全文)。A09 には voted into a position in the list と明記があります。

A03 は A06:2021 の expansion です。範囲は the entire ecosystem of software dependencies, build systems, and distribution infrastructure へ広がりました。改名ではありません。

A03:2025 は、3つの材料の上で両極端に位置する
提供データ出現総数は10項目で最少
CVE の重みexploit・impact の平均は最高
コミュニティ調査回答者のちょうど50%が1位
原文の説明: データに少ないのはテストの難しさのため
A03:2025 は3位A06:2021 を依存・ビルド・配布へ広げた拡張
  • ・原文は、データに少ない理由をテストの難しさのためと書く
  • ・A03 が調査で押し上げた枠だとは明記されていない(A09 は明記)
具体実物

機械が照合している場所 — lockfile の integrity

テストに写りにくいなら、どこで何が照合されているのでしょうか。npm の lockfile を実物で見ます。環境は Docker の node:24(Node v24.21.0 / npm 11.19.0、Debian 12、aarch64)、--network none です。依存は自作のダミー dummy-lib@1.0.0(module.exports = "good" の1行)で、file:../dummy-lib-1.0.0.tgz として入れました。公開レジストリには触れていません。

npm の文書は integrity を A sha512 or sha1 Standard Subresource Integrity string for the artifact that was unpacked in this location. と定義しています。

[0] 記録: openssl で計算した tarball の sha512 と、lockfile に書かれた integrity(生出力)コマンド出力
tarball sha512 (openssl): sha512-wP5HlCzsX5IQGconUMLMwmlEFUHRn2TgV1fjHrDKe0EHuqcvgnye4mE6NB4WdKh0C1vDnl29FnVVBeKdIqEAfQ==
$ npm install
 
added 1 package in 78ms
exit=0
$ grep -n integrity package-lock.json
17: "integrity": "sha512-wP5HlCzsX5IQGconUMLMwmlEFUHRn2TgV1fjHrDKe0EHuqcvgnye4mE6NB4WdKh0C1vDnl29FnVVBeKdIqEAfQ==",

file: の tarball 依存でも、tarball 全体の sha512 がそのまま記録されました。次に、同じ名前・同じ版のまま中身だけ "changed" に変えた tarball に差し替え、空のキャッシュで npm ci を実行します。

[2] 差し替え後の sha512 と、空キャッシュでの npm ci(生出力の抜粋。lockfile の grep、既定キャッシュでの run A・B と ls、後続の cache clean 後の実行を省略。既定キャッシュの分は後で見る)コマンド出力
tarball sha512 (openssl): sha512-omew5+f8K8pwz7GsjG5hmEgqbLFcnri/Ekw0whDtrFTpG1Mmr7Q1l0u9NDgRIjGAs7vBAAsheILr7f28m8k10w==
$ npm ci --cache /tmp/fresh-cache # empty cache dir: npm must read the tarball itself
npm warn tarball tarball data for dummy-lib@file:/work/dummy-lib-1.0.0.tgz (sha512-wP5HlCzsX5IQGconUMLMwmlEFUHRn2TgV1fjHrDKe0EHuqcvgnye4mE6NB4WdKh0C1vDnl29FnVVBeKdIqEAfQ==) seems to be corrupted. Trying again.
npm warn tarball tarball data for dummy-lib@file:/work/dummy-lib-1.0.0.tgz (sha512-wP5HlCzsX5IQGconUMLMwmlEFUHRn2TgV1fjHrDKe0EHuqcvgnye4mE6NB4WdKh0C1vDnl29FnVVBeKdIqEAfQ==) seems to be corrupted. Trying again.
npm error code EINTEGRITY
npm error sha512-wP5HlCzsX5IQGconUMLMwmlEFUHRn2TgV1fjHrDKe0EHuqcvgnye4mE6NB4WdKh0C1vDnl29FnVVBeKdIqEAfQ== integrity checksum failed when using sha512: wanted sha512-wP5HlCzsX5IQGconUMLMwmlEFUHRn2TgV1fjHrDKe0EHuqcvgnye4mE6NB4WdKh0C1vDnl29FnVVBeKdIqEAfQ== but got sha512-omew5+f8K8pwz7GsjG5hmEgqbLFcnri/Ekw0whDtrFTpG1Mmr7Q1l0u9NDgRIjGAs7vBAAsheILr7f28m8k10w==. (224 bytes)
npm error A complete log of this run can be found in: /tmp/fresh-cache/_logs/2026-09-28T10_18_31_320Z-debug-0.log
exit=1
$ ls node_modules 2>&1
ls: cannot access 'node_modules': No such file or directory

wanted は lockfile の記録値、but got はいま読んだ tarball の値です。一致しないので exit 1 で止まり、node_modules も作られません。ただし文言は seems to be corrupted で、npm は改ざんと破損を区別していません。「EINTEGRITY」という語は npm の文書4本(package-lock.json・npm ci・npm install・npm cache)には無く、生出力にだけ現れます。

✓ ここまでの確認

tarball を差し替えたあと、空のキャッシュで実行した npm ci が EINTEGRITY で止まりました。このとき npm が比べていたのはどれですか。

抽象原理

照合の基準は「記録した時点」

ここまでで分かるのは、照合が2つの値の一致だということです。基準は lockfile に記録した時点の値で、それが安全かどうかは照合の外にあります。すると、照合をすり抜ける経路が3つ考えられます。

  1. 記録する前から中身が悪い — 記録値そのものが悪い中身の値になる。
  2. lockfile が無い — 基準が無いので、いまの中身がそのまま新しい記録になる。
  3. npm が差し替えられた中身を読まない — 比べる相手が来ない。

1と2は後の比較で見ます。先に3です。

A03 本文の How to prevent 節も、照合1つで足りるとは書いていません。Centrally generate and manage the Software Bill of Materials (SBOM) of your entire software.、Prefer signed packages to reduce the chance of including a modified, malicious component、成果物には ensure integrity via provenance, signing, and time stamping と、記録・署名・来歴を別々の手段として並べています。

具体実物

温かいキャッシュでは、差し替えを読まない

同じ差し替えのあと、既定のキャッシュ(手順[0]で元の tarball が入ったまま)で npm ci を実行しました。

[2] 差し替え後、既定キャッシュでの npm ci(生出力の抜粋。先頭の tarball sha512 行と run B・ls ~/.npm/_cacache を省略。run B も同じ結果)コマンド出力
$ grep -n integrity package-lock.json # lockfile still holds the OLD hash
17: "integrity": "sha512-wP5HlCzsX5IQGconUMLMwmlEFUHRn2TgV1fjHrDKe0EHuqcvgnye4mE6NB4WdKh0C1vDnl29FnVVBeKdIqEAfQ==",
$ npm ci # run A (default cache, which still holds the ORIGINAL tarball from step 0)
 
added 1 package in 77ms
exit=0
$ cat node_modules/dummy-lib/index.js
module.exports = "good";

exit 0 で、入ったのは差し替え前の "good" です。警告もありません。npm の文書はキャッシュを content-addressable と書き、All data that passes through the cache is fully verified for integrity on both insertion and extraction. とも書きます。記録値に当たる中身がキャッシュにあったので、差し替えられた tarball を読まずにそれを入れた——観察とはそう整合します(この挙動が仕様として書かれているかは確かめていません)。

改ざんが入らなかったことと、改ざんを検知したことは別です。 同じ lockfile でも、空のキャッシュなら止まり、温かいキャッシュなら何も言わずに通りました。実験は2回とも、所要時間とタイムスタンプ以外は同じ出力でした。

✓ ここまでの確認

同じ差し替えのあと、既定のキャッシュのまま npm ci を実行すると exit 0 で `"good"` が入りました。正しい読み方はどれですか。

⇄ 2つを見比べる
具体例 A

lockfile を消してから npm install(差し替え後の tarball)

基準が無いので、差し替え後の中身がそのまま入り、その値が新しい integrity として記録されました。警告は出ません。なお lockfile を残した npm install は、空キャッシュでは EINTEGRITY で止まりました。

[2c] 生出力の抜粋(末尾の diff 行を省略)コマンド出力
$ npm install
 
added 1 package in 79ms
exit=0
$ grep -n integrity package-lock.json # a NEW hash is simply recorded
17: "integrity": "sha512-omew5+f8K8pwz7GsjG5hmEgqbLFcnri/Ekw0whDtrFTpG1Mmr7Q1l0u9NDgRIjGAs7vBAAsheILr7f28m8k10w==",
$ node -e 'console.log(require("dummy-lib"))'
changed
具体例 B

記録する前から中身が悪い tarball で install → ci

記録した時点の中身が悪いので、記録値と一致し、npm ci は通ります。照合は正しく働いていて、それでも悪い中身が入りました。

[3] 生出力の抜粋(先頭の tarball sha512 行を省略)コマンド出力
$ npm install # records whatever the tarball is now
 
added 1 package in 80ms
exit=0
$ grep -n integrity package-lock.json
17: "integrity": "sha512-3VsLLj/7cj/t7zqceLkCeiLd7D0EqsY1LlDAepus4Trhljr3AbbKkf/I48SC3C433XZYOl+rzWSIMDUaVoFE0g==",
$ npm ci
 
added 1 package in 74ms
exit=0
$ node -e 'console.log(require("dummy-lib"))'
bad-from-the-start

どちらも exit 0 で、照合は失敗していません。それでも入った中身が違う理由を、基準の作られ方から説明してください。

それぞれ、integrity が lockfile に書かれたのはいつで、そのとき tarball の中身は何だったかを見てください。

この章のまとめ

  • Web Top 10 2025 は、提供データ(アプリの割合)・CVE の重み・コミュニティ調査で作られている。A03 は出現総数と対応づいた CVE の数が最少、平均の発生率・exploit・impact が最高、調査では50%が1位。
  • 原文はデータに少ない理由を challenges in testing と書く。A03 が調査で押し上げた枠だとは明記していない。
  • A03:2025 は A06:2021 の expansion(依存・ビルド・配布へ)。改名ではない。
  • 導入文の「5 CWEs」と表の 6、説明文の 5.19% と表の 5.72% は食い違う(2025-12-24 に表だけ更新)。CWE-447 は 477 の誤記。
  • integrity の照合は 記録時点との一致 だけを見る。空キャッシュの npm ci は止まり、温かいキャッシュでは差し替えを読まずに通った(検知ではない)。
  • lockfile なしの npm install は黙って新しい値を記録し、記録前から悪い中身は照合を通る。lockfile があれば守れる、とは言えない。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

A03:2025 と A06:2021 Vulnerable and Outdated Components の関係を、2025 の導入文はどう書いていますか。

用語集(3語)
A03:2025 Software Supply Chain Failures
Web Top 10 2025 の3位。原文は、ソフトウェアのビルド・配布・更新の過程での破綻や侵害と説明する。A06:2021 の拡張。
integrity(package-lock.json)
npm の文書の定義は、その場所に展開された成果物の sha512 または sha1 の Subresource Integrity 文字列。実演では tarball 全体の sha512 を base64 にした値と一致した。
コミュニティ調査(community survey)
Web Top 10 が、データに写りにくい危険を拾うために実務者へ尋ねる調査。2025 の導入文は、12カテゴリをデータで並べ、2つを調査の回答で押し上げたと書く。