本文へ移動
技術を、自社の仕事に。
判断と実行を助けるメディア

記事を検索

特集:脆弱性の緊急対応を記録で振り返る

Node.js 22.23.4へ社内の5リポジトリを一括更新 本番ビルドで見つけた落とし穴

目次 · 10項目

Node.jsのセキュリティ修正が出た日に最初に困るのは、社内のどのリポジトリが、どのファイルでNode.jsの版を決めているのかが、すぐには分からないことです。package.json のほかに、Dockerfile、GitHub Actionsのワークフロー、Cloud Buildの設定にも別々に版が書かれていることがあり、1か所だけ直しても本番のビルドは古い版のまま、ということが起こります。私たちグリームハブの開発チームは2026年10月22日に、開発用フォルダにある39個のgitリポジトリと、GitHubの組織にある107リポジトリを調べ、5つのリポジトリをNode.js 22.23.4に上げました。

この記事は、10月21〜22日に私たちが行ったセキュリティ対応を3回に分けて記録する特集の第3回です。第1回ではPHPの緊急セキュリティ修正への管理型ホスティングとCMSクラウド版での対応、第2回ではeMudhraの中間CAの侵害を受けた開発端末の証明書ストアの点検を記録しました。今回は、棚卸しから対象の絞り込み、設定の書き方の統一、AIエージェントを使った並行作業、マージと本番での確認までの流れと、このサイト(gleamhub.net)の本番ビルドで見つかった落とし穴をまとめます。

Node.jsの系列ごとのサポート期間はNode.js 24の保守期入りと26のLTS化で、Cloudflare Workers Buildsの既定のNode.jsが変わった件と版の固定方法はWorkers Builds、既定のNode.jsが24.21.0に変更で整理しています。Astroで必要なNode.jsの版が上がった件はAstro 7.4へ上げる前にで扱いました。ここでは、それらの解説は繰り返しません。

22.23.4に上げた理由

Node.jsの公式ブログは、2026年10月21日付の告知で、26.x・24.x・22.xのセキュリティ修正を公表しました。修正版は22.23.4・24.22.1・26.12.1です。告知で深刻度が「high」とされた問題には、次のようなものがあります。

  • Brotli(圧縮形式の1つ)の展開処理での範囲外書き込みで、リモートからのコード実行につながりうる問題(CVE-2026-111497)
  • TLSの証明書のホスト名の検証が、条件によってすり抜けられる問題(22.xも対象のものはCVE-2026-106069)
  • dns.resolve がNUL文字を含む名前を切り詰める問題(CVE-2026-105482)

私たちは、このうちBrotliの問題を最も重く見ました。また、告知の依存関係の更新には「ca-cert (eMudhra Intermediate CA Compromise)」が22.xを含む3系列すべてに入っています。第2回の端末の点検では、22.23.4がテスト用のホストを CERT_REVOKED で拒否することを確かめていました。

告知は、サポートが終わった版は常に影響を受けると書いています。Node.js 20以前には修正版が出ないため、今回の作業は「20以前を22系に上げる」か「22系の中で22.23.4に上げる」かのどちらかになります。

Node.jsの版はどこに書かれているか

棚卸しは、AIエージェントに読み取りだけを許して進めました。対象は、開発用フォルダにある39個のgitリポジトリと、GitHubの組織にある107リポジトリです。組織のリポジトリのうち手元にない88個は、GitHubのAPIで既定のブランチの中身を読み、JavaScript/TypeScriptを使っている27個を確認しました。

Node.jsの版が書かれていた場所は、次のとおりです。

書かれている場所書き方の例見るときの注意
package.json の volta・engines"engines": { "node": ">=20" }engines は範囲の宣言で、使う版そのものは決めない
.nvmrc・.node-version22.22.2手元のツールやビルドサービスが読む
DockerfileFROM node:20-alpine本番のコンテナで動く版を決める
GitHub Actionsのワークフローactions/setup-node の node-version'22' だけでは具体的な版が決まらない(後述)
Cloud Buildの設定手順の name: nodeタグを書かないと最新のイメージになる
Firebaseなどの実行環境の設定関数の実行環境の指定ビルドとは別に、本番で動く版を決める

