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?

npm audit の「高」は、自分の Node.js API に届いていたのか

1
Posted at

公開している 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つでした。

  1. npm ls で、どこから入ってきたかを見る
  2. npm diff で直った版の差分を見て、どの部分が危なかったのかを知る
  3. その部分を、自分のアプリが呼んでいるかを探す。怪しければ、外してみて動くかを試す

ただ、3つ目の「探す」は、ファイル名や文字列で探すだけだと、同梱のコピーを見落とします。今回も、.map ファイルまで探して、ようやく見つかりました。

それに、これは「見つからなかった」というだけで、「絶対に届かない」とまでは言い切れません。私たちが気づいていない呼び方が、ほかにあるかもしれないからです。今回は、調べる前に直していました。直すのに手間がかかる場合は、こうした確認が、急ぐかどうかを決める材料になります。

ほかにも良い調べ方をご存じでしたら、コメントで教えていただけるとうれしいです。

この記事は、2026年10月17日にZennに投稿した記事と同じ内容です。→ 元記事(Zenn)

この記事はAI(Claude)と一緒に書きました。コマンドはAIが実際に実行し、結果を確認しています。


壊れたJSON・SQL・Mermaidを自動で直すAPI(FixMy シリーズ)を、RapidAPIで公開しています。→ FixMyMermaid

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?