パッケージマネージャーのサプライチェーン攻撃対策 — uv, pnpm, bun, cargo, go の比較

この記事はAIによって生成されています。内容の正確性にご注意ください。

2026年3月のLiteLLM攻撃を題材に、主要パッケージマネージャー(uv・pnpm・bun・cargo・go)のサプライチェーン防御機能を比較する。各ツールの設定方法やトレードオフに焦点を当て、攻撃手法の詳細な解析やインシデントレスポンスの手順は扱わない。

TL;DR

  • 2026年3月のLiteLLMサプライチェーン攻撃では、侵害されたTrivy経由でPyPI公開トークンが窃取され、悪意あるバージョンが約3時間公開された。リリース直後のバージョンを自動取得するデフォルト動作が攻撃を容易にしている
  • 最小リリース経過時間(minimum release age) は、公開直後のバージョンをインストールしない防御機能。uv (--exclude-newer)、pnpm (minimumReleaseAge)、bun (minimumReleaseAge) が対応済み。Cargoはnightlyで --publish-time が実験中、GoはMVS(最小バージョン選択)で構造的に緩和
  • ビルドスクリプトの制限、lockfileの厳密な固定、provenance attestation、依存関係の監査ツールなど、パッケージマネージャーごとに多層的な防御策が提供されている

機能比較サマリー

機能uvpnpmbunCargoGo
最小リリース経過時間--exclude-newerminimumReleaseAgeminimumReleaseAge--publish-time (nightly)— (MVS で緩和)
パッケージ単位の除外exclude-newer-packageminimumReleaseAgeExcludeminimumReleaseAgeExcludes
lockfile 固定モード--locked / --frozen--frozen-lockfile--frozen-lockfile / bun ci--frozen / --lockedgo.sum + proxy
ハッシュ検証○ (sum.golang.org)
ビルドスクリプト制限— (wheel ベース)allowBuilds (v10 デフォルト)trustedDependencies
provenance 検証trustPolicy (唯一)
脆弱性スキャンuv-secure / pip-auditpnpm auditbun auditcargo-auditgovulncheck
コード監査cargo-vet / cargo-crev

背景: LiteLLM サプライチェーン攻撃(2026年3月)

事件の概要

2026年3月24日、Pythonパッケージ litellm(月間約9,500万ダウンロード)の悪意あるバージョン 1.82.71.82.8 がPyPIに公開された。攻撃者は TeamPCP(PCPcat, ShellForceとも)と呼ばれるグループで、多段階のサプライチェーン攻撃を仕掛けた。

攻撃チェーン

Trivy (セキュリティスキャナ) 侵害
  ↓ GitHub Action の Git タグを書き換え
LiteLLM の CI/CD パイプラインが侵害された Trivy を実行
  ↓ PyPI 公開トークンを窃取
悪意ある litellm v1.82.7/v1.82.8 を PyPI に公開
  ↓ 約3時間の公開
PyPI がパッケージを検疫・削除

この攻撃の特徴は、GitHubリポジトリのソースコードは改変されていない点にある。悪意あるコードはホイールビルド時に注入され、PyPIに直接公開された。つまりソースコードのレビューだけでは防げない。

悪意あるコードの動作

マルウェアは3段階のペイロードで構成されていた。

段階動作
Stage 1Base64デコード・AES-256-CBC暗号化で収集データを models.litellm[.]cloud に送信
Stage 2環境変数、SSH鍵、AWS/GCP/Azure認証情報、Kubernetesシークレット、暗号通貨ウォレット、Slack/Discordトークン等を網羅的に収集
Stage 3systemdサービスとして永続化し、50分ごとにC2サーバーからペイロードを取得。Kubernetes環境では全ノードに特権Podを作成しホストファイルシステムにバックドアを設置

