メインコンテンツまでスキップ

パッケージのセキュリティ

パッケージは開発を効率化してくれる便利な仕組みですが、セキュリティの観点で注意すべきポイントがあります。このページでは、パッケージに関連するセキュリティリスクと、開発者が実践できる具体的な対策について説明します。

なぜパッケージのセキュリティが重要か

現代のアプリケーションは、数百から数千のパッケージに依存しています。自分が直接インストールしたパッケージだけでなく、その推移的依存関係も含めると、その数は膨大になります。

shell
# プロジェクトの依存パッケージ数を確認するコマンド
npm ls --all | wc -l
shell
# プロジェクトの依存パッケージ数を確認するコマンド
npm ls --all | wc -l

この膨大な依存関係の中に、ひとつでも悪意あるパッケージが混入すると、アプリケーション全体に影響を及ぼす可能性があります。だからこそ、パッケージのセキュリティを意識することが大切です。

サプライチェーン攻撃とは

パッケージに関するセキュリティ上の脅威として代表的なものが、サプライチェーン攻撃です。

サプライチェーンとは「供給網」のことです。製造業では原材料の調達から製品の出荷まで一連の流れをサプライチェーンと呼びますが、ソフトウェアにも同様の概念があります。開発者がパッケージをインストールし、それを使ってアプリケーションを構築し、ユーザーに届けるまでの流れが、ソフトウェアのサプライチェーンです。

サプライチェーン攻撃とは、このソフトウェアの供給網を狙った攻撃のことです。攻撃者はパッケージを経由して悪意あるコードを混入させ、それを使う開発者やユーザーに被害を与えます。

主な攻撃手法

サプライチェーン攻撃には、いくつかの代表的な手法があります。

タイポスクワッティング

タイポスクワッティングは、正規のパッケージ名に似たスペル違いの名前で、悪意あるパッケージを登録する手法です。開発者がパッケージ名を打ち間違えたときに、誤って悪意あるパッケージをインストールさせることを狙います。

たとえば、人気パッケージexpressに対してexprssexpresのような名前で不正なパッケージを登録するケースがあります。

依存関係混乱攻撃

依存関係混乱攻撃(Dependency Confusion)は、組織が内部で使っているプライベートパッケージと同じ名前のパッケージを、npmのような公開レジストリに登録する手法です。パッケージマネージャーの設定によっては、内部パッケージよりも公開レジストリのパッケージが優先的にインストールされてしまうことがあります。

メンテナアカウントの侵害

パッケージのメンテナ(管理者)のアカウントが、フィッシング(偽のログインページなどで認証情報を騙し取る手法)などによって侵害されるケースです。攻撃者はメンテナになりすまし、正規のパッケージにバックドア(外部から不正にアクセスするための隠し入口)を仕込んだバージョンを公開します。

この手法は、すでに多くの開発者に信頼されている有名パッケージが狙われるため、影響範囲が非常に大きくなります。

コラム: 実際に起きたサプライチェーン攻撃

2025年には大規模な攻撃が複数発生しました。

  • 2025年9月、週間ダウンロード数26億以上を誇るchalkやdebugなどの有名パッケージが、メンテナアカウントのフィッシングにより一時的に侵害されました。約2時間で検知・除去されましたが、その間にインストールしたユーザーは影響を受けた可能性があります。
  • 同じく2025年には、Shai-Huludと呼ばれる自己複製型のワーム攻撃が発生し、約800のnpmパッケージが侵害されたほか、盗んだ認証情報による悪意あるリポジトリが25,000以上作成されました。

これらの事例は、日常的に使っている有名パッケージでもセキュリティリスクがゼロではないことを示しています。

コラム: AIとスロップスクワッティング

近年、AIコーディングアシスタントが実在しないパッケージ名を推奨してしまう「幻覚」が問題になっています。攻撃者はAIが推奨しそうな架空のパッケージ名を先取りして悪意あるパッケージを公開する手法があり、これはスロップスクワッティングと呼ばれています。AIの提案するパッケージも、npmjs.comで実在確認することが大切です。

多層防御の考え方

