From 687b8bb235645b1d872faec68b7c2ea775af1865 Mon Sep 17 00:00:00 2001 From: Satoru Nakamura Date: Thu, 17 Sep 2026 11:52:34 -0700 Subject: [PATCH 1/7] ci: .github/workflows/dependabot-security-merge.yml --- .../workflows/dependabot-security-merge.yml | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 .github/workflows/dependabot-security-merge.yml diff --git a/.github/workflows/dependabot-security-merge.yml b/.github/workflows/dependabot-security-merge.yml new file mode 100644 index 0000000..a1a583e --- /dev/null +++ b/.github/workflows/dependabot-security-merge.yml @@ -0,0 +1,128 @@ +name: Dependabot security merge + +# Dependabot の更新のうち「脆弱性を減らすもの」だけを自動で取り込む。 +# +# 取り込む条件 (すべて満たすとき): +# 1. Dependabot が作った PR である +# 2. メジャー更新ではない (破壊的変更を自動で入れない) +# 3. 変更された package-lock.json で、npm audit の high/critical が減る +# 4. この PR の他のチェック (CI など) がすべて成功している +# +# ふつうの更新 (脆弱性と無関係) は取り込まない。公開直後の悪意ある版を +# 自動で入れないため。そちらは人が見てからマージする。 +# +# 判定に dependabot/fetch-metadata の ghsa-id を使わないのは、GITHUB_TOKEN では +# Dependabot alert を読めず、常に空になるため。npm audit は同じ GHSA データベースを +# 見るので、更新前後の件数差で「セキュリティ修正か」を判定できる。 +# +# この job は書き込み権限を持つので、PR 側のコードは一切実行しない +# (checkout も npm install もしない。lockfile を API で読むだけ)。 +# +# GITHUB_TOKEN でのマージは push のワークフローを起動しない。 +# そのため、デプロイ用のワークフローを DEPLOY_WORKFLOWS で明示的に起動する。 + +on: pull_request + +permissions: + contents: write + pull-requests: write + actions: write + +env: + # マージ後に起動するワークフロー (空白区切り。無ければ空) + DEPLOY_WORKFLOWS: "nuxtjs.yml" + +jobs: + merge: + if: github.event.pull_request.user.login == 'dependabot[bot]' + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + id: meta + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: 脆弱性が減るか (npm audit の high/critical を更新前後で比べる) + id: audit + if: steps.meta.outputs.update-type != 'version-update:semver-major' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + PR: ${{ github.event.pull_request.number }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + # 比較の起点は PR の分岐元。pull_request.base.sha は PR 作成時の main を指すことがあり、 + # rebase 後だと、その間に main へ入った別の修正までこの PR の手柄に数えてしまう。 + BASE_SHA=$(gh api "repos/$REPO/compare/$BASE_REF...$HEAD_SHA" --jq .merge_base_commit.sha) + count() { # $1=ref $2=dir + local d; d=$(mktemp -d) + for f in package.json package-lock.json; do + gh api -H 'Accept: application/vnd.github.raw' "repos/$REPO/contents/$2$f?ref=$1" > "$d/$f" || { echo -1; return; } + done + local v + v=$( (cd "$d" && npm audit --package-lock-only --json 2>/dev/null || true) \ + | jq '[.vulnerabilities[]?.via[] | objects | select(.severity == "high" or .severity == "critical") | .source] | unique | length' 2>/dev/null || true) + rm -rf "$d" + echo "${v:--1}" + } + reduced=false + for lock in $(gh pr diff "$PR" -R "$REPO" --name-only | grep -E '(^|/)package-lock\.json$' || true); do + dir=$(dirname "$lock"); [ "$dir" = "." ] && dir="" || dir="$dir/" + before=$(count "$BASE_SHA" "$dir"); after=$(count "$HEAD_SHA" "$dir") + echo "${lock}: high+critical ${before} -> ${after}" + if [ "$before" -ge 0 ] && [ "$after" -ge 0 ] && [ "$after" -lt "$before" ]; then reduced=true; fi + done + echo "reduced=$reduced" >> "$GITHUB_OUTPUT" + [ "$reduced" = true ] || echo "::notice::脆弱性が減らない更新なので、自動では取り込みません" + + - name: 他のチェックの完了を待つ + id: checks + if: steps.audit.outputs.reduced == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + PR: ${{ github.event.pull_request.number }} + SELF: ${{ github.workflow }} + run: | + set -uo pipefail + sleep 60 + i=0 + while [ $i -lt 100 ]; do + json=$(gh pr checks "$PR" -R "$REPO" --json name,bucket,workflow 2>/dev/null) || true # 待ち中は終了コード 8 + [ -n "$json" ] || json='[]' + others=$(echo "$json" | jq --arg s "$SELF" '[.[] | select(.workflow != $s)]') + total=$(echo "$others" | jq length) + pending=$(echo "$others" | jq '[.[] | select(.bucket == "pending")] | length') + bad=$(echo "$others" | jq '[.[] | select(.bucket == "fail" or .bucket == "cancel")] | length') + echo "checks: total=$total pending=$pending failed=$bad" + if [ "$bad" -gt 0 ]; then echo "::warning::失敗したチェックがあるので取り込みません"; exit 0; fi + if [ "$total" -gt 0 ] && [ "$pending" -eq 0 ]; then echo "ok=true" >> "$GITHUB_OUTPUT"; exit 0; fi + sleep 30; i=$((i+1)) + done + echo "::warning::チェックが無い、または時間内に終わらないので取り込みません" + + - name: マージしてデプロイを起動 + if: steps.checks.outputs.ok == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + PR_URL: ${{ github.event.pull_request.html_url }} + BASE: ${{ github.event.pull_request.base.ref }} + run: | + set -euo pipefail + # 同時に複数の PR が通ると、後の方は「main が変わった」で一時的に失敗する。 + # 間を空けて数回やり直す。本当に衝突している場合は、Dependabot が自分で + # rebase し、この判定がもう一度走る。 + n=0 + until gh pr merge --squash --delete-branch "$PR_URL"; do + n=$((n+1)) + if [ $n -ge 5 ]; then echo "::warning::マージできませんでした (衝突の可能性)"; exit 0; fi + sleep $((15 + RANDOM % 30)) + done + for w in $DEPLOY_WORKFLOWS; do + echo "start $w" + gh workflow run "$w" -R "$REPO" --ref "$BASE" + done From 555cdd314cc25bd757f75f0b479c9c946f9e0ef5 Mon Sep 17 00:00:00 2001 From: Satoru Nakamura Date: Thu, 17 Sep 2026 11:52:36 -0700 Subject: [PATCH 2/7] ci: .github/workflows/ci.yml --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9668819 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +# Dependabot の PR を自動で取り込む前の関門 (dependabot-security-merge.yml が +# この結果を待つ)。人が出す PR でも同じ確認が走る。 + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 22 + cache: npm + - run: npm ci --ignore-scripts + - run: npm run build --if-present From 5d257e008bfca37faf8659b492269f44246d5338 Mon Sep 17 00:00:00 2001 From: Satoru Nakamura Date: Thu, 17 Sep 2026 11:56:23 -0700 Subject: [PATCH 3/7] =?UTF-8?q?ci:=20=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E5=85=88=E3=82=92=E7=B5=9E=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From a1e25bb3808303153511dcd5decef300fa27ed3d Mon Sep 17 00:00:00 2001 From: Satoru Nakamura Date: Thu, 17 Sep 2026 11:58:33 -0700 Subject: [PATCH 4/7] =?UTF-8?q?ci:=20=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E5=85=88=E3=82=92=E7=B5=9E=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From b9e0aaae0a14770fe91452f13794589ecaad4a3f Mon Sep 17 00:00:00 2001 From: Satoru Nakamura Date: Thu, 17 Sep 2026 12:24:01 -0700 Subject: [PATCH 5/7] =?UTF-8?q?ci:=20lockfile=20=E3=81=8C=E5=90=88?= =?UTF-8?q?=E3=82=8F=E3=81=AA=E3=81=84=E3=81=A8=E3=81=8D=E3=81=AF=20instal?= =?UTF-8?q?l=20=E3=81=A7=E5=85=A5=E3=82=8C=E7=9B=B4=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9668819..6a66932 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,5 +20,6 @@ jobs: with: node-version: 22 cache: npm - - run: npm ci --ignore-scripts + # lockfile が package.json と合っていないリポジトリがあるので、その場合は入れ直す + - run: npm ci --ignore-scripts || npm install --ignore-scripts --no-audit --no-fund - run: npm run build --if-present From ad3ec3430db3ba373e18139f3393139289abeae0 Mon Sep 17 00:00:00 2001 From: Satoru Nakamura Date: Thu, 17 Sep 2026 12:57:00 -0700 Subject: [PATCH 6/7] =?UTF-8?q?ci:=20lockfile=20=E3=81=8C=E5=90=88?= =?UTF-8?q?=E3=82=8F=E3=81=AA=E3=81=84=E3=81=A8=E3=81=8D=E3=81=AF=20instal?= =?UTF-8?q?l=20=E3=81=A7=E5=85=A5=E3=82=8C=E7=9B=B4=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 857295604f74ba2d85863d9c40a7de11f30f6946 Mon Sep 17 00:00:00 2001 From: Satoru Nakamura Date: Thu, 17 Sep 2026 14:23:27 -0700 Subject: [PATCH 7/7] =?UTF-8?q?ci:=20npm=20=E3=83=97=E3=83=AD=E3=82=B8?= =?UTF-8?q?=E3=82=A7=E3=82=AF=E3=83=88=E3=81=AE=E5=A0=B4=E6=89=80=E3=81=AB?= =?UTF-8?q?=E5=90=88=E3=82=8F=E3=81=9B=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a66932..48ad327 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: with: node-version: 22 cache: npm + cache-dependency-path: package-lock.json # lockfile が package.json と合っていないリポジトリがあるので、その場合は入れ直す - run: npm ci --ignore-scripts || npm install --ignore-scripts --no-audit --no-fund - run: npm run build --if-present