1つのリポジトリの中を git grep で一覧にする方法は、Node.jsのリリース計画の記事の「版を固定している場所を洗い出す」で紹介しています。今回は、それを組織のリポジトリ全体に広げた形です。

棚卸しで分かったことが1つあります。手元のフォルダでは古い版に見えた2つのリポジトリが、GitHubの既定のブランチではすでに22.23.4になっていました。手元のフォルダが最新の変更を取り込んでいない(pullしていない)だけだったのです。対象かどうかは、手元ではなくGitHub上の既定のブランチで判断する必要があります。

対象を5つに絞る

調べた結果を状態ごとに分け、次のように判断しました。

状態判断
Node.js 14で止まっているAngularなどの更新も要るため、別の企画として相談する
Node.js 20以前で、使われておらず後継がある更新よりアーカイブ(読み取り専用にして残すこと)を勧める
Node.js 20をDockerで使い、稼働している対象(22.23.4へ上げる)
22系だが、修正版より前の版に固定している対象
GitHub Actionsに '22' とだけ書いている対象
Cloud Buildで node(タグなし)を使っている(4つ)ビルドのたびに最新のイメージが使われると判断し、今回は対象外
GitHub上ではすでに22.23.4(2つ)対応済み
クライアントのリポジトリ(1つ)先方の確認を待つため除外

@types/node(Node.jsの型定義のパッケージ)の版は、実際に動くNode.jsの版を決めないため、対象から外しました。こうして、5つのリポジトリを、1リポジトリにつき1つのプルリクエストで上げることにしました。

版の数字は .node-version の1か所に書く

版の数字を書く場所が多いほど、次の更新で直し漏れが出ます。そこで、すでに対応済みだった2つのリポジトリと同じ書き方にそろえました。版の数字は .node-version に1か所だけ書き、ほかの設定はできるだけそれを読むようにします。

22.23.4

GitHub Actionsでは、node-version をやめ、node-version-file で .node-version を読ませます。次は、このサイトのリポジトリでの変更です(actionの版は変えていません)。

       - uses: actions/setup-node@v4
         with:
-          node-version: '22'
+          node-version-file: .node-version
           cache: 'npm'

'22' のままにしなかった理由は、actions/setup-nodeのREADMEにあります。check-latest の既定は false で、このときアクションは、まずランナー(ワークフローを実行するマシン)にキャッシュされている版から条件に合うものを探します。そのため、ランナーに入っている22.xが修正版より古ければ、その古い版が使われることがあります。check-latest: true にすれば最新版を確かめにいきますが、どの版で動いたかが差分に残らないため、私たちは具体的な版を書くほうを選びました。

READMEには、node-version と node-version-file の両方を書くと node-version が使われる、とも書かれています。書き換えるときは、node-version の行を必ず消します。また、node-version-file はリポジトリの中のファイルを読むため、ワークフローで actions/checkout が actions/setup-node より前にある必要があります。

engines は、もともと書かれていたリポジトリだけを次の範囲に変えました。新しく書き足すことはしていません。engines は既定ではインストールを止めないため(Node.jsのリリース計画の記事の「engines は止めてくれない前提で扱う」を参照)、使う版を決める役割は .node-version に任せています。

"engines": {
  "node": ">=22.23.4 <23"
}

Dockerfileは、もとのイメージの種類(alpineなどのvariant)を変えずに、版まで書いたタグにしました。Node.js 20のalpine版を使っていたAPIサーバーは、次のようになります。

FROM node:22.23.4-alpine

Cloud Buildは .node-version を読めないため、手順のイメージに版を直接書きます。

steps:
  - name: "node:22.23.4"

AIエージェントで並行して上げ、結果を確かめる

作業は、AIエージェントを2つの役割に分けて進めました。全体をまとめるエージェント(オーケストレーター)が、リポジトリごとに作業用のエージェント(ワーカー)を1つずつ立て、並行で動かします。各ワーカーには次の3つを渡しました。

  • 変更してよいファイル
  • 実行する検証のコマンド(テスト、build、lint、型チェックなど)
  • 禁止事項(依存パッケージ・lockfile・型定義の変更、コミット)

