In this article
npmのセキュリティ対策:2025年のShai Hulud攻撃後にパッケージを保護する方法
Shai-Hulud、Nx、event-stream、colors、node-ipcなどの攻撃は、パッケージマネージャーが単なるライブラリのダウンローダーではなく、実行エンジンとして機能することを示しています。サードパーティ依存関係の管理には細心の注意を払い、適切なセキュリティ対策を講じる必要があります。
以下は、安全なローカル開発とオープンソースソフトウェアのメンテナープロセスのために推奨する、実践的でセキュリティに重点を置いたnpmパッケージマネージャー強化策の一覧です。ターミナルの横に開いておきましょう。
以下では、開発者とオープンソースのメンテナーが責任を持って利用できるよう、セキュリティ対策、ツール、分野の重要なポイントを紹介します。
npm、pnpm、Bun、Yarn、Denoの安全性を既定で高めるCLIオプション
サプライチェーンの強化と再現可能なインストール
ロックファイルと依存関係の健全性
脆弱性とパッケージの健全性チェック
シークレットの取り扱いと開発環境の分離
メンテナーの実践:2FA、provenance、OIDC、依存関係の削減
Shai-Huludとサプライチェーンマルウェアについて
Shai-Hulud攻撃ファミリーは、JavaScript開発者にとって転換点となる攻撃です。npm installは、無害な便利機能ではなく、リモートコード実行の手段であることが明らかになりました。2025年9月、最初のShai-Huludキャンペーンでは、ngx-bootstrap、ng2-file-upload、@ctrl/tinycolorなどのパッケージを改ざんし、npmのライフサイクルスクリプトを通じてワームのようなペイロードを配信しました。悪意あるpostinstallフックが難読化されたbundle.jsファイルを取得し、開発者のマシンやCIエージェント上で実行。npm、GitHub、クラウドの認証情報を収集し、WebhookやGitHub Actionsのワークフロー経由で外部に送信しました。最終的に数百のパッケージが関与したことが確認されています。
2025年11月24日、同じ手口の第2波としてSHA1-Huludが出現し、攻撃者がいかに速く手法を進化させるかを示しました。この亜種は、preinstallスクリプトにペイロードを隠したトロイの木馬化npmパッケージを通じて拡散します。パッケージがインストールされると、ワームは被害者の環境を攻撃者が制御するGitHub Actionsのセルフホステッドランナーに変えようとし、リポジトリに悪意あるワークフローを注入して任意のコマンドを実行し、npmとGitHubのシークレットを窃取します。AWS、Azure、GCPの認証情報を積極的に探し、場合によってはコンテナからの脱出、ホスト上での権限昇格、ユーザーのホームディレクトリを破壊する「ワイパー」動作まで試みます。執筆時点で、Zapier、PostHog、Postmanなどのベンダーが提供する人気パッケージを含め、600を超えるパッケージがこのキャンペーンに関連すると特定されており、その数は現在も増え続けています。
Shai-HuludとSHA1-Huludは、現代のサプライチェーンマルウェアの明確な手口を示しています。npmのライフサイクルフックを実行手段として悪用し、開発者のワークステーションやCI/CDインフラを中継地点として武器化し、クラウドの認証情報、トークン、シークレットを主な標的にします。各インシデントの詳細は今も変化していますが、その傾向は十分に明確です。あらゆるJavaScript開発者が習慣として身につけるべき、長く役立つ対策を導き出せます。
このチートシートでは、得られた教訓を今日から実践できる具体的な対策にまとめています。安全性を既定で高めるパッケージマネージャー設定、サプライチェーン攻撃への備え、安全で再現可能な依存関係解決の徹底、継続的な脆弱性・パッケージ健全性チェックの導入、そして各対策をnpm、pnpm、Bunなどのツールチェーンに適用する方法を取り上げます。Shai-Huludだけに対応するのではなく、それが象徴する種類の攻撃に対して、日々のnpm利用を強靭にすることが目的です。
クイックインデックス
post-installスクリプトを無効にする
クールダウン期間を設けてインストールする
npqでインストールを強化するロックファイルへの不正な注入を防ぐ
再現可能なインストールを使う(
npmciなど)無条件のアップグレードを避ける
.envに平文のシークレットを保存しないコンテナ内で開発する
npmの2FAを有効にする
provenanceを付けて公開する
OIDC(信頼された公開)で公開する
依存関係ツリーを縮小する
1. post-installスクリプトを無効にする
目標は、install中に任意のコードが実行されないようにすることです。
リスク
post-installスクリプト(およびその他のライフサイクルスクリプト)は、サプライチェーン攻撃の主要な手口です(Shai-Hulud、Nx、event-stream)。依存関係や推移的依存関係は、どれもインストール時に任意のコードを実行できます。
npmの基本的な強化策
グローバル設定:すべてのライフサイクルスクリプトを無効にする(推奨):
# Safe-by-default on your machine
npm config set ignore-scripts trueインストールごとに設定する場合:
# One-off installs without running lifecycle scripts
npm install --ignore-scripts <package-name>pnpm
v10以降、pnpmは既定でpostinstallスクリプトを無効にし、許可リストや再有効化の仕組みをサポートしています。詳しい設定例はpnpmのサプライチェーンセキュリティドキュメントを確認することをおすすめします。
Bun
Bunは既定でpostinstallスクリプトを無効にしています。内部の許可リストも管理しています。package.jsonのtrustedDependenciesで、特定の依存関係を明示的に信頼できます。
{
"trustedDependencies": [
"some-package",
"another-package"
]
}本当に必要なスクリプトだけを実行する
package.jsonを無条件に信頼するのではなく、許可リストを使いましょう。
1# Use LavaMoat's allow-scripts to define where scripts may run
2npm install --save-dev @lavamoat/allow-scripts
3npx allow-scriptsLavaMoatのallow-scripts npmパッケージを使うと、bcryptやplaywrightなど、preinstallまたはpostinstallスクリプトを正当に必要とする信頼できるnpmパッケージに対して、依存関係グラフの特定の位置でスクリプトを選択的に有効化できます。
2. クールダウン期間を設けてインストールする
目標は、「公開されたばかりの悪意ある」リリースやタイポスクワッティングの罠を避けることです。
リスク
攻撃者はSemVerと「latest」による解決を悪用し、新しいバージョンを公開した後、すぐに特定・取り下げられるようにします。すぐにインストールすると、被害が広がる危険にさらされます。
npm:公開日時を基準にインストールする
指定日より前に公開されたバージョンだけに固定する:
# Install only if published before 2025-01-01
npm install express --before=2025-01-01動的な7日間のクールダウン(BSD形式のdateの例):
npm install express --before="$(date -v -7d)"
注:自動化には手動対応が必要で壊れやすい方法ですが、明示的に安全性を高める手段として役立ちます。
2.1 pnpmのminimumReleaseAge
pnpm-workspace.yamlに設定します。
minimumReleaseAge: 20160 # minutes; here: 2 weekspnpmはこの期間より新しいバージョンを拒否するため、悪意あるリリースや不具合のあるリリースをエコシステムが検出する時間を確保できます。
2.2 Snykによる自動PRのクールダウン
Snykの依存関係自動アップグレードPRでは、公開から約21日未満のバージョンをスキップし、次のリスクを軽減します。
すぐに取り下げられる不具合バージョンへのアップグレード
侵害されたアカウントから公開されたパッケージへのアップグレード
これは「自動化に組み込まれたクールダウン」のパターンです。
3. npqでインストールを強化する
目標は、基本的なセキュリティと妥当性のチェックに合格するまで、パッケージをインストールしないことです。
問題点
次のコマンドを実行するとします。
npm install some-package次のことが分からないままです。
そのパッケージが人気パッケージの名前をまねたものか
前日に公開され、利用実績がないものか
既知の脆弱性があるか
危険なpre/post-installスクリプトが含まれているか
対策:インストールコマンドの前にnpqを使う
npqは、インストール前にセキュリティを監査するツールです(チェックには「marshalls」を使用します)。
インストール:
npm install -g npqnpmの代わりに使用する:
npq install express既定のコマンドにする:
alias npm='npq-hero'
# Persist the alias
echo "alias npm='npq-hero'" >> ~/.zshrc # or ~/.bashrc
source ~/.zshrcnpqパッケージを使うと、npqとnpq-heroがインストールされます。後者はnpmの代わりにそのまま使えるラッパーです。
npqがチェックする項目(「marshalls」)
Snyk CVE DBを使った脆弱性チェック
新規パッケージの検出(公開から22日未満)
バージョンの成熟度(公開から7日未満)
タイポスクワッティングを狙った類似パッケージ
npmレジストリの署名検証
ビルドprovenanceの証明
pre/post-installスクリプトの有無
パッケージの健全性:README、LICENSE、リポジトリURL、ダウンロード数
バイナリの導入(新しいCLIツール)
非推奨化の兆候
メンテナーのドメインの有効性/期限切れドメイン
pnpmとBunのインテグレーション
# One-off
NPQ_PKG_MGR=pnpm npq install fastify
NPQ_PKG_MGR=bun npq install fastify
# Make pnpm go through npq
alias pnpm="NPQ_PKG_MGR=pnpm npq-hero"4. npmロックファイルへの不正な注入を防ぐ
目標は、package-lock.jsonやyarn.lockによって、悪意ある配布元へ気づかないうちに誘導されないようにすることです。
リスク
コントリビューター(またはPR経由の攻撃者)は、次のことができます。
ロックファイルに悪意あるパッケージを追加する。
resolvedURLを、攻撃者が管理するホスト(Gitリポジトリ、tarball、gist)に変更する。integrityハッシュを調整し、「正当」に見せかける。
その結果、package.jsonに問題がなく見えても、次のinstallでマルウェアが取得されます。
対策:lockfile-lint
インストール:
npm install --save-dev lockfile-lint許可するホストとHTTPSを指定してロックファイルを検証する:
npx lockfile-lint \
--path package-lock.json \
--type npm \
--allowed-hosts npm yarn \
--validate-https主な検証オプション
ホストの検証 –
npm、yarn、verdaccioなどに限定HTTPSの強制 – 安全でないURLスキームを拒否
スキームの検証 –
https:、git+https:、git+ssh:を許可リストに登録パッケージ名の検証 – 解決先URLとパッケージ名が一致することを確認
integrityの検証 – 安全なSHA-512 integrityハッシュを必須にする
CI/CDとのインテグレーション
インストール前にロックファイルをlintするステップを追加する:
{
"scripts": {
"lint:lockfile": "lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https",
"preinstall": "npm run lint:lockfile"
}
}pnpmとロックファイルへの不正な注入
pnpmのpnpm-lock.yamlは、次の理由から耐性が高くなっています。
同じ方法で任意のtarball URLに依存しない
ロックファイルにあっても、
package.jsonにないパッケージはインストールしないnpm/yarnで見られる複数の注入経路を、フォーマット上回避している
それでも、ロックファイルはセキュリティ上重要な成果物として扱いましょう。
5. 再現可能なインストールを使う(npm ci)
目標は、ビルド環境と本番環境で、ロックファイルに記録されたバージョンを正確に使用することです。
リスク
npm installはpackage.jsonとロックファイルの不一致を「修正」しようとするため、次の問題が起こる可能性があります。
記録とは異なるバージョンが取得される
CI/本番環境で再現性が損なわれる
予期しない脆弱なバージョンや悪意あるバージョンが導入される
npm:installではなくciを使う
ローカルとCI:
# Deterministic install based on package-lock.json
npm ciCI/CDで本番用依存関係のみをインストールする場合:
npm ci --only=productionロックファイルをコミットし、最新の状態に保ちましょう。
パッケージマネージャーごとの再現可能なコマンド
Yarn:
yarn install --immutable --immutable-cachepnpm:
pnpm install --frozen-lockfileBun:
bun install --frozen-lockfileDeno:
deno install --frozenロックファイルはサプライチェーンの契約の一部であり、無視してよいビルド成果物ではありません。
6. npmパッケージの無条件なアップグレードを避ける
目標は、「すべてを最新版に」することではなく、確認と情報に基づいてアップグレードすることです。
リスク
次のようなサードパーティ依存関係の無条件なアップグレードは避けましょう。
npm update
npx npm-check-updates -uCIでもローカルの開発環境でも、これらのコマンドを実行すると次のリスクがあります。
侵害されたメンテナーアカウントから公開された悪意あるバージョンを取り込む
互換性を損なう不具合や、取り下げられたリリースを取り込む
依存関係の混同や名前空間の乗っ取り攻撃を招く
より安全な方法
1. 対話形式でアップグレードする:
npx npm-check-updates --interactive2. 依存関係を更新する前に、ひとつずつ確認する。
3. セキュリティを考慮するボットを使う:
Snykの自動アップグレードPR
GitHub DependabotのPR
4. こうした方法では、ロックファイルを黙って変更するのではなく、変更履歴やCVEなどの情報を含む、レビュー可能なPRが作成されます。
7. .envファイルに平文のシークレットを保存しない
目標は、開発環境からシークレットが簡単に窃取されないようにすることです。
リスク
平文の.envファイルや環境変数は、次のような状態です。
悪意あるパッケージや開発環境のマルウェアにとって、簡単な標的になる
ログ、クラッシュダンプ、ターミナル履歴などに残りやすい
サプライチェーン攻撃で
process.envやファイルの直接読み取りを通じて窃取される
避けるべき例:
DATABASE_PASSWORD=my-secret-password
API_KEY=sk-1234567890abcdef推奨パターン:シークレットへの参照と実行時の注入
ステップ1 – .envには値ではなく参照を記載する:
DATABASE_PASSWORD=op://vault/database/password
API_KEY=infisical://project/env/api-keyステップ2 – 実行時にシークレットマネージャーのCLIを使う。ここでは1Password CLIを例にします。
# Run app with secrets injected into process.env
op run -- npm start
# More explicit example with env-file
op run --env-file="./.env" -- node --env-file="./.env" server.jsほかの選択肢:Infisical CLI、クラウドのシークレットマネージャーなど。重要なのは、環境変数にシークレットそのものではなく参照情報を格納することです。
8. 開発コンテナで作業する
目標は、npmマルウェアにホストを乗っ取られないよう、開発環境をサンドボックス化することです。
リスク
ホスト上でnpm installを実行すると、次の危険があります。
悪意あるパッケージが、ほかのリポジトリのファイルを読み取れる
SSHキー、ブラウザープロファイル、AI/エージェントのトークンなどをスキャンできる
ほかのすべての作業と同じOS名前空間を共有する
開発コンテナのパターン
VS Code Dev Containersなどを使って環境を分離します。以下はDev Containerの設定ファイル.devcontainer/devcontainer.jsonの例です。
{
"name": "Node.js Dev Container",
"image": "mcr.microsoft.com/devcontainers/javascript-node:18",
"features": {
"ghcr.io/devcontainers/features/1password:1": {}
},
"postCreateCommand": "npm ci"
}続いて、次の操作を行います。
VS Codeでフォルダーを開く
「コンテナーで再度開く」を選択
すべてのインストールと実行はコンテナ内で行われます。
開発コンテナを強化する
Dockerのセキュリティオプションと安全なNodeフラグを追加する:
"runArgs": [
"--security-opt=no-new-privileges:true",
"--cap-drop=ALL",
"--cap-add=CHOWN",
"--cap-add=SETUID",
"--cap-add=SETGID"
],
"containerEnv": {
"NODE_OPTIONS": "--disable-proto=delete"
}さらに細かく制御するには、最小限のベースイメージ、root以外のユーザー、強化されたランタイムを使ったカスタムDockerfileを利用します。
9. npmアカウントで2FAを有効にする
目標は、アカウント乗っ取りによって侵害されたアカウントから悪意あるバージョンが公開されるのを防ぐことです。
リスク
eslint-scopeのようなインシデントから、攻撃者が認証情報を入手すると、何百万人ものユーザーにバックドアを仕込んだバージョンを公開できることがわかります。パスワードだけの認証では不十分です。
コマンド
ログイン、公開、プロフィール変更に2FAを適用:
npm profile enable-2fa auth-and-writesログインとプロフィール変更のみに2FAを適用(より緩やかな設定):
npm profile enable-2fa auth-only公開またはメンテナーの追加が可能なすべてのアカウントに、認証と書き込みの保護を適用します。
信頼されたOIDC公開を設定する
2FAやパスキーなど適切なパスワード管理をnpmアカウントに設定するだけでなく、新しい信頼されたOIDC公開方式を新しいnpmパッケージを公開する唯一の方法として使用し、GitHubリポジトリと特定のワークフローに直接ひも付ける必要があります。詳しくは、後述の専用セクションをご覧ください。
10. Provenanceアテステーションを使って公開する
目的は、利用者がパッケージのビルド元とビルド方法を検証できるようにすることです。
問題点
Provenanceがなければ、利用者は次の点を簡単に確認できません。
このtarballはGitHubのソースAからビルドされたのか、それとも不正なマシンからビルドされたのか?
侵害されたCIパイプラインによってコードが混入されたのか?
解決策: npm publish --provenance
GitHub Actionsの場合:
permissions:
id-token: write
steps:
- run: npm publish --provenance要件:
npm CLI 9.5.0以降
クラウドホスト型ランナーとOIDCに対応したGitHub Actions(またはGitLab CI/CD)
これにより、新たなサプライチェーン標準(OpenSSFなど)に準拠した、暗号学的に検証可能なビルドメタデータが生成されます。
11. OIDC(信頼された公開)を使って公開する
目的は、CI/CDから長期間有効なnpmトークンをなくすことです。
リスク
長期間有効なトークンには、次のリスクがあります。
誤ってログに記録されたり、コミットされたりする
漏えい後も有効なまま残る
組織やパッケージへの広範かつ長期的なアクセスを許してしまう
信頼された公開の仕組み
npmjs.comでパッケージの信頼された公開元としてGitHubまたはGitLabを設定します。
ワークフローでOIDCを使用します:
GitHub Actionsの例:
permissions:
id-token: write
steps:
- run: npm publishNPM_TOKENをどこにも保存する必要はありません。npmはCIからのOIDCトークンを検証し、承認済みのワークフローからのみ公開を許可します。Provenanceアテステーションも自動的に生成されます。
12. パッケージの依存関係ツリーを縮小する
目指すのは依存関係グラフを小さくし、リスクにつながる攻撃対象領域を縮小することです。
リスク
依存関係ごとに:
独自の推移的依存関係が持ち込まれる
メンテナーやアカウント、そしてそれらが侵害される可能性を引き継ぐ
脆弱性やライセンスの対象範囲が広がる
対策
依存関係がゼロ、または少ない設計を優先しましょう。単純な処理のためにユーティリティライブラリを追加するのではなく、最新のJavaScriptを使いましょう。
例:
// Instead of lodash uniq
const unique = [...new Set(array)];
// Instead of axios for simple HTTP GET
const response = await fetch(url);
// Instead of utility libs for trivial checks
const isEmpty = obj => Object.keys(obj).length === 0;依存関係を追加する前に、自分やチームに問いかけてみましょう。
これは複雑な機能か?
セキュリティとメンテナンスのコストに見合うか?
今は標準APIで実現できないか?
開発者セキュリティとサプライチェーンマルウェアの今後
このガイドでは、2019年に初めて公開したnpmセキュリティのベストプラクティスを紹介し、2025年に目の当たりにしたサプライチェーン攻撃から得た教訓と現代の手法を取り入れて、内容をさらに強化・拡充しています。
まず、開発者はパッケージマネージャーを信頼できない実行エンジンと捉え、安全な設定をデフォルトにする必要があります。可能な限りライフサイクルスクリプトを無効にし、本当に必要な場合にのみ明示的な許可リストを使うことが重要です。また、クールダウン期間やインストール前の監査などの仕組みを導入し、「新しいパッケージのインストール」を反射的な操作ではなく、意識的な判断にしましょう。こうした制御はnpmにとどまらず、pnpm、Bunなどの最新パッケージマネージャーにも適用し、ワークスペース内のすべてのツールでデフォルト設定を統一する必要があります。
次に、依存関係の解決は決定論的で、根拠を示せるものでなければなりません。Shai-Hulud攻撃では、気付かれないバージョン更新やロックファイルの変更ひとつで、悪意あるtarballが何千ものプロジェクトに持ち込まれる可能性が悪用されました。これを防ぐため、厳格なロックファイルの固定をワークフローの基本とし、CIでその動作を徹底するとともに、パッケージの出所を検証するツールでロックファイルを保護しましょう。決定論的なインストールは再現性のためだけではありません。インシデント発生時に「どのコードを実行し、そのコードはどこから来たのか」に答えられるようにするためです。
さらに、依存関係の脆弱性や健全性を示すシグナルは、時折確認するレポートではなく、継続的に取り込むデータとして扱う必要があります。こうしたインシデントは従来のCVE公開よりも速く進行するため、脆弱性データベース、マルウェアに関する勧告、Provenanceアテステーション、パッケージの公開からの経過時間や人気度のシグナル、クールダウン期間を組み込んだ自動アップグレードポリシーを組み合わせて防御する必要があります。インストール時のセキュリティゲート、非常に新しいリリースを避ける自動PR、そして軽微なバグ修正と、信頼できず前例のないパッケージバージョンの違いを理解するツールが不可欠です。
最後に、効果的なnpmセキュリティは依存関係のインストールだけにとどまりません。ローカル開発環境の構成、シークレットの管理、独自パッケージの公開とメンテナンスにも左右されます。強化された開発コンテナ内で作業すれば、悪意あるパッケージが検出された際の被害範囲を抑えられます。環境変数に平文のシークレットを使わないことをお勧めします。平文のenvファイルからジャストインタイムのシークレット注入に移行すれば、攻撃者がコードを実行できた場合でも、盗まれる可能性のある情報を減らせます。独自のnpmパッケージで2FA、OIDCによる信頼された公開、Provenanceアテステーションを有効にすれば、エコシステム内であなたのIDを乗っ取ろうとする攻撃者へのハードルを高められます。
Snykで安全を守る
SnykはShai-Huludの状況を綿密に監視し、プラットフォームを通じて定期的にアップデートを提供しています。昨日(11月24日)時点で、すでに800を超える悪意あるパッケージを追跡しています。
現在のインシデントへの対応力を高め、次回の発生に備えるためのリソースを以下にまとめました。
Shai-Huludによる侵害パッケージの一覧
Snykは、このマルウェアキャンペーンに関連するShai-Huludの悪意あるパッケージを掲載した公開ウェブページを管理しています。