注目すべきは2つのバージョンで異なる注入手法が使われた点。v1.82.7はlitellm/proxy/proxy_server.pyにBase64ペイロードを埋め込み、proxyモジュールのimport時に実行された。v1.82.8ではより危険な手法に切り替わり、litellm_init.pthsite-packages/に配置した。.pthファイルはPythonインタプリタ起動時にimportなしで自動実行されるため、litellmを明示的にインポートしなくても、同じ環境内で任意のPythonスクリプトを実行するだけでペイロードが発火する。

発見の経緯

  • Sonatypeの自動マルウェア検出ツールが公開後「数秒以内」にパッケージをブロックしたが、PyPIの検疫完了まで約3時間を要した
  • FutureSearchのリサーチャーは、AIコードエディタCursor内で動作するMCPプラグインが transitive dependency(推移的な依存関係) としてlitellmを取得した際に侵害を発見した。被害者は直接pip install litellmを実行していない
  • コミュニティがGitHub issue #24512で警鐘を鳴らすと、攻撃者は侵害したメンテナアカウントを使い102秒間に73アカウントから88件のボットコメントを投稿して問題を隠蔽しようとし、issueを「not planned」としてクローズした

影響と教訓

  • 悪意あるバージョンの公開から検疫まで約3時間。この間にインストールしたユーザーが影響を受けた
  • v1.82.6が最後の安全なバージョン
  • TeamPCPによるTrivy侵害を起点とした一連の攻撃では、Checkmarx KICS、45以上のnpmパッケージも侵害され、5日間でGitHub Actions、Docker Hub、npm、Open VSX、PyPIにまたがる連鎖的な攻撃が展開された
  • LiteLLMの公式Dockerイメージはrequirements.txtで依存関係を固定していたため影響を受けなかった — lockfileの固定が有効に機能した実例

この事件は、公開直後のパッケージバージョンを無条件にインストールするパッケージマネージャーの動作がサプライチェーン攻撃を容易にしていることを明確に示した。

最小リリース経過時間(Minimum Release Age)

最小リリース経過時間は、公開から一定時間が経過していないバージョンをインストール対象から除外する機能です。多くの悪意あるパッケージは公開後24時間以内に検出・削除されるため、この「クールダウン期間」を設けることで攻撃のウィンドウを狭められます。

uv: --exclude-newer

uvは最も早くこの機能を実装したパッケージマネージャーの1つで、--exclude-newerオプションで指定した日時以降に公開されたバージョンを除外できる。

pyproject.toml

[tool.uv]
exclude-newer = "2026-03-20T00:00:00Z"

絶対的なタイムスタンプだけでなく、期間ベースの指定も可能。フレンドリーな形式("24 hours" / "1 week" / "30 days")やISO 8601形式("PT24H" / "P7D")が使える。環境変数UV_EXCLUDE_NEWERでも設定できる。

# 3日以内に公開されたバージョンを除外
uv lock --exclude-newer "3 days"

# 特定パッケージのみ除外設定を上書き
uv lock --exclude-newer "3 days" --exclude-newer-package "fastapi="

pyproject.toml[tool.uv]セクションで恒久的に設定でき、パッケージ単位の除外(exclude-newer-package)もサポートしている。

なお、--exclude-newerはレジストリがPEP 700のupload-timeフィールドをサポートしている必要がある。PyPIは全パッケージでサポート済み。upload-timeがないディストリビューションは利用不可として扱われる。

uvのlockfileは全パッケージの暗号学的ハッシュを記録しており、--lockedフラグでuv.lockpyproject.tomlの整合性チェック、--frozenフラグでlockfileの変更を禁止できる。さらに、lockfileをCycloneDX SBOMとしてエクスポートする機能や、PEP 751標準lockfile形式へのエクスポートもサポートしている。

pnpm: minimumReleaseAge

pnpmはv10.16(2025年9月)でこの機能を導入した。pnpm-workspace.yamlで分単位の設定が可能。

pnpm-workspace.yaml

minimumReleaseAge: 1440          # 1日 = 1440分
minimumReleaseAgeExclude:
  - webpack
  - react