作業場所は、各リポジトリの最新の既定ブランチから作ったworktree(同じリポジトリの別の作業フォルダ)です。手元のフォルダが古いという棚卸しで見つかった問題を避け、ほかの作業とも混ざらないようにしました。

ワーカーの報告を受けたあと、オーケストレーターが差分を読み、次の2点を確かめました。

  • ワークフローで actions/checkout が actions/setup-node より前にあること
  • node:22.23.4 と node:22.23.4-alpine のイメージが実在すること(docker manifest inspect で確認)

検証はすべて手元のNode.js 22.23.4で行いました。テストのあるリポジトリでは、25件・28件・201件・59件のテストがそれぞれすべて成功し、各リポジトリのbuild・lint・型チェックも通りました(次に書くフロントエンドの型エラーを除きます)。Node.js 20から22に上げたリポジトリでは、バックエンドのbuildと型チェック、docker build が成功し、コンテナの中のNode.jsがv22.23.4であることを確かめました。

同じリポジトリのフロントエンドでは、型エラーが10件出ました。原因はUIライブラリ(MUI v7)で廃止された書き方で、Node.js 20でも同じエラーが出たため、今回の変更とは関係ないと判断しました。もとからあった失敗かどうかは、変更前の版で同じことが起きるかを試すと切り分けられます。

うまくいかなかったこともあります。並行で動いた2つのワーカーが同じ名前のログファイルに書き込み、出力が混ざりました。並行で動かすときは、ログのファイル名を作業ごとに分けておくべきでした。

マージの順番と本番での確認

マージは、担当者がAIエージェントに明示して依頼してから行いました。順番は、デプロイへの影響が小さいものからです。

  1. デプロイのないリポジトリ(1つ)
  2. CIだけが変わるリポジトリ(1つ)
  3. マージすると自動でデプロイされるリポジトリ(3つ)

自動でデプロイされる3つのうち2つは、GitHub Actionsでデプロイされます。この2つでは、Actionsのデプロイの成功、/healthz(稼働確認用のURL)の応答、認証画面への302の転送(想定どおりの動き)を確かめました。Actionsのログには Acquiring 22.23.4 と出ており、node-version-file で指定した版が使われていることも確認できました。残る1つはこのサイトのリポジトリで、こちらはデプロイ自体は成功したものの、後で書くように本番のビルドに古い版が使われていました。

想定外だったのは、「CIだけが変わる」と見ていたリポジトリです。マージをきっかけにCloud Buildのトリガーが動き、静的サイトを本番に再デプロイしました。中身は同じで、デプロイも成功しましたが、マージの前にトリガーの設定まで確かめるべきでした。リポジトリの中のワークフローだけを見ていると、GitHubの外で設定されたトリガーを見落とします。

このサイトの本番ビルドが22.22.2のままだった

このサイト(gleamhub.net)は、mainへのマージをCloudflare Workers Buildsが検知して、ビルドと公開をしています。このサイトのリポジトリは今回の5つのうちの1つで、.node-version(22.23.4)を加えてマージしましたが、本番のビルドは22.22.2のままでした。

原因は、Workers Buildsのダッシュボードに、環境変数 NODE_VERSION=22.22.2 が設定されていたことです。9月にWorkers Buildsへ移したときに設定したものでした。CloudflareのドキュメントBuild imageには、環境変数と .nvmrc・.node-version のどちらでも版を指定できるとありますが、両方あるときにどちらが優先されるかは書かれていません(10月22日に確認)。私たちの環境では、環境変数がリポジトリの .node-version より優先されました。

Node.jsの版を読む経路の図。版の数字は.node-version(22.23.4)の1か所に書き、GitHub Actionsはnode-version-fileでそれを読む。DockerfileとCloud Buildはイメージのタグに22.23.4を直接書く。Workers Buildsでは環境変数NODE_VERSION(22.22.2)が.node-versionより優先され、本番ビルドは22.22.2のままだったため、環境変数を22.23.4に変えた