Snykのゼロデイレポート
リポジトリをSnykに接続するか、ほかの方法で監視すると、依存関係のインベントリが作成されます。これにより、Shai-Huludマルウェアの影響を受けているかどうかを含め、研究開発組織全体で依存関係のさまざまな項目を簡単に監査・追跡できます。
この事象の影響を受けているか確認するには、[Reports]>[Featured Zero-Day Report]にアクセスしてください。この機能の詳細は、User Docsをご覧ください。
次のスクリーンショットは、SnykのUIでレポートを見つける方法を示しています(2025年9月に発生したShai-Huludのnpmサプライチェーン攻撃を表示)。新しいレポートの名称はSHA1-Hulud npm Supply Chain Attack - Nov 2025です。

Snykで依存関係を監視する
オープンソースソフトウェアライブラリを利用するには、組織全体の依存関係ツリーに含まれる直接・間接の依存関係について、CVE脆弱性かマルウェアキャンペーンかを問わず、セキュリティアップデートを継続的に監視し、常に最新の状態に保つ必要があります。
Snykでは、Gitリポジトリを接続し、SCAツールであるSnyk Open Sourceを使って、JavaScriptプロジェクトのオープンソースリスクをプロアクティブに管理できます。また、Snykが標準で提供するSBOMの維持も行うべきです。ぜひ実施してください。
また、明日のゼロデイ脆弱性に今日から備えることをお勧めします。