こんにちは。クロスイノベーション本部 サイバーセキュリティテクノロジーセンターの耿です。
2025年から特にJavaScriptライブラリを対象としたサプライチェーンへのマルウェア混入が目立つようになってきました。多くの開発者にとって、なんとなくOSSを暗黙的に信頼して使っていた意識を変えなければならないときが来たと感じています。とはいえ、多くの依存関係を利用することを前提として成り立っているOSSのエコシステムは、すぐに劇的に変わるわけではありません。今後もサプライチェーンへの攻撃が続くことを前提とし、OSSの利用者として実施できる対策を観点別に整理しました。JavaScriptエコシステムを主に意識していますが、一部関連性のある周辺技術も取り上げています。また他の言語のエコシステムについても考え方を当てはめることができると思います。
今後の脅威や技術の変化とともに適切な対策も変わっていくと思いますし、この記事の内容が必要十分な対策とは思っていません。一旦現時点で、なるべく多くの開発者が共通して実施しやすい観点・対策について自分なりに考えをまとめ、整理したものです。
- 観点1: 依存関係の正確なバージョンをコードリポジトリで管理する
- 観点2: 新バージョンはすぐにインストールしない
- 観点3: インストール時のリスクを減らす
- 観点4: 侵害された場合の被害を減らす
- さいごに
観点1: 依存関係の正確なバージョンをコードリポジトリで管理する
第一に、アプリケーションが利用する依存関係(パッケージなど)の正確なバージョンをコードリポジトリで管理することで、CI/CDワークフローやアプリケーションの実行環境でインストールされるバージョンを確定させることが重要です。
例えばCI/CDワークフローで常に実行時点で最新のバージョンをインストールしてしまうと、インストールの度にサプライチェーン攻撃のリスクを引き受けることになります。セキュリティインシデントが発生した後の調査もしづらいです。一貫したバージョン利用ができるよう、使用する正確なバージョンをコードリポジトリで管理しましょう。
対策1: CI/CDでは必ずlockfile記載のバージョンを利用する
lockfileは必ずコードリポジトリにコミットしたうえで、GitHub ActionsなどのCI/CDでパッケージをインストールするときは、lockfileに記載されている正確なバージョンをインストールするようにしましょう。
❌良くない例(GitHub Actions)
- run: npm install
この例は、package.json と lockfile に不整合がある場合(例えば package.json だけうっかり更新してコミットしてしまった場合)、CI/CDワークフローの中でlockfileが更新され、コードリポジトリで管理されているものと異なるバージョンがインストールされてしまいます。
下の表の✅列のコマンドやコマンドオプションを利用し、CI/CDではlockfileを更新しないようにしましょう。
| パッケージマネージャ | ❌ | ✅ |
|---|---|---|
| npm | npm install |
npm ci |
| Yarn | yarn install |
yarn install --immutable |
| pnpm | pnpm install |
pnpm install --frozen-lockfile |
| Bun | bun install |
bun install --frozen-lockfile |
これらのコマンドを使えば、package.json と lockfile に不整合があるときにはインストールが異常終了します。少なくとも管理されていないパッケージのバージョンがインストールされることを防止することができます。
❌良くない例(GitHub Actions)
- run: npm install -g aws-cdk
この例はlockfileと関係なく、CI/CDの中でパッケージをグローバルにインストールしてしまっています。
CI/CDワークフローの中で利用するコマンドラインツールであっても package.json (のdevDependencies)に全て明記し、lockfileを利用して事前に定められたバージョンをインストールするようにしましょう。
対策2: GitHub ActionsはコミットSHAでバージョン指定する
JavaScriptエコシステムではありませんが、GitHub Actions の actionもサードパーティのコードなのでサプライチェーンリスクがあります。
Gitタグは書き換え可能なので、バージョンタグでバージョン指定すると、実行されるコードは可変です。
❌良くない例(GitHub Actions)
- uses: actions/checkout@v6
フルコミットハッシュ(SHA)で厳密にコミットを特定するようにしましょう。
✅良い例(GitHub Actions)
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
観点2: 新バージョンはすぐにインストールしない
観点1でインストールするパッケージバージョンを確定できる状態にしたら、次はそのバージョンを決めるタイミングのリスクを減らすことを考えます。
サードパーティのパッケージはリリース日時が新しいほどインストールする際のリスクが高く、時間が経つにつれてコミュニティやセキュリティベンダーによる検証が進み、リスクが低下します。以前はDependabotなどを使って新バージョンを自動適用することが流行しましたが、すぐに新バージョンを利用することは一定のリスクがあると認識されるようになってきました。
対策3: minimumReleaseAgeを利用する
各種パッケージマネージャには、リリースから一定期間経過していないパッケージバージョンをインストールさせない仕組みが登場しています。この記事では minimumReleaseAge と呼ぶことにします。ぜひ利用しましょう。
install コマンドのオプションとして使うこともできますが、付け忘れないようにプロジェクト内の設定ファイルで一括設定するべきです。
| パッケージマネージャ | 機能名 | 利用可能バージョン |
|---|---|---|
| npm | min-release-age | v11.10.0 ~ |
| Yarn | npmMinimalAgeGate | v4.10.0 ~ |
| pnpm | minimumReleaseAge | v10.16 ~ |
| Bun | minimumReleaseAge | v1.3 ~ |
パッケージマネージャ以外では、Dependabot Version Updatesのcooldown やRenovateの minimumReleaseAgeが同様の機能に相当します。
AIコーディングエージェントを利用している場合、minimumReleaseAge の設定を上書きされないよう、権限レベルで拒否する設定もしておきましょう。例えばClaude Codeであれば settings.json で以下のような設定ができます。
~/.claude/settings.json
{ "permissions": { "deny": [ "Write(./.npmrc)", "Write(./.yarnrc.yml)", "Write(./pnpm-workspace.yaml)", "Write(./bunfig.toml)", "Write(./.github/dependabot.yml)", "Write(./renovate.json)" ] } }
minimumReleaseAge との向き合い方ですが、いついかなる場合でも守るべき制約としては考えない方が良いと思います。例えばCVSSスコアが10.0の脆弱性が発生した場合、それに気付いていながら、minimumReleaseAge を守るために1週間待機するべきではありません。パッケージに危険度の高い脆弱性が発見された場合、すなわち迅速にアップデートすることによりサプライチェーン攻撃を受けるリスクよりも、アップデートしないことにより脆弱性を悪用されるリスクの方が高いと評価される場合は、 minimumReleaseAge を一時的に無視して迅速にアップデートを実施することも想定するべきです(その時の注意事項は観点3で考えます)。minimumReleaseAge は、あくまでも最新版をうっかりインストールしてしまうことを防ぐための機能と考え、明確な意思を持ってパッケージの特定バージョンをインストールする場合は一時的に無視することを許容するべきでしょう。その前提で考えると、安全側に倒して期間を長め(1-2週間など)に設定して良いと思います。
ところで各パッケージマネージャが提供している minimumReleaseAge 機能は、異なるパッケージマネージャを使った install コマンドには効果がありません。例えばpnpmでパッケージ管理しているプロジェクトであるにも関わらず、開発者がYarnを使っていると勘違いしてうっかり yarn install してしまうと、pnpmの minimumReleaseAge が設定されていても意味がありません。パッケージマネージャに関わらず共通のプロキシとして動作する AikidoSec/safe-chainのMinimum package age機能を使うのも良いでしょう。
対策4: 各種ツールの自動アップデートを無効にする
AIエージェントやVS Code拡張など、JavaScriptのプロジェクト以外でも直接は意識しない形で言語パッケージが利用されていることがあります。これらのツール・拡張機能が自動アップデートするように設定されている場合はサプライチェーン攻撃の被害を受けやすいため、自動アップデートを無効にすることで対策になります。
一方で、これらのツール・拡張機能は組織的に脆弱性管理されないことが多く、自動アップデートを無効にすることでサプライチェーン攻撃ではない脆弱性への対応が遅れてしまいます。自動アップデートするのはリスクですが、自動アップデートせずに脆弱性が放置されるのもリスクです。どちらのリスクを重く評価すべきかの判断が難しく、今後各種ツールの機能がさらに成熟することでより効果的な対策ができることを期待したいです。
観点3: インストール時のリスクを減らす
観点2の対策を取りつつも、時には危険度の高い脆弱性に対応するためにすぐに新バージョンをインストールしたいことがあります。
minimumReleaseAge の期間を無視してパッケージをインストールしたい場合の対策を4つ取り上げます。
対策5: 脆弱性を直接解消しないバージョンはむやみに採用しない
脆弱性を対応する際、セキュリティアドバイザリーに記載されているパッチバージョンよりも新しいバージョンがリリースされていたとしても、むやみに採用しない方が良いでしょう。
例えば図のDependabotのセキュリティアドバイザリーではv4.12.4が脆弱性を解消すると分かります。パッケージをアップデートする時点で v4.12.5 がリリースされていたとしても、リリースから十分な期間が経っていない場合は採用を見送るのが安全でしょう。