対応は次の順に進めました。

  1. ダッシュボードでの手作業になるため、作業の内容をissueに起票した
  2. ダッシュボードで NODE_VERSION を22.23.4に変え、ビルドをやり直した
  3. ビルドのログで、使われた版を確かめた
Detected the following tools from environment: nodejs@22.23.4
Installing nodejs 22.23.4

新しいバージョンが100%で公開され、トップページとメディアの一覧がHTTP 200を返すことを確かめました。最後に、社内のCloudflareの手順書の記載を、実際の設定に合わせて直しました。

気になる表示もありました。ダッシュボードのBuildの設定で、APIトークンが「利用できません」と警告されていました。トークンを別のメンバーが作ったためだと私たちは見ています(推測)。変数を保存したあとも、ビルドは同じトークンで成功しました。

この件で分かったのは、「リポジトリのファイルを直した」ことと「本番が新しい版でビルドされた」ことは、別々に確かめる必要があるということです。今回も、ビルドのログで版を見るまでは、本番が古い版のままだと気づけませんでした。

残った課題

今回の5つで終わりではありません。次のことが残っています。

  • Node.js 14のリポジトリと、使われていないリポジトリ:Node.js 14のものは、Angularなどの更新と合わせた別の企画として相談します。使われていないものは、アーカイブを提案している段階です
  • クライアントのリポジトリ:先方の確認を待っています
  • Cloud Buildで node とだけ書いた4つ:版が固定されていません。Docker Hubで確かめると、10月22日時点で node(latest)のタグは26.12.1と同じイメージを指していました。修正は入っていますが、メジャーバージョンの切り替わりもそのまま入ってくるため、固定するかどうかは別に決める必要があると考えています
  • Workers Buildsの版を書く場所が2つ:NODE_VERSION と .node-version に同じ版を書いているため、次の更新でも両方を直します。環境変数を消して .node-version だけにする案もありますが、そのときにファイルの版が使われるかはまだ試していません
  • 守られる範囲:本番がCloudflare Workersで動くサービスは、実行時にNode.jsの証明書ストアを使わないため、今回の更新で守られるのは主にビルドとCIの環境になる見込みです(推測を含み、実行環境では確かめていません)
  • 22系の終わり:22系のサポートは2027年4月30日に終わります(Node.jsのリリース計画の記事を参照)。次の移行先と時期を決めておく必要があります

確認した範囲

2026年10月22日に私たちが社内のリポジトリで行った作業の記録(棚卸し、変更、手元のNode.js 22.23.4での検証、マージ後の確認、このサイトのWorkers Buildsの設定変更とビルドのログ)をもとに書いています(自社の実務経験)。あわせて同日に、Node.jsの告知「Wednesday, October 21, 2026 Security Releases」と nodejs.org/dist/index.json、actions/setup-nodeのREADMEとadvanced-usage、CloudflareのBuild imageの原稿(cloudflare-docsリポジトリ)、Docker Hubの node イメージのタグを確認しました(資料調査)。Workers Buildsで環境変数を消した場合の動き、Firebaseなどの実行環境の設定、Node.js 14のリポジトリの移行、Cloud Buildがイメージを取得する仕組みは確認していません。リポジトリの名前、クライアント、個別のプルリクエストは非公開のため伏せています。

複数のリポジトリにまたがる依存の更新や、AIエージェントを使った保守の進め方は、開発・AI・自動化のご相談からお問い合わせください。

参考資料

この記事を共有XFacebook
鈴木 翔

技術の可能性に魅了され、学生時代からプログラミングとデジタルアートの分野に深い関心を持つ

テーマに沿って読み進める脆弱性の緊急対応を記録で振り返る

この記事のテーマを、自社の次の一歩へ

自社での進め方を、具体的に。

つくりたい仕組み、既存システム、運用の条件を整理し、実現に向けた次の一歩を考えます。

  • 実現したい仕組み
  • 既存環境との接続
  • 運用の条件
開発・運用の構想を相談する

構想段階からご相談いただけます。この記事の情報を相談フォームに引き継ぎます。

最新記事をメールで受け取る