detecting-dependency-confusion について
サプライチェーンセキュリティやビルドエンジニアリングのチーム向けに、依存関係の混同リスクを検出・防止するスキルです。マニフェストのスキャン、公開レジストリの確認、プライベートソースの固定、CIでの継続検査を扱います。
主な機能
- 01複数エコシステムのマニフェストをスキャン
- 02公開レジストリで取得可能な名前を検出
- 03依存関係リスクを照合
- 04プライベートレジストリのソースを固定
- 05未登録名のプレースホルダー登録
- 06CIで継続的に検出
活用シーン
- 01リポジトリの依存関係混同リスクを基準化
- 02公開・非公開レジストリを使うパイプラインの監査
- 03内部パッケージ名の漏えい後のリスク評価
- 04CIで新たな混同可能パッケージを検出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill detecting-dependency-confusionインストール後、Claude Code で/detecting-dependency-confusionを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/api-reference.mdstandards.mdscripts/agent.py詳細
依存関係の混同の検出
法的通知: このスキルは、許可を得たセキュリティテスト、防御的エンジニアリング、教育目的に限って使用してください。所有していないパッケージの名前空間を登録または取得したり、書面による許可なくビルドパイプラインをテストしたりすると、違法となる可能性があり、公開レジストリの利用規約に違反する場合があります。名前空間の取得や解決のテストは、自分が管理する名前とインフラストラクチャ、または評価を明示的に許可された対象に対してのみ実施してください。
概要
依存関係の混同(置換攻撃または名前空間シャドーイング攻撃とも呼ばれます)は、2021 年に Alex Birsan が Apple、Microsoft、PayPal など数十社の内部ビルドシステムに悪意のあるコードを侵入させたことで広く知られるようになりました。根本的な原因は、多くのパッケージマネージャーが内部/プライベートレジストリと公開レジストリの両方から解決するよう設定されている場合、取得元を優先せず、バージョン番号がより大きい方を選ぶことです。攻撃者がプライベートパッケージの名前(@acme/internal-utils、acme-billing-sdk)を知ると、同じ名前の悪意のあるパッケージを、高いバージョン番号(例:99.0.0)で公開レジストリ(npmjs.com、PyPI、Maven Central)に公開できます。被害者の CI/CD ランナーまたは開発者のマシンが依存関係を解決すると、攻撃者の公開パッケージを取得してインストールスクリプトを実行し、サプライチェーンが侵害されます。
このスキルでは、問題の両面を扱います。confused と OWASP dep-scan を使用して、公開レジストリに登録されていない(防御目的でスクワッティングされていない)ため取得可能な内部パッケージ名を列挙する検出と、.npmrc、pip.conf/pyproject.toml、Maven の settings.xml でスコープ/名前空間をプライベートレジストリに固定し、プレースホルダーパッケージを登録して、取得元の制限を適用する防止です。内部パッケージ名は、コミット済みのロックファイル、ソースマップ、公開 JS バンドル、Docker レイヤー、エラースタックトレースなどから常に漏えいするため、これは一度限りの確認ではなく、攻撃対象領域管理の問題として扱います。
使用する場面
- リポジトリまたは組織をサプライチェーンセキュリティプログラムに新たに参加させる際に、公開レジストリで取得可能な内部パッケージを把握する必要がある場合。
- CI/CD パイプラインがプライベートレジストリと公開レジストリの両方から依存関係を解決している場合(混在/ハイブリッドフィード)。
- 内部パッケージ名が漏えいした可能性のあるインシデント(ソースの漏えい、公開バンドル、侵害されたリポジトリなど)の発生後。
package.json、requirements.txt、pom.xml、composer.json、Gemfile.lockファイルを監査し、混同されやすい依存関係を調べる場合。- 防御的に未登録のままになっている新たな内部パッケージを検出するため、定期的に実施する制御として。
前提条件
confusedのインストールには Go 1.20 以降が必要です。go install github.com/visma-prodsec/confused@latest # binary lands in $(go env GOPATH)/bin/confused- OWASP dep-scan には Python 3.10 以降が必要です。
pip install owasp-depscan # or container: docker pull ghcr.io/owasp-dep-scan/dep-scan - Node.js + npm(
.npmrcとnpm configによる修正用)、およびプライベートレジストリ(Artifactory、Nexus、Azure Artifacts、GitHub Packages、AWS CodeArtifact)へのアクセス。 - 評価対象のリポジトリ/ロックファイルへの読み取りアクセス権と、防御的な登録を行うためのプライベートレジストリへの書き込みアクセス権。
目的
- npm、PyPI、Maven、Composer、RubyGems にわたるプロジェクトマニフェストで宣言されたすべての内部依存関係を列挙します。
- 対応する公開レジストリに存在せず、そのため取得可能な内部名を特定します。
- 実際の露出と誤検知(スコープ付きパッケージ、すでにミラーリングされた名前)を区別します。
- 公開パッケージによる置換を不可能にする、レジストリ固定とスコープ制限の制御を適用します。
- 未取得の内部名に対し、防御目的でプレースホルダーパッケージを登録します。
- 新たに追加された混同されやすい依存関係を検出するため、CI で定期的に実施する検出制御を確立します。
MITRE ATT&CK マッピング
| テクニック ID | テクニック名 | 関連性 |
|---|---|---|
| T1195.001 | サプライチェーンの侵害:ソフトウェア依存関係および開発ツールの侵害 | 中核となるテクニックです。攻撃者が、内部依存関係を悪意のある公開パッケージに置き換えます。 |
| T1195.002 | サプライチェーンの侵害:ソフトウェアサプライチェーンの侵害 | 混乱が成功した後に生成される、侵害されたビルド成果物を含む、より広範なカテゴリです。 |
| T1059.007 | コマンドおよびスクリプトインタープリター:JavaScript | npm の preinstall/postinstall ライフサイクルスクリプトが、依存関係の解決時に攻撃者の JavaScript を実行します。 |
| T1071.001 | アプリケーション層プロトコル:Web プロトコル | 置き換えられたパッケージが、盗み出した環境情報や認証情報を攻撃者の HTTP(S) エンドポイントに送信します。 |
ワークフロー
1. コードベース全体のマニフェストを洗い出す
見落としがないよう、すべての依存関係マニフェストを探します。
# Find all supported manifests in a monorepo
find . -type f \( \
-name package.json -o \
-name requirements.txt -o \
-name pom.xml -o \
-name composer.json -o \
-name Gemfile.lock \
\) -not -path '*/node_modules/*' -print
2. confused で npm マニフェストをスキャンする
confused はマニフェストを読み込み、公開レジストリで見つからない依存関係名をすべて報告します。これらが依存関係の取り違えの候補です。
# npm (default language is npm)
confused -l npm package.json
# Treat your known-good scopes as secure to suppress false positives (supports wildcards)
confused -l npm -s '@acme/*,@acme-internal/*' package.json
# Verbose, to see each lookup
confused -l npm -v package.json
3. PyPI、Maven、Composer、RubyGems のマニフェストをスキャンする
-l フラグでエコシステムを選択します。それぞれ、標準のマニフェストファイルに対応しています。
confused -l pip requirements.txt # PyPI -> requirements.txt
confused -l mvn pom.xml # Maven -> pom.xml
confused -l composer composer.json # PHP -> composer.json
confused -l rubygems Gemfile.lock # Ruby -> Gemfile.lock
4. OWASP dep-scan のプライベート名前空間モードで照合する
dep-scan は、宣言されたプライベート名前空間における依存関係の取り違えのリスクを確認し、より広範なリスク監査に反映します。
# Flag private namespaces accidentally claimable on public registries
depscan --src $PWD --reports-dir ./reports \
--private-ns acme,acme_internal,@acme
# Enable deep package risk audit (npm + pypi): typosquats, takeover risk, etc.
depscan --src $PWD --reports-dir ./reports --risk-audit
5. 候補をトリアージし、取得可能か確認する
報告された名前ごとに、公開レジストリに本当に存在しないことを確認します(404 であれば取得可能です)。
# npm: a 404 status means the name is unregistered on the public registry
curl -s -o /dev/null -w "%{http_code}\n" https://registry.npmjs.org/@acme%2finternal-utils
# PyPI: 404 from the JSON API means the project name is free
curl -s -o /dev/null -w "%{http_code}\n" https://pypi.org/pypi/acme-billing-sdk/json
6. スコープとレジストリの固定で npm を修正する
すべての内部スコープをプライベートレジストリに紐付け、同名の公開パッケージが解決されないようにします。
# .npmrc (project root, committed)
@acme:registry=https://artifactory.example.com/api/npm/npm-internal/
//artifactory.example.com/api/npm/npm-internal/:_authToken=${NPM_TOKEN}
# Force the default registry to a single proxy that does NOT merge public + private
registry=https://artifactory.example.com/api/npm/npm-virtual/
インストール前に、解決元を確認します。
npm config get @acme:registry
npm install --dry-run # confirm @acme/* resolves from the private host
7. PyPI と Maven を修正する
Python のインデックス解決と Maven のミラーリングを固定し、公開ソースによって内部成果物が置き換えられないようにします。
# pyproject.toml (PEP 621 / pip >= 23): explicit index pinning
[tool.pip]
index-url = "https://artifactory.example.com/api/pypi/pypi-internal/simple/"
# Do NOT use extra-index-url for internal packages — pip merges and picks highest version.
<mirrors>
<mirror>
<id>internal-virtual</id>
<mirrorOf>*</mirrorOf>
<url>https://artifactory.example.com/artifactory/maven-virtual</url>
</mirror>
</mirrors>
8. 防御目的でプレースホルダーパッケージを登録する
完全に隔離できない名前については、空で機能しないプレースホルダーを公開名として自ら取得し、攻撃者に取得されないようにします。
# npm placeholder claim (scoped, public)
mkdir acme-internal-utils && cd acme-internal-utils
npm init -y
npm pkg set version=0.0.1-placeholder description="Reserved internal name. Do not use."
npm publish --access public
9. 検出を CI に組み込む
新たに紛らわしい依存関係が見つかった場合、パイプラインを失敗させます。
# .github/workflows/depconfusion.yml
name: dependency-confusion
on: [push, pull_request]
jobs:
confused:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- run: go install github.com/visma-prodsec/confused@latest
- name: Scan npm manifest
run: $(go env GOPATH)/bin/confused -l npm -s '@acme/*' package.json
10. 同梱のヘルパーで一括トリアージを実行する
同梱の agent.py を使ってツリーをスキャンし、confused とレジストリへのライブプローブを組み合わせた構造化レポートを出力します。
python scripts/agent.py --path . --ecosystem npm \
--secure-namespaces '@acme/*,@acme-internal/*' \
--output report.json
ツールとリソース
| ツール | 目的 | ソース |
|---|---|---|
| confused | 宣言された依存関係に対応する未登録の名前空間を検出します | https://github.com/visma-prodsec/confused |
| ConfusedDotnet | NuGet/.NET 向けの同じチェックを行います | https://github.com/visma-prodsec/ConfusedDotnet |
| OWASP dep-scan | --private-ns による依存関係の取り違えチェックを含むリスク監査 | https://github.com/owasp-dep-scan/dep-scan |
| OWASP CI/CD Top 10 | CICD-SEC-03 依存関係チェーンの悪用 | https://owasp.org/www-project-top-10-ci-cd-security-risks/ |
| Birsan の調査 | 依存関係の取り違えに関する最初の報告 | https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610 |
| npm scopes docs | スコープとレジストリの固定に関するリファレンス | https://docs.npmjs.com/cli/v10/using-npm/scope |
検証基準
- コードベース内のすべての依存関係マニフェストを洗い出している。
- セキュアな名前空間を指定し、関連するすべてのエコシステムで
confusedを実行している。 - OWASP dep-scan の
--private-nsを実行し、その結果をconfusedの出力と照合している。 - 報告された各名前が取得可能(404)であることを確認するか、誤検知として除外している。
- 内部スコープを
.npmrc/pipの設定/Maven のミラーでプライベートレジストリに固定している。 -
extra-index-urlと統合された仮想フィードについて、最高バージョンが取得されるリスクを確認している。 - 隔離できない名前について、プレースホルダーパッケージを登録している。
- CI ジョブで、すべての push/PR に対してチェックを実行している。
- 担当者と修正状況を含めて、検出事項を文書化している。