公開している Node.js のAPIで、依存パッケージを npm audit にかけたら、脆弱性が3件出てきました。うち1件は重要度「高」です。
npm audit fix で更新し、テストの結果が前と変わらないことを見てから、本番に反映しました。ここまでは、よくある対応です。
では、あの「高」は、このAPIにとって本当に危なかったのか。記事にするにあたって、直した後ではありますが、脆弱なコードがAPIの処理から実際に呼ばれていたのかを調べてもらいました。
私は専門のプログラマーではなく、AI(Claude Code)に手伝ってもらいながらAPIを作っています。今回の調査も、Claude Codeにコードを読んでもらい、コマンドを実際に動かして確かめてもらったものです。
対象のAPI
FixMyMermaid という、AIが出力した壊れた Mermaid の図を直して返すAPIです。Mermaid は、テキストで流れ図などを書ける形式です。
直した図が本当に正しいかは、公式の mermaid パッケージで毎回チェックしています。Node.js にはブラウザの画面がないので、jsdom(ブラウザの代わりをするライブラリ)の上で動かしています。
npm audit の結果
$ npm audit
katex 0.11.0 - 0.18.1
KaTeX: Existing prototype pollution can bypass trust restrictions - https://github.com/advisories/GHSA-238p-pmpm-9mq7
mermaid 10.9.0-rc.1 - 12.1.0
Depends on vulnerable versions of katex
source-map-js 1.0.0 - 1.2.1
Severity: high
source-map-js allows event-loop denial of service through indexed source-map section offsets - https://github.com/advisories/GHSA-68fv-2mgg-jv7q
3 vulnerabilities (2 low, 1 high)
(fix available via npm audit fix や node_modules/... の行は省きました)
「高」「低」は、脆弱性の重要度です。npm audit は、低いほうから info・low・moderate・high・critical の5段階で表示します。元になっているのは、CVSS という0〜10点の共通の採点で、7.0〜8.9点がおおむね「高」に当たります。npm audit --json で見ると、今回の source-map-js は7.5点でした。KaTeX は「低」ですが、点数は付いていませんでした。
この点数は、そのパッケージを最も危ない使い方をした場合を想定したものです。自分のアプリがその部分を使っているかどうかまでは、見ていません。
「高」は source-map-js です。source map とは、圧縮や変換をしたコードと、元のコードの行の対応表のことで、主に開発ツールが使います。題名を読むと、細工した source map を読ませると処理が詰まる、という内容です。Node.js は1本の流れ(イベントループ)で順に処理するので、そこが詰まると、ほかのリクエストも止まります。いわゆる DoS です。
「低」の1件は KaTeX という、数式を描くライブラリです。題名を訳すと「すでに別の原因で JavaScript の土台(prototype)が書き換えられている場合に、信頼の制限をすり抜けられる」となります。もう1件の mermaid は、mermaid 自体の欠陥ではありません。KaTeX を使っているので、一緒に数えられています。
どちらも、私が直接入れたパッケージではありません。npm ls で、どこから入ってきたかを見てもらいました。
$ npm ls source-map-js katex
+-- jsdom@30.1.1
| `-- css-tree@3.2.1
| `-- source-map-js@1.2.1
`-- mermaid@12.0.0
`-- katex@0.16.47
source-map-js は jsdom の中の css-tree(CSSを読むライブラリ)から、KaTeX は mermaid から入っていました。
先に直したこと
npm audit fix は、package.json に書いた版の範囲の中で、脆弱性が直った版に入れ替えるコマンドです。実行すると、package-lock.json(実際に入れる版の記録)の中で、11個のパッケージの版が上がりました。package.json(使ってよい版の範囲の宣言)は変わっていません。
$ npm audit
found 0 vulnerabilities
上がった版は、mermaid が 12.0.0 → 12.2.0、katex が 0.16.47 → 0.18.10、source-map-js が 1.2.1 → 1.2.2 などです。ただ、mermaid の中で文法を読む部品(chevrotain)は、11.1.2 → 13.2.0 と大きく上がっていました。図が正しいかのチェックは、このAPIの肝です。
なので、修正の前と後で、同じテストを走らせて比べてもらいました。
- 手で作った27件の確認:前も後も、27件すべて想定どおり
- 壊れた図230件のデータ:直せるはずの210件は、前も後も「正解と完全に一致203件・中身が一部違う7件・失敗0件」。直せないはずの20件は、前も後も20件すべて正しく「直せない」と返した
結果は、前と後でまったく同じでした。
「高」の source-map-js は、どこで使われていたか
ここからが、記事のために調べてもらったことです。
まず、直った版で何が変わったかを比べてもらいました。npm diff を使うと、2つの版の差分を見られます。
$ npm diff --diff=source-map-js@1.2.1 --diff=source-map-js@1.2.2 --diff-name-only
lib/quick-sort.js
lib/source-map-consumer.js
lib/source-map-generator.js
lib/source-node.js
package.json
いちばん大きく変わっていたのは、source map を「読み込む」側(source-map-consumer.js)です。行の位置に上限を設けて、細工した source map で膨大な処理が走るのを防いでいました(+ が付いた行が、足された行です)。
+// Upper bound for a section's `offset.line` in an indexed source map.
+var MAX_SECTION_OFFSET_LINE = 1e7;
...
+ if (offsetLine > MAX_SECTION_OFFSET_LINE) {
+ throw new Error('Section offset line must not exceed ' + MAX_SECTION_OFFSET_LINE + '.');
+ }
source map を「作る」側(source-map-generator.js)にも、同じ脆弱性への対策が入っていました。行が大きく飛んだときに ; を1つずつ足していくと、メモリを使い果たすことがある、という修正です。quick-sort.js の変更は、今回の脆弱性とは別のものでした。
「読み込む」側は、使われていなかった
では、css-tree は source-map-js の何を使っているのか。node_modules の中を探してもらいました。
$ grep -rln "source-map-js" node_modules --include=*.js --include=*.cjs --include=*.mjs | grep -v "^node_modules/source-map-js/"
node_modules/css-tree/lib/generator/sourceMap.js
node_modules/css-tree/cjs/generator/sourceMap.cjs
2つのファイルは、同じ内容の ESM 版と CommonJS 版です。中で読み込んでいるのは「作る」側だけでした。
import { SourceMapGenerator } from 'source-map-js/lib/source-map-generator.js';
このAPIが受け取るのは Mermaid の図のテキストです。source map を受け取る入口はありません。
「作る」側も、呼ばれる設定になっていなかった
css-tree が source map を作るのは、generate() を sourceMap という設定付きで呼んだときだけです(lib/generator/create.js の if (options.sourceMap))。
css-tree を呼んでいるのは、jsdom と、jsdom が使う css-tree 系のパッケージ(@asamuzakjp/dom-selector と @bramus/specificity)です。そこを sourceMap で検索してもらうと、0件でした。
$ grep -rn "sourceMap" node_modules/jsdom/lib node_modules/@asamuzakjp node_modules/@bramus/specificity/src | wc -l
0
ここまでで、source-map-js の脆弱な部分に、このAPIの処理から届く経路は見つかりませんでした。
npm ls に出てこないコピーがあった
最初の調べでは、見落としていたことがあります。@bramus/specificity は、配布用のファイルの中に、source-map-js の「作る」側のコードを、ひとまとめにして同梱していました。
$ grep -rl "source-map-js/lib/source-map-generator" node_modules --include=*.map | grep -v "^node_modules/source-map-js/"
node_modules/@bramus/specificity/dist/index.js.map
node_modules/@bramus/specificity/dist/index.cjs.map
こうした同梱のコピーは、npm ls にも npm audit にも出てきません。実際、npm audit fix の後も、中身は古い版のままでした。
今回は、同梱されていたのが「作る」側だけで、「読み込む」側は入っていませんでした。呼び出しも sourceMap の設定なしです。なので、こちらからも届く経路はありませんでした。
「低」の KaTeX は、いつ読み込まれるか
mermaid の中で KaTeX を探してもらうと、図の中の $$...$$(数式)を、画面に描くための文字列に変えるところで使われていました。
const { default: katex } = await import("katex");
...
(_, c) => katex.renderToString(c, { ... })
await import("katex") は、ファイルの先頭の import ... from と違って、その行が実行されたときに初めて KaTeX を読み込む書き方です。
一方、このAPIが mermaid に頼んでいるのは、文法のチェック(parse())と、図の中身の読み取りだけです。図を描く処理は呼んでいません。
コードを読んだだけでは見落としがあるかもしれないので、実際に試してもらいました。KaTeX を一時的に別の名前にして、読み込めない状態にします。そのうえで、数式入りの流れ図とシーケンス図を、チェックにかけました。
$ mv node_modules/katex node_modules/katex.off
$ node probe.mjs
{"ok":true,"diagramType":"flowchart-v2","message":null,"line":null}
{"ok":true,"diagramType":"sequence","message":null,"line":null}
$ mv node_modules/katex.off node_modules/katex
(probe.mjs は、このAPIのチェック用の関数に、A["$$x^2$$"] --> B の流れ図と、A->>B: $$\frac{a}{b}$$ のシーケンス図を渡すだけのスクリプトです。試したら、最後の mv で必ず元に戻してください)
KaTeX がなくても、チェックは普通に通りました。文法のチェックでは、KaTeX は読み込まれていません。
やってみて分かったこと
npm audit の重要度は、そのパッケージ自体の危なさです。自分のアプリにとって危ないかどうかは、別に調べないと分かりません。
今回の調べ方は、次の3つでした。
-
npm lsで、どこから入ってきたかを見る -
npm diffで直った版の差分を見て、どの部分が危なかったのかを知る - その部分を、自分のアプリが呼んでいるかを探す。怪しければ、外してみて動くかを試す
ただ、3つ目の「探す」は、ファイル名や文字列で探すだけだと、同梱のコピーを見落とします。今回も、.map ファイルまで探して、ようやく見つかりました。
それに、これは「見つからなかった」というだけで、「絶対に届かない」とまでは言い切れません。私たちが気づいていない呼び方が、ほかにあるかもしれないからです。今回は、調べる前に直していました。直すのに手間がかかる場合は、こうした確認が、急ぐかどうかを決める材料になります。
ほかにも良い調べ方をご存じでしたら、コメントで教えていただけるとうれしいです。
この記事は、2026年10月17日にZennに投稿した記事と同じ内容です。→ 元記事(Zenn)
この記事はAI(Claude)と一緒に書きました。コマンドはAIが実際に実行し、結果を確認しています。
壊れたJSON・SQL・Mermaidを自動で直すAPI(FixMy シリーズ)を、RapidAPIで公開しています。→ FixMyMermaid