この設定により、公開から1440分(24時間)未満のバージョンは解決対象から除外される。レジストリからtimeフィールドを含むフルメタデータを取得する必要があるため、通常のインストールよりやや遅くなる場合がある。レジストリが短縮メタデータでtimeフィールドをサポートしている場合は、registrySupportsTimeField: trueを設定することで高速化できる。

制限事項:

  • 既にlockfileに記録されている依存関係には適用されない(lockfileが優先)
  • 完全一致で指定されたバージョンには適用されない

bun: minimumReleaseAge

bunはv1.3(2025年10月)で同等の機能を追加した。bunfig.toml秒単位の設定が可能。

bunfig.toml

[install]
minimumReleaseAge = 259200       # 3日 = 259200秒
minimumReleaseAgeExcludes = ["@types/node", "typescript"]

CLIオプションとしても利用できる。

bun add @types/bun --minimum-release-age 259200

bunは独自の「安定性検出」ヒューリスティックを持っている。age gateのすぐ外側で複数バージョンが短期間に公開された場合、それらも不安定とみなしてスキップし、より古い安定バージョンを選択する。

Cargo: --publish-time(nightly / 実験的)

Cargoには安定版での最小リリース経過時間機能は存在しない。nightlyツールチェーンで--publish-timeフラグが実験的に利用可能。

cargo +nightly generate-lockfile -Zunstable-options --publish-time "2026-03-20T00:00:00Z"

ただし、crates.ioのレジストリインデックスにはまだpubtimeフィールドが含まれていないため、実用的ではない。安定化の時期は未定。

Go: MVS(最小バージョン選択)による構造的緩和

Goには最小リリース経過時間に相当する機能はないが、Go Modulesの Minimum Version Selection(MVS) が構造的にこの問題を緩和している。

MVSは他のパッケージマネージャーとは逆のアプローチを取る。要件を満たす最も古いバージョンを選択する。

他のパッケージマネージャー: require >= 1.5.0 → 最新の 1.9.2 をインストール
Go MVS:                     require >= 1.5.0 → 1.5.0 をインストール

この設計により、攻撃者が新しい悪意あるバージョンを公開しても、既存のプロジェクトは自動的にそのバージョンを取得しない。バージョンが上がるのは、開発者が明示的にgo getで更新するか、依存関係のいずれかが新バージョンを要求した場合のみ。

依存ツリーが深いほど、新しいバージョンが伝播するには多くの明示的なアップグレードが必要になる。これがサプライチェーン攻撃に対する構造的なダンパーとして機能する。また、Go文化として「a little copying is better than a little dependency」(少しのコピーは少しの依存より良い)という哲学があり、豊富な標準ライブラリとgolang.org/x/エコシステムにより、依存ツリーが小さくなる傾向がある。

加えて、Goのモジュールエコシステムには以下の追加的な保護がある。

  • proxy.golang.org はモジュールのキャッシングプロキシで、一度キャッシュされたバージョンは改変できない。レジストリアカウントが不要で、importパスがVCS情報を直接埋め込む
  • sum.golang.org はグローバルなチェックサムデータベース(Merkle tree、Trillianベース)。go.sumのハッシュがこの透明性ログと照合され、パッケージの改ざんを検出できる。侵害されたプロキシやモジュール作者が異なるユーザーに異なるコードを配信することは検出される
  • Goツールチェーンの明示的な設計目標として、フェッチ・ビルド時に任意コードが実行されないことが保証されている

その他のパッケージマネージャーセキュリティ機能

ビルドスクリプトの制限

npmエコシステムではpostinstall等のライフサイクルスクリプトがサプライチェーン攻撃の主要な攻撃ベクタとなっている。

pnpm v10 はデフォルトでビルドスクリプトをブロックし、allowBuildsで明示的にホワイトリスト登録されたパッケージのみ実行を許可する。これは2024年のRspackサプライチェーン攻撃を受けて導入された。

pnpm-workspace.yaml

allowBuilds:
  - esbuild
  - sharp

