フロントエンド

【Node.js】2026年10月のセキュリティリリースに、Cloud RunのNext.jsでどう対応したか|Dependabotに出ない脆弱性と、動いている版をdigestで確かめる方法

2026年10月21日、Node.jsから22.x / 24.x / 26.xを対象にしたセキュリティリリースが公開されました。Brotliで圧縮されたデータを展開する処理で、リモートでコードを実行されるおそれがある脆弱性が含まれていました。

担当しているコーポレートサイト(Next.jsをコンテナにしてCloud Runで動かしている)について影響を調べ、対応しました。結論から言うと、追加の作業はほぼ要りませんでした。ただ、それを「確かめる」までにいくつか気づきがあったので、手順と判断の理由をまとめます。

この記事の内容は2026年10月時点のものです。担当しているサイトが特定できないよう、構成の細部は伏せています。

同じ日には、PHPの緊急脆弱性php2shellの修正版も公開されています。そちらへの対応は PHPの緊急脆弱性に、Xserver上のWordPressで一晩対応した記録 にまとめています。

スポンサーリンク

この記事の要点

  • Node.js本体(ランタイム)の脆弱性は、Dependabot alertsには出てこない。自分で拾いに行く必要がある
  • Cloud Runで動いているコンテナのNodeの版は、ビルドログのベースイメージのdigestと、Docker Hubのdigestを照らし合わせれば、レジストリの権限がなくても確かめられる
  • ベースイメージをnode:22-alpineのようにメジャー版だけで指定していたので、修正版の公開から1〜2日で、普段のデプロイによって自動的に修正版に入れ替わっていた
  • いちばん古いまま残っていたのは、ローカルの開発環境だった

1. 脆弱性の概要

公式のアドバイザリ(October 2026 Security Releases)によると、CVEで10件(High 4 / Medium 3 / Low 3)です。

深刻度 主な内容 対象
High Brotliのデコーダーの領域外書き込み(node:zlib)。クラッシュ、メモリの漏えい、リモートでのコード実行のおそれ 22 / 24 / 26
High dns.resolve*()が、NULを含むホスト名を途中で切り詰める 22 / 24 / 26
High IPアドレスの書き方の揺れ(先頭のゼロなど)で、TLSのホスト名の検証をすり抜けられる 22 / 24 / 26
High 全角数字のIPアドレスで、TLSのホスト名の検証をすり抜けられる 24 / 26(22は対象外)
Medium / Low Keep-aliveのソケットで不正なレスポンスを受け付ける、スナップショットを使うと乱数が毎回同じになる、権限モデルの不備など ―

修正版は22.23.4 / 24.22.1 / 26.12.1です。サポートが終わったバージョン(20系以前、25系など)には修正版が出ないので、サポート中のバージョンへの移行が必要です。

Brotliの件は、「信頼できないBrotliのデータを展開する」と影響を受けます。Node標準のfetch(Undici)は、Content-Encoding: brのレスポンスを自動で展開します。外部のAPIからデータを取ってくるサーバーの処理や、同期のスクリプトがあるなら、他人事ではありません。

2. まず「Dependabotに出ていない」ことに気づく

最初にリポジトリのDependabot alertsを見ましたが、出ていたのは開発用のnpmパッケージのアラートだけで、今回のNode.jsの件とは関係のないものでした。

Dependabot alertsが見ているのは、package-lock.jsonなどのパッケージの依存だけで、Node.js本体やDockerのベースイメージは対象外です。dependabot.ymlにdockerを足しても、届くのは「新しいタグが出ました」というバージョン更新のプルリクエストで、CriticalやHighのような深刻度の付いたアラートにはなりません(GitHub Docs: supported ecosystems)。

ランタイムの脆弱性は、別の手段で拾う必要があります。

3. どこでNode.jsを使っているかを洗い出す

リポジトリの中で、Nodeの版を指定している箇所を探しました。

バージョンを指定するファイルを確かめます。

cat .nvmrc .node-version .tool-versions 2>/dev/null

package.jsonのenginesとvoltaを確かめます。

grep -n -A3 '"engines"\|"volta"' package.json

Dockerfileのベースイメージと、CIの設定を確かめます。

grep -n '^FROM' Dockerfile
grep -rn 'node-version' .github/workflows

結果は次のとおりです。

場所 指定 判断
Dockerfile(3つのステージ) node:22-alpine 影響あり。動いているイメージがどの版かを確かめる必要がある
CI(GitHub Actions) node-version: 22 実行のたびに最新の22.xを取ってくるので、次の実行から自動で修正版になる
ローカルの開発環境 各自の環境しだい 確かめる必要がある

4. Cloud Runで動いているNodeの版を確かめる

ここがいちばん悩んだところです。

素直な方法:イメージを取ってきてnode -v

docker run --rm --entrypoint node <レジストリ>/<イメージ>@sha256:<digest> -v

これがいちばん確実です。ただ、私の権限ではコンテナのレジストリ(Artifact Registry)からイメージを取得できず、downloadArtifacts deniedで失敗しました。運用の担当と開発の担当で権限が分かれている現場では、よくあることだと思います。

