目次
- ソフトウェアサプライチェーン攻撃とは|依存パッケージ経由の攻撃の型
- 「サプライチェーン」は部品の調達経路
- 代表的な攻撃の型
- ロックファイルとバージョン固定|「意図しない変化」を止める
- ロックファイルが守っているもの
- 「最新すぐ導入」をあえてしない
- npm audit・pip-auditとDependabot・Renovateの運用
- 既知の脆弱性を検出する
- Dependabot・Renovateで更新を自動化する
- SBOM生成(Syft)とTrivyスキャン|「何を使っているか」を証明する
- SBOMは部品表
- SBOMは成果物と一緒に保管する
- CIでの自動化と運用の落とし穴
- ワークフローに組み込む
- トラブル事例:ロックファイルがあるのに本番だけ挙動が違う
- まとめ
自社で書いたコードは数千行でも、npm installやpip installで取り込んでいるコードは数十万行、という状態は珍しくありません。その大半は自分で読んだことのない他人のコードです。「有名なパッケージだから大丈夫」と思っていたら、ある日そのパッケージのメンテナが乗っ取られ、認証情報を盗むコードが混入していた——こうした事件は、ここ数年で現実に繰り返し起きています。
この記事では、依存パッケージ経由の攻撃にどんな型があるかを整理し、ロックファイルとバージョン固定、npm audit・pip-auditによる脆弱性検出、Dependabot・Renovateによる更新の自動化、SyftによるSBOM生成とTrivyによるスキャン、そしてそれらをCIに組み込む方法を解説します。自社アプリの脆弱性そのものはOWASP Top 10、コンテナイメージの安全性はDockerセキュリティのベストプラクティスで扱っているので、ここでは「取り込んでいる他人のコード」に焦点を絞ります。
ソフトウェアサプライチェーン攻撃とは|依存パッケージ経由の攻撃の型
「サプライチェーン」は部品の調達経路
製造業で部品の調達経路をサプライチェーンと呼ぶように、ソフトウェアでも「自社コード+ライブラリ+ビルドツール+コンテナベースイメージ+CI環境」という調達経路全体を指してソフトウェアサプライチェーンと呼びます。攻撃者にとっては、堅牢な自社アプリを直接攻めるより、そのアプリが信頼して取り込む部品に細工するほうが効率的です。1つの人気パッケージに悪意あるコードを混ぜられれば、それを使う何万ものプロジェクトに一度に侵入できるからです。
代表的な攻撃の型
| 攻撃の型 | 手口 | 主な防御 |
|---|---|---|
| タイポスクワッティング | lodashに対してlodahsのような、打ち間違いを狙った名前で悪意あるパッケージを公開する | ロックファイル、パッケージ名のレビュー、社内ミラー |
| 依存関係の混乱(Dependency Confusion) | 社内限定パッケージと同名のパッケージを公開レジストリに高いバージョンで置き、公開側が優先されるようにする | レジストリのスコープ固定、社内パッケージ名の予約 |
| メンテナ乗っ取り・引き継ぎ | 長年更新されていないパッケージの管理権を譲り受けたり、アカウントを乗っ取ったりして悪意あるバージョンを公開する | バージョン固定、更新時の差分確認、更新の遅延導入 |
| 既知の脆弱性の放置 | 攻撃ではなく怠慢だが、公開済みの脆弱性を持つ古いバージョンを使い続ける | npm audit、Dependabot、SBOMによる継続監視 |
| ビルド・CI環境の侵害 | CIのアクションやビルドスクリプトを改ざんし、ビルド成果物に細工する | アクションのバージョンをハッシュで固定、権限の最小化 |
これらに共通するのは、「いつの間にか取り込んでいたものが変わっていた」という点です。したがって防御の基本は、何を取り込んでいるかを正確に把握し(可視化)、意図しない変化を止め(固定)、既知の問題を継続的に検出する(監視)の3つになります。
ロックファイルとバージョン固定|「意図しない変化」を止める
ロックファイルが守っているもの
package.jsonの"express": "^4.18.0"という指定は「4.18.0以上5.0未満のどれか」という意味で、インストールするタイミングによって実際のバージョンが変わります。これを「解決した結果」として、間接的な依存まで含めて厳密なバージョンと内容のハッシュを記録するのがpackage-lock.json(npm)、yarn.lock、poetry.lock、Gemfile.lockといったロックファイルです。ロックファイルがあれば、開発者のPCでもCIでも本番でも、まったく同じ内容が展開されます。
実務での必須ルールは次の3つです。
- ロックファイルを必ずGitにコミットする(アプリケーションの場合)。
- CIと本番では
npm installではなくnpm ciを使う。npm ciはロックファイルとpackage.jsonが矛盾するとエラーになり、勝手にロックファイルを書き換えません。 - ロックファイルの差分をコードレビューの対象にする。見慣れないパッケージが増えていたら、その理由を確認する。
# CIや本番ではロックファイルどおりに再現インストールする
npm ci
# Pythonの場合:ハッシュ付きで固定した要件ファイルから入れる
pip install --require-hashes -r requirements.txt
# 依存の中身が「登録時と同じか」を検証する(npm)
npm ci --ignore-scripts # インストール時スクリプトの実行を抑止し、悪性コードの実行機会を減らす
パッケージ管理ツールの基本的な動作はパッケージ管理ツールの仕組みと選び方で解説しています。
「最新すぐ導入」をあえてしない
メンテナ乗っ取り型の攻撃では、悪意あるバージョンが公開されてから発覚・取り下げまで数時間〜数日かかります。この間に自動更新で取り込んでしまうのが最悪のパターンです。対策として、「公開から一定日数(たとえば3〜7日)経ったバージョンだけを更新候補にする」運用が有効で、RenovateにはminimumReleaseAgeという設定があります。セキュリティ修正の即時適用とのバランスは、脆弱性アラートは即時、通常更新は遅延、と使い分けます。
npm audit・pip-auditとDependabot・Renovateの運用
既知の脆弱性を検出する
npm auditやpip-auditは、ロックファイルに記録された依存関係を脆弱性データベースと突き合わせ、既知の脆弱性を持つバージョンを報告します。手元で実行するだけでなく、CIで「重大度High以上があればビルドを失敗させる」ようにするのが基本形です。
# Node.js:重大度high以上があれば非ゼロ終了(CIで失敗させる)
npm audit --audit-level=high
# Python:pip-auditで環境またはrequirementsを検査
pip install pip-audit
pip-audit -r requirements.txt
# 自動修正(ロックファイルの範囲内で安全なバージョンに上げる)
npm audit fix
注意点として、npm auditの結果には「開発時にしか使わないツールの、実際には到達しない脆弱性」も多く含まれます。すべてをゼロにするのではなく、本番で動くコードに含まれる(--omit=devで絞る)High以上を優先し、それ以外は期限を決めて対応する、という優先順位づけが必要です。
Dependabot・Renovateで更新を自動化する
脆弱性が見つかってから手動で更新していると、必ず遅れが出ます。GitHubのDependabotやRenovateは、依存関係の更新をプルリクエストとして自動作成し、CIでテストを通したうえでレビューに回してくれます。
# .github/dependabot.yml の最小例
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
groups:
minor-and-patch:
update-types: ["minor", "patch"] # マイナー・パッチはまとめて1PRに
- package-ecosystem: "github-actions" # CIで使うアクション自体も更新対象にする
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
運用のコツは、「PRが溜まって誰も見なくなる」状態を避けることです。マイナー・パッチ更新はグループ化して週1回まとめてマージし、メジャー更新だけ個別に検討する。セキュリティアラート由来のPRは優先ラベルを付けて当日中に確認する。この2段構えにすると、中小規模のチームでも無理なく回せます。GitHub Actions側の設定の基本はGitHub Actions入門を参照してください。
SBOM生成(Syft)とTrivyスキャン|「何を使っているか」を証明する
SBOMは部品表
SBOM(Software Bill of Materials)は、ソフトウェアに含まれるすべてのコンポーネントとバージョンを列挙した「部品表」です。新しい脆弱性が公表されたとき、「うちのどのシステムがそのライブラリを使っているか」を即座に答えられることが目的です。SBOMがなければ、全リポジトリを手作業でgrepして回ることになります。取引先や監査からSBOMの提出を求められるケースも増えており、フォーマットはSPDXとCycloneDXの2つが標準的です。
SyftはSBOMを生成するツールで、ソースディレクトリやコンテナイメージを対象にできます。Trivyは脆弱性スキャナーで、SBOMやイメージを入力に既知の脆弱性を検出します。
# SyftでコンテナイメージからCycloneDX形式のSBOMを生成
syft myapp:1.4.2 -o cyclonedx-json > sbom.cdx.json
# 生成したSBOMをTrivyでスキャンし、重大度HIGH以上があれば失敗させる
trivy sbom sbom.cdx.json --severity HIGH,CRITICAL --exit-code 1
# イメージを直接スキャンすることもできる(OSパッケージとアプリ依存の両方を検査)
trivy image --severity HIGH,CRITICAL myapp:1.4.2
コンテナイメージを対象にすると、npmやpipの依存だけでなく、ベースイメージのOSパッケージ(OpenSSLやglibcなど)の脆弱性も同時に検出できます。アプリの依存は綺麗でもベースイメージが古い、というケースは非常に多いものです。
SBOMは成果物と一緒に保管する
SBOMはビルドのたびに生成し、リリースされたバージョンに紐づけて保管します。「本番で動いている1.4.2のSBOM」がすぐ取り出せる状態にしておけば、脆弱性公表時の影響調査が数分で終わります。GitHub Releasesに添付する、あるいはS3などのストレージにタグ名で保存するのが簡単な方法です。
CIでの自動化と運用の落とし穴
ワークフローに組み込む
# .github/workflows/supply-chain.yml(抜粋)
name: supply-chain
on: [pull_request, push]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm ci --ignore-scripts
- run: npm audit --audit-level=high --omit=dev
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: SBOM and scan
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: myapp:${{ github.sha }}
severity: HIGH,CRITICAL
exit-code: 1
ここでuses:に指定しているアクション自体もサプライチェーンの一部です。より厳密にするなら、タグ(@v4)ではなくコミットハッシュで固定し、Dependabotのgithub-actionsエコシステムで更新を追う、という形にします。
トラブル事例:ロックファイルがあるのに本番だけ挙動が違う
症状:ステージングでは動くのに、本番デプロイ後だけ日付処理が壊れた。package-lock.jsonはコミットされていた。
原因:本番のデプロイスクリプトがnpm install --productionを使っていた。開発者がローカルでpackage.jsonだけ編集してロックファイルを更新せずにコミットしたため、npm installが本番で新しい解決を行い、間接依存の日付ライブラリがメジャーバージョンアップしていた。ステージングは古いキャッシュを使っていて差が出なかった。
対処:本番・ステージングともnpm ci --omit=devに統一し、CIに「package.jsonとロックファイルの矛盾があれば失敗」というチェック(npm ci自体がこれを兼ねる)を追加。さらにロックファイルの変更を含むPRには必ずレビュアーを付けるルールにした。
もうひとつの落とし穴が、CIに渡す認証情報です。レジストリのトークンや署名鍵がリポジトリに紛れ込むと、サプライチェーンの防御そのものが崩れます。秘密情報をGitに入れない仕組みとあわせて整備してください。
まとめ
ソフトウェアサプライチェーンの防御は、可視化(SBOM)・固定(ロックファイルとnpm ci)・監視(audit、Dependabot、Trivy)の3本柱で成り立ちます。タイポスクワッティングやメンテナ乗っ取りは「いつの間にか変わっていた」ことを突く攻撃なので、変化を止め、変化があれば気づける仕組みが最大の防御です。まずはロックファイルのコミットとnpm ciの徹底、次にDependabotの有効化、そしてCIでのTrivyスキャンとSBOM保管、という順に導入すれば、数日で一通りの体制が整います。
依存関係の棚卸しからCIパイプラインへの組み込み、SBOM提出への対応まで、実装面での支援が必要な場合はHarmonic Societyのシステム開発・インフラ支援にご相談ください。
Harmonic Society
この記事の内容、自社の業務でも活かせそうですか?
ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。
関連記事
Related / 9 articles
Notes & Insights
- プログラミング
DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用
DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。
- プログラミング
WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド
WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。
- プログラミング
セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説
CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。
- プログラミング
クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計
クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。
- プログラミング
秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除
APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。
- プログラミング
開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御
開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。
- プログラミング
ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方
NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。
- プログラミング
ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方
ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。
- プログラミング
localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決
localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。
Harmonic Society
「読んで終わり」にせず、自社の業務で試してみませんか?
AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。
無料・30分・オンラインOK|1営業日以内に返信します