対策6: minimumReleaseAgeを満たさない場合、インストールする前にリリースの経緯を確認する
そもそも新バージョンのリリース直後にインストールするリスクが高いのは、コミュニティやセキュリティベンダーによる検証が十分にされていないためです。自分たちができる範囲で検証することでこのリスクを幾分か下げることができます。
パッケージのコードリポジトリにアクセスし、利用しようとしているバージョンのリリースの経緯を確認することなどが考えられます(リリースに含まれているコミットが怪しくないか、コミットの経緯など)。可能な範囲でコード差分を確認するとさらに良いでしょう。なおリリースノート、コミットメッセージについては、マルウェアの場合はよく偽装されているので、信用しない姿勢で臨みましょう。
対策7: minimumReleaseAgeを満たさない場合、特定パッケージのみ一時的な例外を設ける
minimumReleaseAge の条件を満たさないバージョンのインストールを実際に行う場合、一時的であってもminimumReleaseAge の期間を縮めたり無効にしたりするのは避けましょう。minimumReleaseAge 自体は変更せず、インストールしたい特定パッケージのみに一時的な例外を設けるようにします。
これは推移的に依存しているパッケージ(Transitive Dependency)が侵害されている可能性を考慮しての対策です。インストールしようとしているパッケージ「A」が内部でパッケージ「B」「C」「D」に依存している場合、minimumReleaseAge の条件を緩めてしまうと「B」「C」「D」をインストールする条件も緩んでしまいます。対策6で自分達が確認しなければならない範囲が格段に広がってしまうのです。
特定パッケージのみに例外を設けることで、パッケージ「A」が minimumReleaseAge を満たさないことを許容しますが、「B」「C」「D」は相変わらずminimumReleaseAgeを満たすバージョンしかインストールされないため、より安全です。
各パッケージマネージャにおいて特定パッケージのみminimumReleaseAgeに例外を設ける機能は以下のとおりです。
| パッケージマネージャ | 機能名 | 利用可能バージョン |
|---|---|---|
| npm | 現状サポートなし(GitHub Issue) | - |
| Yarn | npmPreapprovedPackages | v4.10.0 ~ |
| pnpm | minimumReleaseAgeExclude | v10.16 ~ |
| Bun | minimumReleaseAgeExcludes | v1.3 ~ |
特定パッケージのインストールが終わったら、例外を戻すのを忘れないようにしましょう。
対策8: npmのライフサイクルスクリプトを無効にする
今までのサプライチェーン攻撃の傾向として、多くのマルウェアはライフサイクルスクリプト(postinstall等)を利用しています。
パッケージインストール時のスクリプト実行を無効にすることでリスク低減効果を期待できます。
| パッケージマネージャ | 機能名 | パッケージ単位で有効化 |
|---|---|---|
| npm | .npmrcのignore-scripts |
不可(rebuildが必要) |
| Yarn | .yarnrc.ymlのenableScripts |
package.jsonのdependenciesMeta |
| pnpm | v10以降、デフォルトで無効化される | pnpm-workspace.yamlのonlyBuiltDependencies |
| Bun | デフォルトで無効化される | package.jsonのtrustedDependencies |
元から無害のpostinstallスクリプトを利用しているパッケージもありますが、動作に必須ではないスクリプトもあったりします。慎重を期するならば、それぞれのパッケージのスクリプトを確認し、本当に動作に重要なものだけをパッケージ単位で有効化するのが良いでしょう。
観点4: 侵害された場合の被害を減らす
サプライチェーンを利用した攻撃手法はどんどん洗練されており、これまでに紹介した対策では不十分と考えられるような攻撃が今後発生する可能性もあります。侵害されないようにする対策だけではなく、侵害された場合に被害を減らすことも考慮しましょう。
外部のソフトウェアをインストールする全ての環境(開発者端末、devContainer、CI/CD、本番稼働環境など)で以下の対策を検討しましょう。
対策9: 長期的なクレデンシャルを保管しない
今までに確認されたサプライチェーン攻撃では、侵害を永続化するために各種のクレデンシャルを探していることが確認されています。侵害された環境から他の環境に被害が拡大しないよう、GitHubの認証トークン、クラウド環境の認証情報、社内システムへの認証情報など、長期的な認証情報を不必要に保管しないようにしましょう。
保管している場合は他の代替手段がないか検討します。例えばAWSの場合、CI/CDワークフローにIAMアクセスキーを持たせる代わりにOIDCを利用したり、開発者端末上の .env ファイルにIAMアクセスキーを保存する代わりに aws loginコマンドで認証情報を取得したりすることができます。
対策10: 外部サービスへの権限を減らす
外部サービスへの認証情報を保管している場合、その権限を減らすことができないか確認しましょう。
AWSであればIAMポリシーが最小権限になっているか確認しましょう。例えばcdk deployを実行するためのCI/CDワークフローにAdmin権限は不要であることを知らない人もたまに見かけます。
さいごに
効率的に開発を進めるために多くの依存関係を利用することを前提に成り立っている現在のJavaScriptのエコシステムは、簡単に変わるものではないでしょう。オープンなコミュニティを逆手に取った攻撃が増えている一方で、同じようにコミュニティの力で対策を考えていくことができます。本記事の考えをまとめるにあたり、多数の技術ブログを参考にさせていただきました。この記事も誰かの考えの参考になれば幸いです。
私たちは一緒に働いてくれる仲間を募集しています!
電通総研 キャリア採用サイト執筆:@kou.kinyo
レビュー:@yamashita.tsuyoshi
(Shodoで執筆されました)