bun も同様に、デフォルトでライフサイクルスクリプトをブロックし、trustedDependenciesで明示的に許可する。

uv はwheelベースのインストールを使用するため、setup.pyの任意コード実行のリスクが構造的に低い。

Cargo にはnpmのようなpostinstallフックは存在しないが、ビルドスクリプト(build.rs)は存在する。ただしこれはビルド時に実行されるため、インストール時の自動実行とは性質が異なる。

Provenance と署名

npmは provenance attestation をサポートしており、パッケージがどのリポジトリ・ワークフローからビルドされたかをSigstoreで暗号署名し、透明性ログ(Rekor)に記録する。SLSA Build Level 2 を達成している。

pnpmは trustPolicy: no-downgrade(v10.21)を提供しており、以前のバージョンで署名されていたパッケージが署名なしで公開された場合にインストールを拒否する。

pnpm-workspace.yaml

trustPolicy: no-downgrade
trustPolicyExclude:
  - legacy-package

PyPIは Trusted Publishers(OIDC)と PEP 740 attestation を導入しており、Python 3.14からはSigstoreがCPythonリリースの唯一の署名方法となる。

依存関係の監査ツール

ツールエコシステム特徴
pnpm auditnpmnpmアドバイザリデータベースに基づく脆弱性スキャン
bun auditnpm同上
cargo-auditRustRustSecアドバイザリDB。yankedクレートの検出も可能
cargo-vetRustMozillaが開発。依存関係のコード監査を体系的に管理
cargo-crevRust分散型のコードレビューシステム
govulncheckGoGo脆弱性データベース(vuln.go.dev)に基づくスキャン
uv-securePythonuv.lockをPyPI脆弱性データに照合
pip-auditPythonPythonパッケージの脆弱性スキャン

特筆すべきはcargo-vetで、全サードパーティ依存関係が信頼できるエンティティによってレビュー済みであることを保証する。cargo vet initで既存の依存関係を自動的に免除リストに追加できるため、導入時の負荷が低い。差分監査(バージョン間の差分のみレビュー)をサポートし、Mozilla、Google、Meta等の企業が監査結果を公開共有している。信頼は分散型で、中央データベースではなく信頼する組織のリポジトリから監査データを直接取得する。

cargo-crevはcargo-vetとは異なるアプローチを取り、暗号学的に検証可能なWeb of Trustモデルを採用している。レビュアーが署名付きのコードレビュー「proof」を公開gitリポジトリに公開し、信頼が推移的に伝播する。既存のレビューはweb.crev.devで閲覧できる。

pnpm の追加セキュリティ機能

pnpmはJavaScriptエコシステムで最も包括的なサプライチェーン防御を提供している。

blockExoticSubdeps: true を設定すると、transitive dependencyがgitリポジトリやtarball URLから解決されることを防ぎ、レジストリソースのみを許可できます。

trustPolicy: no-downgrade は、パッケージの信頼レベル(provenance有無)が前バージョンより低下した場合にインストールを拒否します。この機能は2026年3月時点でpnpm固有のもので、npm・bun・uv・cargo・goのいずれもサポートしていません。npmはnpm audit signaturesでprovenanceの手動検証が可能ですが、バージョン間の信頼レベルの推移を追跡するポリシー機構はありません。PyPIはPEP 740でSigstore attestationの公開側インフラを整備中ですが、インストール時の検証はまだ実装されていません。業界全体としてprovenanceの「提供」は進んでいるものの、provenanceの「継続性の強制」まで踏み込んでいるのはpnpmのみです。

trustPolicyIgnoreAfter(v10.27)を使うと、指定日以前のパッケージに対してtrust policyチェックをスキップできます。

再現可能ビルド

ICSE 2025の研究によると、パッケージの再現可能性はエコシステムによって大きく異なる。

エコシステム再現可能率主な要因
Cargo100%アーカイブメタデータに固定値を使用
npm100%同上
PyPI12.2%flit/hatchバックエンドのみメタデータを固定
Maven2.1%アーカイブ内のタイムスタンプ
RubyGems0%同上

