Laravel・自作テーマのビルド環境・WordPress プラグイン開発で composer.json や package.json を使っている Web 担当者・制作会社の方向けの記事です。
自分では書いていない「依存パッケージ(ライブラリ)」に脆弱性やマルウェアが混ざるのを、コマンド 2 本の棚卸し → ブロック設定 → CI と自動更新 PR の順で止める方法をまとめます。
設定はすべてコピペで使える形にしてあり、npm 11 / Composer 2.10 以降を前提にしています。
なぜ依存パッケージが狙われるのか
node_modules と vendor の中身の大半は、直接入れていない推移的依存(依存の依存)です。事故はおおむね次の 2 種類です。
| 種類 | 何が起きるか | 主な対策 |
|---|---|---|
| 既知の脆弱性 | 古いバージョンのまま放置され、公開済みの脆弱性が残る | audit で検出 → 更新 |
| 悪意のあるリリース(サプライチェーン攻撃) | 正規パッケージの乗っ取りや偽パッケージで、新しいバージョンにマルウェアが仕込まれる | 公開直後の版を入れない・ブロック機能・署名検証 |
前者は「検出して直す」、後者は「入れる前に止める」が基本です。以下の 7 つの設定で両方に備えます。
① ロックファイルをコミットし、本番はロックどおりに入れる
監査(audit)はロックファイルに書かれた実際のバージョンを見ます。package-lock.json / composer.lock が無い、あるいは環境ごとに違うと、検査結果と本番の中身が一致しません。npm も npm audit の実行には既定で package-lock(または shrinkwrap)を必要とします。
# ロックファイルが Git 管理されているか確認(何も出なければ未管理)
git ls-files package-lock.json composer.lock
# 本番・CI ではロックファイルどおりに入れる(勝手にバージョンを上げない)
npm ci --omit=dev
composer install --no-dev --prefer-dist --no-interaction
npm install / composer update は依存を解決し直してバージョンを動かすコマンドです。本番サーバ上では使わず、開発環境で更新 → ロックファイルをコミット → 本番は npm ci / composer install の流れに固定します。
② 監査コマンドで現状を棚卸しする
まずは今の状態を見ます。本番に載る依存(--omit=dev / --no-dev)と、開発用を含む全体の両方を確認します。開発用の依存は本番には載りませんが、開発者の PC や CI 上でインストール時スクリプトが動くため、マルウェアの入口としては無関係ではありません。
# npm: 本番依存だけ → 全体
npm audit --omit=dev
npm audit
# Composer: ロックファイルを基準に(vendor の状態に左右されない)
composer audit --locked --no-dev
composer audit --locked
件数が多いときは JSON で一覧にすると整理しやすくなります(jq が必要です)。
# npm: パッケージ名 / 重大度 / 直接依存か / 修正方法
npm audit --json | jq -r '
.vulnerabilities | to_entries[]
| [.key, .value.severity, (if .value.isDirect then "direct" else "transitive" end),
(.value.fixAvailable | if type == "object" then "range-out:" + .name + "@" + .version else tostring end)]
| @tsv'
# Composer: パッケージ名 / 重大度 / CVE(なければアドバイザリID) / タイトル
composer audit --locked --format=json | jq -r '
.advisories | to_entries[] | .key as $p | .value[]
| [$p, (.severity // "-"), (.cve // .advisoryId), .title] | @tsv'
fixAvailable が true なら範囲内の更新で直る、range-out:... と出るなら指定範囲外(多くはメジャーバージョンアップ)の更新が必要、false なら上流の修正待ち、という目安になります。Composer の audit は問題が見つかると 0 以外で終了するので、そのまま CI の判定に使えます。
③ 安全な直し方:範囲内の更新を優先し、--force は使わない
# npm: package.json の範囲内で直せるものだけ直す
npm audit fix
# 何がその脆弱なパッケージを引き込んでいるかを確認
npm explain <パッケージ名>
# Composer: 該当パッケージとその依存だけを更新
composer why <vendor/package>
composer update <vendor/package> --with-dependencies
npm audit fix --force は、指定範囲の外(メジャーバージョンアップを含む)まで入れ替えることがあり、npm 公式ドキュメントも計画なしでの使用を強く非推奨としています。メジャー更新が必要なものは、変更点を確認してから個別に上げます。
推移的依存だけが古く、親パッケージの更新を待てない場合は、npm の overrides で修正済みの版以上に引き上げられます(ルートの package.json でのみ有効です)。
{
"overrides": {
"脆弱なパッケージ名": "^修正済みバージョン"
}
}
適用後は npm install → npm audit → 動作確認の順で戻り値と画面を確かめ、親パッケージが修正版に追従したら overrides を外します(残しっぱなしにすると、今度は自分が更新を止める原因になります)。
④ Composer 2.10 の policy で「入れる前に止める」
2026 年 5 月リリースの Composer 2.10 で、依存ポリシーが config.policy に統合されました。主な既定値は次のとおりです(Composer 公式ドキュメント)。
| ポリシー |
block(更新時に入れさせない) |
audit(composer audit の扱い) |
|---|---|---|
| advisories(既知の脆弱性) | true |
fail |
| malware(マルウェア報告) |
true(install 時も検査) |
fail |
| abandoned(放棄パッケージ) | false |
fail |
マルウェアの判定は Packagist.org 経由で提供され、ロックファイル作成後にマルウェアと判定された版も次回の composer install で失敗します。古い Composer ではこの恩恵が受けられないので、まずバージョンを確認します。
composer --version
composer self-update --2 # 2.x の最新へ
チームで方針を明示したい場合は composer.json に書いておくと、誰かがうっかり緩めたときに差分レビューで気づけます。下の例は既定値に加えて、放棄(abandoned)パッケージも更新時に入れさせないよう一段厳しくしています(既存の放棄パッケージは composer audit で洗い出し、代替への移行計画を立ててから有効にします)。
{
"config": {
"policy": {
"advisories": { "block": true, "audit": "fail" },
"malware": { "block": true, "block-scope": "all", "audit": "fail" },
"abandoned": { "block": true, "audit": "fail" }
}
}
}
旧来の config.audit.*(block-insecure など)は 2.10 ではフォールバックとして読まれますが、2.11 で非推奨警告が出る予定です。新規に書くなら config.policy に統一します。
ブロックを外す --no-blocking / COMPOSER_NO_BLOCKING はデプロイスクリプトや CI に常設しないこと、無視が必要なアドバイザリは ID 単位で理由と見直し日を書き、重大度でまとめて無視しないことが運用上の注意点です。
⑤ npm は「公開直後の版を入れない」+署名検証
npm の乗っ取り型マルウェアは、公開から数時間〜数日で検知・削除されることが多いため、公開直後のバージョンを入れないだけでかなりの被害を避けられます。npm 11.10 以降には、そのための min-release-age(日数)があります。プロジェクト直下の .npmrc に書きます。
# .npmrc(プロジェクト直下・Git 管理する)
# 公開から 3 日以上たったバージョンだけをインストール対象にする
min-release-age=3
# npm のバージョン確認(11.10 未満なら設定は効かない)
npm -v
# インストール済みパッケージのレジストリ署名・来歴(provenance)を検証
npm ci
npm audit signatures
緊急の修正版が公開直後で待てない場合だけ、そのコマンドに限り --min-release-age=0 を付け、.npmrc は書き換えません。
⑥ GitHub Actions で PR ごと・毎週の監査を回す
**依存が変わるとき(PR)**と、**依存が変わらなくても新しい脆弱性が公開されるとき(定期実行)**の両方で監査します。
# .github/workflows/dependency-audit.yml
name: dependency-audit
on:
pull_request:
push:
branches: [main]
schedule:
- cron: "0 0 * * 1" # 毎週月曜 09:00 JST
workflow_dispatch:
permissions:
contents: read
jobs:
npm-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm install -g npm@11
- run: npm ci --ignore-scripts
- run: npm audit --omit=dev --audit-level=high
- run: npm audit signatures
composer-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
tools: composer:v2
coverage: none
- run: composer --version
- run: composer validate --no-check-publish
- run: composer audit --locked --no-dev
- 使わない側のジョブは削除し、テーマ配下などにある場合は
defaults: run: working-directory:を足します - 監査はビルドしないので
npm ci --ignore-scriptsで十分です(インストール時スクリプトを CI で動かさない) -
--audit-level=highは失敗にする基準で、表示は絞りません。moderate 以下もログで週次に確認します - トークン権限は
contents: readに最小化し、外部 Action は本番運用ではコミット SHA 固定がより安全です
⑦ Dependabot で更新 PR を自動化する
検出だけでは直りません。GitHub の Dependabot に更新 PR を自動で作らせ、レビューして取り込む流れにします。まずリポジトリの Settings にあるセキュリティ設定(Advanced Security / Code security)で Dependabot alerts と Dependabot security updates を有効にし、続けて定期のバージョン更新を設定します。
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 3
groups:
npm-minor-patch:
patterns: ["*"]
update-types: ["minor", "patch"]
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 3
groups:
composer-minor-patch:
patterns: ["*"]
update-types: ["minor", "patch"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
-
groupsで minor / patch をまとめ、PR の大量放置を防ぎます。メジャー更新は個別 PR で変更点を確認してから取り込みます -
cooldownは ⑤ のmin-release-ageと同じ考え方で、セキュリティ更新には適用されないため修正 PR は遅れません -
github-actionsも対象にすると ⑥ の Action 自体も更新されます。directoryはテーマ配下なら"/wp-content/themes/<テーマ名>"です
Dependabot の PR も ⑥ で監査されるので、「PR → CI 通過 → 動作確認して取り込む」が毎週の作業になります。
まとめ
| # | 設定 | 防ぐもの |
|---|---|---|
| ① | ロックファイルをコミット・本番は npm ci / composer install
|
検査結果と本番の中身のずれ |
| ② |
npm audit / composer audit --locked で棚卸し |
既知の脆弱性の放置 |
| ③ | 範囲内更新・overrides・--force を使わない |
修正のつもりでサイトを壊す事故 |
| ④ | Composer 2.10 の config.policy
|
脆弱な版・マルウェア版の混入 |
| ⑤ | npm の min-release-age と audit signatures
|
公開直後の乗っ取り版の混入 |
| ⑥ | GitHub Actions で PR・週次監査 | 新たに公開された脆弱性の見落とし |
| ⑦ | Dependabot の更新 PR + cooldown | 「検出したが直していない」状態 |
なお、ここで扱ったのは npm / Composer で管理する依存です。WordPress 本体・管理画面から入れたプラグインやテーマは対象外なので、そちらは自動更新の方針(関連記事)と、脆弱性情報の確認を別に回してください。
関連記事
- CDNのJS改ざんを検知するSRI設定と運用
- WordPress自動更新を全部ONにしない運用
- サポート切れPHP(7.4/8.0/8.1)の実害と、止めずに安全に上げる手順
- 「.git」「.env」が世界に公開されていませんか?
本記事のような設定の抜けは、Web サイトを 9 つの守りでまるごと守るサイトドックでまとめて対策できます → https://sitedock.jp