権限がなくてもできる方法:digestを照らし合わせる

発想を変えて、「ビルドしたときに、どのベースイメージが使われたか」を確かめることにしました。

手順1:ビルドログから、ベースイメージのdigestを拾う

GitHub Actionsのデプロイのジョブで、docker buildのログをnode:22-alpineで検索すると、BuildKitが次のような行を出しています。

#8 [deps 1/5] FROM docker.io/library/node:22-alpine@sha256:<digest>

node:22-alpineというタグが、ビルドした時点で どの実体(digest)を指していたか が記録されています。

手順2:修正版のdigestを調べる

Docker Hubは公開されているので、権限は要りません。

docker buildx imagetools inspect node:22.23.4-alpine | head -3
Name:      docker.io/library/node:22.23.4-alpine
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:<digest>

手順3:比べる

本番もステージングも、ビルドログのdigestとnode:22.23.4-alpineのdigestが一致しました。修正版のNode 22.23.4でビルドされたイメージが動いていることを確かめられました。

なぜ何もしなくても修正版になっていたのか

  • デプロイはGitHub Actionsのランナーで、キャッシュを使わずにdocker buildしている → ビルドのたびにnode:22-alpineの最新版を取ってくる
  • デプロイの頻度が高く、修正版の公開後すぐに次のデプロイが走っていた
  • Docker Hubの公式イメージは、Nodeのリリースから短い時間で更新される

この3つがそろっていたので、修正版の公開から1〜2日のうちに、普段のデプロイで自然に入れ替わっていました。

逆に言うと、しばらくデプロイしていないサービスは、node:22-alpineと書いていても古いまま動き続けます。Cloud Runは受け取ったイメージをそのまま動かすだけで、Nodeの版を自動で上げてはくれません。

5. ローカルの開発環境をそろえる

意外と見落としていたのがこちらです。私のMacにはVoltaでv24.14.1が入っていて、修正前の版でした。そのうえ、本番(22系)ともメジャー版が食い違っていました。

volta install node@22
node -v
npm ci && npm test

node -vがv22.23.4になり、テストもすべて通りました。

ローカルはインターネットに公開されたサーバーではありません。ただ、開発サーバーや同期のスクリプトは外と通信していますし、開発用のPCには認証情報や.envが集まっています。放っておかないほうがよいと判断し、開発メンバーにも 本番と同じ22系の最新版(v22.23.4以上)にそろえるよう共有しました。

細かい注意点もあわせて共有しています。

  • npm ciのあとに「npmの新しいメジャー版に上げられます」と出ても上げない(Node 22に付いているnpmのままにして、lockfileに余計な差分を出さない)
  • npm audit fixをローカルで実行しない(依存パッケージの更新は、Dependabotのプルリクエストでまとめて行う)

6. ベースイメージは固定すべきか

調べている途中で、「node:22.23.4-alpineのように版を固定したほうがいいのでは」という話になったので、整理しました。

固定する(+Dependabotのdocker) 固定しない(node:22-alpine)
良いところ 何が動いているかがコードを見れば分かる。同じコミットからは同じイメージができる。更新は必ずプルリクエストを通る ビルドし直すだけで修正版が入る。運用の手間がない
気をつけるところ プルリクエストをマージするまで修正版が入らない。放っておくと古いまま止まる 今何が動いているかは、ログを見ないと分からない。ベースイメージが知らないうちに変わる
共通 脆弱性のアラートは出ない。デプロイし直さない限り、動いているコンテナは古いまま 同左

このサイトはデプロイの頻度が高く、固定しないことの弱点も「ビルドログのdigest」「Cloud Runの前のリビジョンへの切り戻し」「ステージングでの事前の確認」で補えています。そのため、今は固定せずに運用を続けることにしました。デプロイの頻度が落ちてきたら、固定+Dependabotに切り替えるつもりです。

なお、どちらを選んでも、node:22-alpineが24系に勝手に上がることはありません。Node 22は2027年4月 でサポートが終わるので、それまでにDockerfile、CI、ローカルを、24系か26系へ手で移す必要があります。

7. 振り返り

  • ランタイムの脆弱性は、自分で拾いに行く:Dependabot alertsは万能ではありません。GitHubのnodejs/nodeのリポジトリのReleasesをWatchしておく、コンテナイメージのスキャンをCIに入れるなど、別の経路を用意しておくと安心です
  • 「動いているもの」を確かめる手段を持っておく:ビルドログのdigestとDocker Hubのdigestを照らし合わせる方法は、レジストリの権限がなくても使えて、次からもそのまま使い回せます
  • ローカル環境も対象に入れる:本番だけを見ていると、開発用のPCがいちばん古いまま、ということが起きます
  • 「何もしなくてよかった」も、確かめて初めて言える:結果的に作業はほぼ要りませんでしたが、確かめた手順と判断の理由を残しておけば、次に同じことが起きたときにすぐ判断できます

同じリリースをきっかけに、放っておいた個人サイトのNext.js 13も片付けました。そちらは Node.jsのセキュリティリリースをきっかけに、放置していたNext.js 13を片付けた話 にまとめています。

参考