KiSAKU
技術系7分で読めます

依存の中の依存

公開

pushしたら、GitHubからこう返ってきました。

push時のメッセージ
remote: GitHub found 29 vulnerabilities on kisaku39/kisaku-portfolio's
remote: default branch (13 high, 14 moderate, 2 low).

29件。数字だけ見ると、手のつけようがないように感じます。ところが中身を分類すると、実質3つの問題でした。

この記事で扱うこと

警告の件数をそのまま受け取らずに読み替える方法、自動で出てくる更新PRの中で「そのまま入れてよいもの」の見分け方、そして「直接の依存を上げたのに警告が消えない」という現象の正体。

件数は、そのままの意味ではない

一覧を開いて、パッケージ別に数え直しました。

パッケージ 件数 用途
next 19 本番で動く
postcss 5 ビルド時に動く
lodash 2 開発用のみ
glob 1 開発用のみ
nanoid 1 本番で動く

29という数は、1つのパッケージに対する報告が積み上がった結果でした。nextの19件は、SSRF、Server ComponentsのDoS、画像最適化APIのDoS、キャッシュ混同——別々の問題ですが、直すのは「nextを上げる」の1手です。

件数ではなく、手数を数える

29件を29個の作業だと思うと、着手する気力が湧きません。実際に必要な操作は5つ以下でした。警告システムは問題の個数を報告しますが、こちらが知りたいのは作業の個数です。ここを読み替えないと、量に圧倒されて何もしないことになります。

開発用かどうかで、優先度が変わる

一覧には Development という表示が付くものがあります。lodashglob がそれでした。

これらはビルドや開発中にしか動かず、配信される成果物には入りません。つまり、サイトを見に来た人がこの脆弱性に触れることはありません。悪用するには、まず開発環境に入り込む必要があります。

危険がゼロという意味ではありませんが、本番で動くものと同じ緊急度で扱う必要はありません。3件がここに該当したので、後回しにしました。

自動で出てくるPRは、同じ安全さではない

Dependabotは修正用のPRを自動で作ってくれます。5本ありました。

開いていたPR
#7   next               14.2.35 → 16.3.1    メジャー2つ跨ぎ
#5   eslint-config-next 14.2.35 → 16.3.1    メジャー2つ跨ぎ
#6   tailwindcss        3.4.4   → 4.3.3     メジャー跨ぎ
#8   graphql-request    6.1.0   → 7.4.0     メジャー跨ぎ
#10  postcss            8.5.23  → 8.5.26    パッチのみ

ボタンひとつでマージできます。しかし、同じ「更新」でも中身の危険度がまったく違います。

バージョンの変化 意味 扱い
8.5.23 → 8.5.26 不具合修正のみ。使い方は変わらない そのまま入れてよい
6.1.0 → 7.4.0 使い方が変わっている可能性がある 動作確認が要る
14.2.35 → 16.3.1 2世代ぶんの変更が入る 移行作業として扱う
GitHub上でマージすると、確認の機会が無い

手元で更新する場合は npm run build で通ることを確かめてからpushできます。PRをブラウザでマージすると、その工程が丸ごと飛びます。通るかどうかは、本番のデプロイが動き出してから分かります。パッチ更新なら許容できますが、メジャー更新でこれをやるのは賭けです。

そこで、#10だけをマージしました。チェックも2つとも通っていたので、これは安全な1本です。

1件しか減らなかった

マージ後、件数を数え直しました。

結果
29件 → 28件

postcss  5件 → 4件

postcssを上げたのに、postcssの警告が4件残っています。バージョンは 8.5.26 になっているはずです。

残っている警告を1つ開くと、こう書いてありました。

アラートの詳細
影響を受けるバージョン   8.5.11 以下
修正されたバージョン     8.5.12

8.5.26 は 8.5.12 より新しいので、条件に当てはまりません。それなのに警告が残っている。

依存の中の依存

ロックファイルを直接見て、答えが出ました。

package-lock.json 内の postcss
8.5.23 (dev)  node_modules/postcss                ← 8.5.26 に更新された
8.4.31        node_modules/next/node_modules/postcss ← 手つかず

postcssが2つ入っていました。

1つは自分で指定したもの。もう1つは next が自分の内側に抱えているものです。npmは、あるパッケージが要求するバージョンと自分の指定が食い違うとき、そのパッケージの中に別のコピーを置きます。同じ名前のものが、違うバージョンで同居できる仕組みです。

なぜそういう作りになっているのか

もし1つしか置けないなら、「AはpostCSS 8.4を要求、BはpostCSS 8.5を要求」という状況で、どちらかが壊れます。入れ子にすることで、互いの要求を同時に満たしています。便利な仕組みですが、その代わり「上げたつもりが上がっていない」が起こります。

自分で指定した依存は、自分で上げられます。他のパッケージが抱えている依存は、そのパッケージ自身が上げてくれるのを待つしかありません。

同じことが nanoid にも起きていました。これも next 経由で入っています。

読み替えた結果

ここまでを踏まえて、28件を整理し直しました。

分類 件数 必要な操作
next 本体 18 next を上げる(1手)
next が抱える postcss 4
next が抱える nanoid 1
開発用(lodash / glob) 5 本番に影響なし。後回し

28件のうち23件が、1つの操作に集約されました。最初に見た「29件」という数字とは、受ける印象がまったく違います。

それでも、一気には上げない

では next を16に上げればいい——とはなりませんでした。

14から16は、2世代ぶんの変更が入ります。このサイトはApp Routerを使い、ISRで事前生成し、つい先ほどsitemapと構造化データを入れたばかりです。それらが動かなくなる可能性を、確認せずに本番へ出すことはできません。

段階を分けました。

今日やること
npm install next@14 eslint-config-next@14   # 14系の最新まで
npm run build                               # 通るか確認
npm run start                               # 本番と同じ条件で表示確認

まずメジャーを跨がずに取れる分だけ取ります。これで消えない分が、16に上げる判断の材料になります。「全部消したい」から一気に上げると、落ちたときに何が原因か分かりません。

確認は dev ではなく start で

開発サーバー(npm run dev)と本番(npm run buildnpm run start)では動きが違います。sitemapや構造化データのようにビルド時に生成されるものは、devで見ても本番と同じとは限りません。更新の影響を確かめる目的なら、本番と同じ手順で見る必要があります。

今回の学び

まず、警告の件数と、作業の件数は違うこと。29件のうち23件が1つの操作に集約されました。件数のまま受け取ると、量に圧倒されて着手できなくなります。パッケージ別に数え直すだけで、見える景色が変わります。

次に、「更新」の一言に、危険度の違う操作が混ざっていること。パッチ更新とメジャー更新は、ボタンの見た目が同じでもやっていることが違います。自動で提案されたからといって、同じ扱いをしてよいわけではありません。

そして、直接の依存を上げても、内側の依存は動かないこと。同じパッケージが違うバージョンで同居できる仕組みがあるため、「上げたのに消えない」が起こります。ロックファイルを見れば10秒で分かる話ですが、知らないと延々と悩みます。

最後に、本番に届くものと、届かないものを分けること。開発用の依存は、同じ「高」でも読者に届きません。全部を等しく緊急扱いすると、本当に急ぐものに手が回らなくなります。

← ブログ一覧へ戻る