サプライチェーン攻撃の手法はさまざまで、ひとつの対策だけで完全に防ぐことはできません。そこで重要になるのが「多層防御」という考え方です。

多層防御とは、複数の対策を組み合わせてセキュリティを高めるアプローチです。たとえば、建物のセキュリティでは、玄関の鍵だけでなく、防犯カメラ、オートロック、警備員など複数の手段を組み合わせます。パッケージのセキュリティでも同様に、導入前の確認、ロックファイルの活用、インストールスクリプトの制御、脆弱性チェックなど、複数の層で守ることが大切です。

これから紹介する5つの対策は、それぞれ異なるリスクに対応しています。すべてを完璧に実践する必要はありませんが、できるところから取り入れていきましょう。

対策1: 導入前に信頼性を確認する

パッケージをインストールする前に、そのパッケージが信頼できるものかを確認することが、最初の防御層です。

パッケージの選定基準

新しいパッケージをインストールする前に、次のポイントを確認しましょう。

  • ダウンロード数: 週間ダウンロード数が多いパッケージは、多くの開発者に使われている実績があります。
  • メンテナンス状況: 最終更新日が極端に古いパッケージは、脆弱性が放置されている可能性があります。
  • GitHubのスター数やIssueの対応状況: コミュニティが活発に活動しているかが判断材料になります。
  • 依存パッケージの数: 依存が少ないほど、サプライチェーンリスクが低くなります。

パッケージの選び方について詳しくは、次のページを参照してください。

パッケージ名のスペルを確認する

タイポスクワッティングを防ぐために、npm installするときはパッケージ名のスペルを慎重に確認してください。とくにコマンドラインで手入力するときは打ち間違いに注意しましょう。npmjs.comで検索してから正確な名前をコピーするのが確実です。

npxの確認プロンプトに注意する

npxは、パッケージをインストールせずに一時的にコマンドを実行するツールです。npm v7以降のnpxでは、ローカルにインストールされていないパッケージを実行しようとすると確認プロンプトが表示されます。

shell
$ npx cowsay hello
Need to install the following packages:
cowsay
Ok to proceed? (y)
shell
$ npx cowsay hello
Need to install the following packages:
cowsay
Ok to proceed? (y)

この確認プロンプトは、意図しないパッケージの実行を防ぐための安全機能です。表示されたパッケージ名が本当に自分が実行したいものか確認してからyを入力してください。タイポスクワッティングにより、意図しないパッケージが実行されるのを防ぐ最後の砦になります。

なお、pnpmのpnpm dlx、Yarnのyarn dlx、Bunのbunxには確認プロンプトがありません。これらを使う場合は、実行前にパッケージ名をより慎重に確認してください。

対策2: ロックファイルで再現性を確保する

ロックファイルは、プロジェクトの依存パッケージのバージョンを正確に記録したファイルです。ロックファイルがあることで、チームメンバー全員が同じバージョンのパッケージを使えるだけでなく、意図しないバージョンのパッケージがインストールされることを防げます。

ロックファイルをバージョン管理に含める

ロックファイルは必ずgitにコミットしてください。これにより、チーム全員が同じ依存関係でビルドできます。

CIではロックファイルを厳密に固定する

ローカルの開発環境ではnpm installでロックファイルが自動更新されますが、CI(継続的インテグレーション)環境では、ロックファイルに記録されたバージョンを厳密に再現するコマンドを使いましょう。

パッケージマネージャーコマンド
npmnpm ci
pnpmpnpm install --frozen-lockfile
Bunbun install --frozen-lockfile
Yarnyarn install --immutable

これらのコマンドは、ロックファイルとpackage.jsonの間に不整合があるとエラーになります。CI環境でこれを使うことで、開発者がロックファイルの更新を忘れたままデプロイされるのを防げます。

ロックファイルについて詳しくは、次のページを参照してください。

対策3: インストールスクリプトを制御する

パッケージの中には、インストール時に自動実行されるスクリプト(postinstallスクリプト)を含むものがあります。これはネイティブモジュールのビルドなど正当な用途に使われる機能ですが、悪意あるパッケージがこの仕組みを悪用して、インストールしただけで有害なコードを実行するケースがあります。

パッケージマネージャーごとの対応状況