GoogleのOSS Rebuild(2025年7月開始)プロジェクトは、PyPI・npm・crates.ioのパッケージをソースから再ビルドし、セマンティック比較を実施した上でSLSA L3のprovenance attestationを発行している。

レジストリ側の保護: Trusted Publishers

パッケージマネージャーのクライアント側だけでなく、レジストリ側でもセキュリティ強化が進んでいる。Trusted PublishersはOIDCを使い、CI/CDプラットフォーム(GitHub Actions、GitLab CI/CD等)から直接パッケージを公開する仕組みで、長期間有効なAPIトークンを不要にする。

レジストリTrusted Publishersその他の対策
PyPI50,000プロジェクトが有効化、全アップロードの25%が利用タイポスクワッティング自動検出、Sigstore attestation
npm2025年7月開始。ローカル公開に2FA必須化、トークン有効期限を7日に短縮Shai-Hulud npmワーム事件後にFIDO/WebAuthn推奨
crates.io2025年7月開始(GitHub Actions対応)typomaniaによるタイポ検出

LiteLLM事件では、CI/CDパイプラインから窃取されたPyPIトークンが悪用された。Trusted Publishersを使っていれば、トークンは短命のOIDCトークンに置き換わり、特定のリポジトリ・ワークフローからの公開のみが許可されるため、窃取されたトークンによる別環境からの公開を防止できた可能性がある。

新たな脅威: slopsquatting

従来のタイポスクワッティング(reqeustsrequests等)に加え、slopsquattingという新しい攻撃ベクタが出現しています。LLMがコード生成時に存在しないパッケージ名を「幻覚」する傾向を悪用し、攻撃者がそのパッケージ名を先に登録する手法です。AIコードエディタの普及に伴い、この脅威は拡大しています。

推奨対策

LiteLLM事件のような攻撃を防ぐために、以下の多層防御を推奨する。

1. 最小リリース経過時間を設定する

"uv: pyproject.toml"

[tool.uv]
exclude-newer = "3 days"

"pnpm: pnpm-workspace.yaml"

minimumReleaseAge: 4320  # 3日

"bun: bunfig.toml"

[install]
minimumReleaseAge = 259200  # 3日

2. CI では lockfile を厳密に固定する

# uv
uv sync --locked

# pnpm
pnpm install --frozen-lockfile

# bun
bun ci

# Cargo
cargo build --locked

# Go (go.sum が自動的に検証)
go build ./...

3. ビルドスクリプトを制限する

"pnpm: pnpm-workspace.yaml"

allowBuilds:
  - esbuild
  - sharp
  - @biomejs/biome

4. 定期的に脆弱性スキャンを実行する

# 各エコシステム
pnpm audit
cargo audit
govulncheck ./...
pip-audit

5. Provenance を確認する

npmパッケージを公開する場合は--provenanceフラグを使用し、消費する側はprovenanceの有無を確認する。pnpmのtrustPolicy: no-downgradeは自動化された確認手段として有効。

まとめ

LiteLLMへのサプライチェーン攻撃は、パッケージマネージャーが「最新バージョンを即座にインストールする」というデフォルト動作のリスクを明確にした。この攻撃では約3時間の公開で済んだが、その間にインストールした全ユーザーが影響を受けた。

最小リリース経過時間は万能ではないが、攻撃者が成功する時間的ウィンドウを狭める。LiteLLMの場合は約3時間で検疫されたが、24時間のクールダウンがあれば影響を受けるユーザーはゼロだった。lockfileの厳密な固定、ビルドスクリプトの制限、provenanceの活用と組み合わせることで、多層的な防御を構築できる。

特にGoのMVSやCargoのcargo-vetのように、言語やエコシステムの設計レベルでサプライチェーンセキュリティを考慮するアプローチは、今後他のパッケージマネージャーにも影響を与えていくだろう。

参考リンク

この記事に関するIssueをGithubで作成する

Read Next