本記事は生成AIと共同で執筆しています。事実関係は可能な範囲で公式ドキュメント等と照合していますが、誤りが含まれている可能性があります。重要な判断を行う前にご自身でも一次情報をご確認ください。
このブログ (Next.js + Cloudflare Workers) を運用しているリポジトリに Dependabot のアラートが 2 件届いていました。postcss と esbuild の moderate 脆弱性で、どちらも transitive (孫依存) として残っていたものです。
postcss < 8.5.10 — XSS via Unescaped </style> (GHSA-qx2v-qp2m-jg93 / CVE-2026-41305)
esbuild <= 0.24.2 — dev server CORS leak (GHSA-67mh-4wv8-2f99)
「自動で塞いでほしい」のがそもそもの動機でしたが、調べていくうちに、Dependabot のような既知 CVE ベースの仕組みだけではサプライチェーン攻撃には弱いことが見えてきました。今回は「既知 CVE の自動対応」と「サプライチェーン攻撃への部分的な備え」を 1 セットでまとめます。
何を「自動対応」したいのかを切り分ける
仕組みを足す前に、想定する脅威を分けて考えたほうがよさそうでした。
| 種類 | 例 | 検出のしくみ |
|---|---|---|
| 既知の脆弱性 | postcss の XSS、Next.js の SSRF | NVD / GHSA に登録済み。npm audit / Dependabot で見える |
| メンテナ乗っ取り → 悪意ある新版公開 | chalk/debug の npm アカウント侵害 (2025-09、qix アカウントのフィッシング侵害)、eslint-config-prettier (2025-07)、ua-parser-js (2021) | 公開後に検出され yank されるまでの数時間〜数日が攻撃の窓 |
| Actions の改竄 | tj-actions/changed-files (2025-03) で世界中のリポジトリから secret 流出 | 複数の mutable タグを単一の悪意 commit に force-push し直す形 |
| 長期潜伏型 backdoor | xz-utils (2024-03 発覚、信頼構築に 2 年以上) | 通常の audit では検出できない |
| postinstall フックでの RCE | npm install の瞬間にコード実行 | スキャン困難。実行を止めるしかない |
npm audit や Dependabot security update は 1 行目 に対しては強いものの、2〜5 行目には別レイヤーが必要です。今回入れた設定は、このうち 2 と 3 と 5 をある程度カバーする構成です。4 (xz-utils 型) には太刀打ちできません。
入れた設定
公式機能の組み合わせで、特殊なことはしていません。
1. transitive な脆弱性は npm overrides で pin
postcss も esbuild も直接依存ではなく、next などが内部で引き連れていた孫依存でした。Dependabot は直接依存しかバージョン提案できないので、package.json の overrides で固定します。
{
"overrides": {
"postcss": ">=8.5.10",
"esbuild": ">=0.25.0"
}
}
npm install --package-lock-only --ignore-scripts
npm audit # found 0 vulnerabilities
実際の node_modules/postcss は 8.5.14 に置き換わり、アラートは解消されました。
2. Dependabot に cooldown を入れる
最近のメンテナ乗っ取り型サプライチェーン攻撃は、公開後 数時間〜数日で検出され yank されるケースが多いように見えます。なので「公開直後の版は取り込まない」を入れます。
この目的では Renovate の minimumReleaseAge 設定が先行して提供されており、「サプライチェーン対策を入れるなら Renovate」と言われることが多かった印象です。Dependabot 側にも 2025-07 に cooldown が GA として追加された ので、いまは Dependabot 1 本でもこの層の防御を組めます。
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/web"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
groups:
minor-and-patch:
update-types: ["minor", "patch"]
cooldown:
default-days: 5
semver-major-days: 30
semver-minor-days: 7
semver-patch-days: 3
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 5
semver-major-days: 30
semver-minor-days: 7
semver-patch-days: 3
公式 docs によると cooldown は security update には適用されません。つまり「CVE が出たら即時、それ以外は数日待つ」の両立になります。
3. GitHub Actions は SHA で pin する
actions/checkout@v4 のようなタグは可変で、メンテナがいつでも別の commit に貼り直せます。tj-actions/changed-files の事件 (2025-03、CVE-2025-30066) は、攻撃者が v1 から v45 付近までの複数の既存タグを 単一の悪意ある commit に force-push し直し、その commit が CI runner のメモリから secret を抽出してワークフローログに base64 で書き出すようにした実例として知られています。流出経路は外部サーバではなく公開ワークフローログそのものでした。
防御は GitHub 公式の Security hardening for GitHub Actions でも推奨されている通り「不変な commit SHA で固定する」だけです。
# NG: 可変タグ
- uses: actions/checkout@v4
# OK: 不変な SHA + 人間用コメント
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
タグから SHA を引くには git ls-remote が手堅いです。
git ls-remote --tags https://github.com/actions/checkout.git refs/tags/v4
# 34e114876b0b11c390a56381ad16ebd13914f8d5 refs/tags/v4
実リポジトリでは sed で一括置換しました。
cd .github/workflows && for f in *.yml; do
sed -i.bak \
-e 's|uses: actions/checkout@v4|uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4|g' \
-e 's|uses: actions/setup-node@v4|uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4|g' \
-e 's|uses: actions/setup-python@v5|uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5|g' \
"$f"
rm "$f.bak"
done
コメントの # v4 は人間が読むためのもので、Dependabot もここを読んで「v4 の最新 SHA」へと SHA とコメント両方を書き換えてくれます。なので SHA pin にすると更新追従ができなくなるわけではありません。GitHub 公式の actions/* org も対象にしました (公式 org でも乗っ取りリスクはゼロではないため)。
4. security update だけ auto-merge する
cooldown を入れた上で、security advisory が紐づいた PR だけ自動マージします。minor/patch の通常更新はそのまま手動レビューに残します。
判定方法は、最初は dependabot/fetch-metadata アクションの ghsa-id output を使うつもりでしたが、調べてみるとこの output は alert-lookup: true を指定したうえで PAT (Personal Access Token) を github-token に渡さないと populate されない仕様でした。デフォルトの GITHUB_TOKEN だと常に空文字になり、条件が false で全 PR が skip されてしまいます。
代わりに、Dependabot が security update PR に自動で付与する security ラベルで判定するほうがシンプルでした。ラベル判定なら追加トークンは不要です。
# .github/workflows/dependabot-auto-merge.yml
name: Dependabot auto-merge
on: pull_request
permissions:
contents: write
pull-requests: write
jobs:
auto-merge:
if: >-
github.actor == 'dependabot[bot]' &&
contains(github.event.pull_request.labels.*.name, 'security')
runs-on: ubuntu-latest
steps:
- env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
gh pr review --approve "$PR_URL" -b "Auto-approving Dependabot security update."
gh pr merge --auto --squash "$PR_URL"
auto-merge を有効にするには、リポジトリ側のフラグも必要です。gh から非対話で叩けました。後者は Enable Dependabot security updates のエンドポイントです。
gh api -X PATCH repos/:owner/:repo \
-f allow_auto_merge=true \
-f delete_branch_on_merge=true
gh api -X PUT repos/:owner/:repo/automated-security-fixes
auto-merge は CI 通過がゲートになるので、PR 用の最小 CI workflow も同時に置きました (npm ci + npm audit --audit-level=high + npm run build だけ)。なお、auto-merge の安全性は CI で何を検査しているか に依存します。今回のように静的なブログで build が通れば概ね大丈夫な構成ならこれで足りますが、複雑な機能を持つアプリで auto-merge を入れる場合は、E2E や integration test までカバーしておかないと「脆弱性は塞げたが機能はデグレ」のリスクが上がるはずです。
5. ignore-scripts=true で postinstall を止める
サプライチェーン攻撃の中で実害が大きいのが、postinstall 等のインストール時フックでの任意コード実行です。web/.npmrc に 1 行入れました。
ignore-scripts=true
ただ、これを有効にすると native binary を持つパッケージが壊れる印象があったので、念のため別ディレクトリで npm ci --ignore-scripts を実行して挙動を確認しました。
esbuild—postinstallhook 自体は持つものの、本体バイナリは@esbuild/darwin-arm64等 26 プラットフォーム分の optionalDependencies 経由で配布されるため動作 OKsharp—@img/sharp-darwin-arm64等 14 プラットフォーム +@img/sharp-libvips-*を optionalDependencies として取るため動作 OKworkerd(Cloudflare Workers ランタイム) — こちらもpostinstallhook を持つが、バイナリは@cloudflare/workerd-darwin-arm64等の optionalDependencies 経由で入るため動作 OK
postinstall 自体は残っていますが、配布の本筋が optional dep に移ったので ignore-scripts 下でも build が通る、というのが実情でした。
ただし、これは今回のスタックがたまたま optional dep への移行が進んでいる側に倒れていただけで、スタック次第ではこの設定で build が壊れます。たとえば prisma のように postinstall で prisma generate を走らせて型を生成する構成や、native 拡張をローカルでビルドするライブラリを使っている場合は、ignore-scripts=true だけだと型や binary が生成されずに死にます。その場合は npm rebuild <pkg> や npx prisma generate を build スクリプト側で明示的に呼び直すなど、必要なフックだけ opt-in する設計が必要になります。導入する前に対象リポジトリで一度 npm ci --ignore-scripts && npm run build を試して確かめるのが安全です。
何が守れて、何が守れないか
正直に整理しておきます。
| シナリオ | 今回の構成で守れる? |
|---|---|
| 既知 CVE (今回の postcss/esbuild など) | 守れる (npm audit + auto-merge) |
| 公開後 数時間〜数日で yank されるアカウント侵害型 | 守れることが多い (cooldown 5 日) |
tj-actions/changed-files 型 (Actions の mutable tag 改竄) | 守れる (SHA pin) |
| postinstall フックでの RCE | 守れる (ignore-scripts) |
| 長期潜伏型 backdoor (xz-utils 型) | 守れない。cooldown 30 日でも通る |
| ゼロデイ脆弱性 | 守れない (登録される前は誰も知らない) |
dev マシンで npm install した瞬間に侵害される経路 | 一部のみ (ローカルにも .npmrc は効く) |
typosquatting (reactdom のような偽パッケージ) | 守れない (人間レビュー頼り) |
GitHub Actions の permissions: 最小化不足による secret 流出 | 今回は手をつけていません |
xz-utils 型 (悪意ある maintainer が 2 年以上かけて信頼を得てから差し込んだ backdoor) のような攻撃には、今回の構成では対応できません。ここに踏み込むには socket.dev のような事前スキャンや、OpenSSF Scorecard ベースの依存先評価といった別レイヤーが要りそうです。
残しているもの
次に手をつけるとしたら、このあたりかと考えています。
- 各 workflow の
permissions:を job 単位で最小化する (現状はデフォルト) socket.devの GitHub App を入れて PR 時にスコアを表示gitleaksの pre-commit hook (secret コミット防御)- ローカル開発マシン側の
.npmrcグローバル設定にもignore-scripts=trueを入れる検討
今回入れた構成は「個人運用の小さなリポジトリでも、追加コストほぼゼロで足せる範囲」に収めたつもりです。すべての攻撃を防げる構成ではないですが、現状の Dependabot だけよりは攻撃面が狭まったと考えています。
関連記事 (日本語)
本記事の個別トピックについては、より深く扱った日本語の先行記事が複数あります。交差して読むと内容の裏取りになります。
- 電通総研「GitHub Dependabotのcooldown機能を試す」 —
cooldown機能の挙動検証 - ncdc「サードパーティアクションはSHAで指定すれば安心? 残念ながら、いいえ」 — SHA pin の限界も含めた踏み込んだ議論
- junko_ai「axios攻撃は2行で防げる|.npmrc設定とignore-scriptsの注意点」 —
ignore-scriptsの実害シナリオと実験 - zephel01「2026年のサプライチェーン攻撃から身を守る:実装可能な対策まとめ|npm / Python / Go / Rust」 — 言語横断のサプライチェーン対策まとめ
参考 (一次情報)
- Security hardening for GitHub Actions (GitHub Docs) — SHA pin の根拠
- Dependabot options reference (GitHub Docs) —
cooldownの構文と security update への非適用 - Dependabot supports configuration of a minimum package age (GitHub Changelog, 2025-07-01) —
cooldownGA 告知 overrides(npm Docs) —npm overrides仕様dependabot/fetch-metadata(GitHub) — 当初試したアクション (ghsa-id利用にはalert-lookup: true+ PAT が必要)- tj-actions/changed-files の事件まとめ (StepSecurity) — mutable tag 攻撃の実例
- CVE-2025-30066 (NVD) — tj-actions/changed-files の CVE 記録
- CVE-2024-3094 (NVD) — xz-utils backdoor
- OpenSSF Scorecard checks —
Pinned-Dependenciesチェック項目


