0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

npm/Composerの脆弱な依存を止める7つの設定(audit・Dependabot・コピペOK)

0
Posted at

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 本体・管理画面から入れたプラグインやテーマは対象外なので、そちらは自動更新の方針(関連記事)と、脆弱性情報の確認を別に回してください。

関連記事


本記事のような設定の抜けは、Web サイトを 9 つの守りでまるごと守るサイトドックでまとめて対策できます → https://sitedock.jp

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?