パッケージマネージャーによって、インストールスクリプトの扱いが異なります。

パッケージマネージャーデフォルトの動作スクリプトを無効にする方法特定パッケージだけ許可する方法
npmスクリプトを実行する--ignore-scriptsフラグなし
pnpm (v10以降)スクリプトをブロックする(デフォルトでブロック)pnpm-workspace.yamlonlyBuiltDependencies
Bunスクリプトをブロックする(デフォルトでブロック)package.jsontrustedDependencies
Yarn Berryスクリプトを実行するenableScripts: false設定dependenciesMeta設定

pnpm v10とBunは、デフォルトでインストールスクリプトをブロックします。これはセキュリティの観点でもっとも安全なデフォルト設定です。スクリプトの実行が必要なパッケージ(たとえばesbuildのようなネイティブバイナリを含むパッケージ)だけを明示的に許可する運用ができます。

npmでの対策

npmはデフォルトでスクリプトを実行するため、意識的な対策が必要です。.npmrcファイルに次の設定を追加すると、スクリプトの実行を無効にできます。

ini
ignore-scripts=true
ini
ignore-scripts=true

ただし、この設定を有効にすると、ネイティブモジュールのビルドが必要なパッケージ(esbuildsharpなど)が正しく動作しなくなる場合があります。そのため、必要に応じて個別に--ignore-scripts=falseを指定してインストールするか、pnpmBunのように許可リスト方式を採用しているパッケージマネージャーへの移行を検討してください。

対策4: 脆弱性を定期的にチェックする

依存パッケージに既知の脆弱性が見つかることがあります。パッケージマネージャーには、これをチェックするauditコマンドが用意されています。

パッケージマネージャーコマンド
npmnpm audit
pnpmpnpm audit
Bunbun audit
Yarnyarn npm audit
shell
npm audit
shell
npm audit

このコマンドを実行すると、脆弱性が見つかったパッケージの一覧と、その深刻度(low, moderate, high, critical)が表示されます。定期的に実行して、脆弱性がないかを確認しましょう。

脆弱性が見つかった場合は、npm audit fix(npmの場合)コマンドで自動修正を試みることもできます。

shell
npm audit fix
shell
npm audit fix

CIパイプラインにauditコマンドを組み込んでおくと、脆弱性の検出を自動化できます。

対策5: 不要なパッケージを整理する

使わなくなったパッケージがプロジェクトに残っていると、それだけ攻撃対象が増えることになります。依存パッケージが少ないほど、リスクにさらされる範囲は小さくなります。定期的にpackage.jsonを見直し、不要なパッケージは削除しましょう。

shell
npm uninstall 不要なパッケージ名
shell
npm uninstall 不要なパッケージ名
コラム: さらに進んだ対策 ─ minimumReleaseAge

一部のパッケージマネージャーには、公開されてから一定期間が経過していないバージョンのインストールを制限するminimumReleaseAgeという機能があります。新しく公開された直後のバージョンには、侵害されたコードが含まれている可能性があるため、一定期間の「待ち」を設けることで、問題のあるバージョンがコミュニティに発見・報告される時間を確保できます。

  • pnpm: v10.16以降で対応。pnpm-workspace.yamlminimumReleaseAgeで分単位の設定が可能です。デフォルトは無効です。
  • Bun: v1.3以降で対応。bunfig.tomlminimumReleaseAgeで秒単位の設定が可能です。デフォルトは無効です。
  • Yarn Berry: v4.10以降で対応。.yarnrc.ymlnpmMinimalAgeGateで期間を文字列で指定します。デフォルトは3d(3日間)で、唯一デフォルトで有効です。
  • npm: 未対応です。

関心のある方は、各パッケージマネージャーのドキュメントを確認してみてください。

学びをシェアする

・サプライチェーン攻撃に備えるには「多層防御」が重要
・信頼性確認→ロックファイル→スクリプト制御→脆弱性チェック→整理の5層で守る
・pnpm v10/Bunはpostinstallをデフォルトブロックし安全性が高い
・CIではfrozen lockfileで依存を厳密に固定しよう

『サバイバルTypeScript』より

この内容をXにポストする