Both findings were "previously missed" suppressed ones; the two against scripts/dependency-refresh.mjs and its workflow are relayed to #2239. - Serialize the sweep with a fixed concurrency group and cancel-in-progress: false. The marker check is a read-before-write and a workflow_dispatch can land on top of the scheduled run, so two runs could both see no open issue and both file one — the duplicate the whole idempotency design exists to prevent. The queued run must wait and re-read, never be cancelled. - Pick the milestone in JS, not in jq. jq sorts null before every string, so sort_by(.due_on) | .[0] returns an UNDATED open milestone in preference to every dated one. An undated bucket has no due date and so cannot be the nearest; pickMilestone drops it, and files the issue unmilestoned if nothing dated is open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013H6Cew3KB3jVmQ5x4Dq1sq Signed-off-by: cliffhall <cliff@futurescale.com>
103 lines
4.8 KiB
YAML
103 lines
4.8 KiB
YAML
# Dependabot alert sweep (#2233), the alert-consuming half of #2229.
|
|
#
|
|
# Dependabot's SECURITY-update PRs are turned off for this repo; its ALERTS are
|
|
# left on. Those are two independent settings, and this workflow depends on the
|
|
# split: it reads the alerts and turns them into ordinary board-tracked issues,
|
|
# so the fix is written by hand against `v2/main` like any other work.
|
|
#
|
|
# alert -> this sweep -> issue (labeled, milestoned, boarded) -> maintainer PR -> v2/main
|
|
#
|
|
# It is a SCHEDULE, not an event handler, because there is no `dependabot_alert`
|
|
# workflow trigger — that is a webhook event only. Daily is deliberate: with
|
|
# security PRs off there is no mergeable-against-`main` artifact and no window to
|
|
# race, so the merge guard #2060 needed has no analogue here. What replaces it is
|
|
# a precondition inside the script: `automated-security-fixes` is a repo SETTING
|
|
# and can be switched back on from the UI without a commit, so the sweep reads it
|
|
# back and fails loudly on an explicit `enabled: true`. That read needs a
|
|
# permission `GITHUB_TOKEN` cannot hold, so with the default token it reports
|
|
# UNVERIFIED instead — see the token notes below; it is a conditional guard, not
|
|
# an invariant.
|
|
#
|
|
# ⚠️ GitHub computes the dependency graph, and therefore every alert, from the
|
|
# DEFAULT branch (`main`), while we ship from `v2/main`. Two consequences:
|
|
#
|
|
# * An alert is re-checked against `v2/main`'s own lockfile before an issue is
|
|
# filed — hence the `ref: v2/main` checkout below. An alert whose vulnerable
|
|
# range no longer matches is already fixed on the branch we ship from and is
|
|
# waiting on a milestone merge to close, so it is skipped silently.
|
|
# * A vulnerable dependency introduced on `v2/main` and not yet merged to
|
|
# `main` produces NO alert at all. No approach that consumes GitHub's alerts
|
|
# avoids that. The release-time `npm audit --audit-level=high` report from
|
|
# #2231 is a second signal that partially covers it — at release time only;
|
|
# running that same report over `v2/main`'s lockfiles on a schedule would
|
|
# close it fully and is a separable follow-up.
|
|
#
|
|
# `vulnerability-alerts: read` is the one non-default permission, and
|
|
# `GITHUB_TOKEN` supports it — no PAT is needed to read the alerts themselves.
|
|
# Two side steps are outside its reach, and `PROJECT_TOKEN` is what covers them
|
|
# when it exists:
|
|
#
|
|
# * Writing the board card, since board #28 is an ORG project
|
|
# (`organization projects: write`). Absent, the issue is still filed labeled
|
|
# and milestoned and the next `/issue-triage` sweep boards it.
|
|
# * Reading back `automated-security-fixes`, which needs `administration: read`
|
|
# — a permission `permissions:` has no key for, so `GITHUB_TOKEN` can never
|
|
# have it. Absent, that assertion is reported as UNVERIFIED rather than
|
|
# failing the run; an explicit `enabled: true` still fails it.
|
|
#
|
|
# Nothing the sweep exists to do is skipped for want of that secret.
|
|
#
|
|
# The version-update half of #2229 is the sibling `dependency-refresh.yml`.
|
|
name: Dependabot Alert Sweep
|
|
|
|
on:
|
|
schedule:
|
|
- cron: "17 6 * * *" # 06:17 UTC daily; alerts are not minute-sensitive
|
|
workflow_dispatch:
|
|
|
|
# The marker check is a read-before-write, not an atomic one, and nothing stops
|
|
# a `workflow_dispatch` from landing on top of the scheduled run. Two overlapping
|
|
# runs would both see no open issue and both file one, which is the exact
|
|
# duplicate this sweep's whole idempotency design exists to prevent (Copilot).
|
|
# `cancel-in-progress: false` because the queued run must WAIT and then re-read
|
|
# the state the first run wrote — cancelling it would drop a sweep instead.
|
|
concurrency:
|
|
group: dependabot-alert-sweep
|
|
cancel-in-progress: false
|
|
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
vulnerability-alerts: read
|
|
|
|
jobs:
|
|
alert-sweep:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout v2/main
|
|
uses: actions/checkout@v7
|
|
with:
|
|
ref: v2/main
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version: "22.x"
|
|
cache: "npm"
|
|
|
|
# Root install only, and no lifecycle scripts. The sweep's one dependency
|
|
# is `semver`; it reads every lockfile as JSON and never needs a client's
|
|
# tree on disk, so the postinstall cascade into `clients/*` that
|
|
# `dependency-refresh.yml` genuinely needs (it shells out to
|
|
# `npm outdated` in each) would be minutes of nothing here.
|
|
- name: Install root dependencies
|
|
run: npm ci --ignore-scripts
|
|
|
|
- name: Run the Dependabot alert sweep
|
|
run: node scripts/dependabot-alerts.mjs
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
# Optional: an org-project PAT with `organization projects: write`.
|
|
# Absent, the issue is filed unboarded and triage picks it up.
|
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|