mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Watch main and active release lines daily for new npm advisories
On 30 Sep-1 Oct 2026 GHSA-q2hr-2g5m-vwhr (brace-expansion) and GHSA-p98j-92pf-mc4p (DOMPurify) failed the required "Audit complete frontend dependency graph" step on every pull request to main, release/v6.4 and release/v6.5. That held the v6.4.6 preparation PR and the v6.5 RC for days with no owner. The scheduled audit in security-scan.yml only informs and runs on the default branch, so release lines were never checked. dependency-advisory-watch.yml runs daily and on dispatch. It lists main plus every release/v<major>.<minor> line at or newer than the latest stable's line (all lines if that lookup fails), then audits each in a fail-fast-free matrix. Each job fetches only that line's frontend-modern/package.json and package-lock.json with git and runs scripts/npm-audit-retry.sh all on them under the same Node.js pin that build-and-test requires. npm audit reads the lockfile, so nothing is installed. The job never checks out or runs the audited branch's code, because a scheduled run holds the default branch's cache scope and CodeQL flagged running npm ci there as cache poisoning. A job fails when the audit fails, and its step summary and annotation name the branch, the GHSA ids and the fix (npm audit fix --package-lock-only plus raised floors in dependencySecurity.test.ts). Read-only, hosted-only, no secrets, no uploads.
This commit is contained in:
committed by
rcourtman
parent
e5082c5fb1
commit
64d8dbc7fc
@@ -151,6 +151,19 @@ nothing; results are in the job log and step summary. Run
|
||||
`scripts/release_lifecycle_rehearsal.sh --from <tag> --to <tag>` to reproduce
|
||||
it locally (`PULSE_REHEARSAL_ENGINE=podman` on hosts without Docker).
|
||||
|
||||
`dependency-advisory-watch.yml` runs the required build-and-test frontend audit
|
||||
daily against `main` and every active `release/v<major>.<minor>` line (the
|
||||
latest stable's line and newer), because a new npm advisory against an
|
||||
unchanged lockfile otherwise surfaces only when it fails every pull request on
|
||||
that branch. Each line is a separate matrix job that reads only that line's
|
||||
`frontend-modern/package.json` and `package-lock.json` (it never checks out,
|
||||
installs or runs another branch's code in the default branch's scope) and audits
|
||||
them with the same Node.js pin and `scripts/npm-audit-retry.sh all`. A failing job names
|
||||
the branch and advisories in its step summary and annotation; the fix is
|
||||
`npm audit fix --package-lock-only` on that line plus raised floors in
|
||||
`frontend-modern/src/security/__tests__/dependencySecurity.test.ts`. It is
|
||||
read-only, hosted-only, uses no secrets and uploads nothing.
|
||||
|
||||
The shared `install-sh-smoke-body.yml` inherits its caller's token permissions;
|
||||
keep it free of workflow- or job-level permission overrides. Continuity calls
|
||||
that body directly with `contents: read`. The existing `install-sh-smoke.yml`
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
name: Dependency Advisory Watch
|
||||
|
||||
# A new npm advisory against an unchanged lockfile fails the required
|
||||
# "Audit complete frontend dependency graph" step in build-and-test.yml on
|
||||
# every pull request to that branch. On 30 Sep-1 Oct 2026 two advisories did
|
||||
# exactly that to main, release/v6.4 and release/v6.5 at once, and nothing
|
||||
# owned the fix. This watch runs the same audit daily against main and every
|
||||
# active release line, fails when any of them would block, and names the
|
||||
# branch, the advisories and the fix in the step summary. Scheduled workflows
|
||||
# only run on the default branch. The audit needs only each line's
|
||||
# frontend-modern/package.json and package-lock.json, so this job reads those
|
||||
# two files with git and never checks out, installs or executes another
|
||||
# branch's code in the default branch's context.
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 5 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: dependency-advisory-watch
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
lines:
|
||||
name: List active release lines
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
branches: ${{ steps.lines.outputs.branches }}
|
||||
steps:
|
||||
- name: Checkout watch helpers
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Resolve main and active release lines
|
||||
id: lines
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
heads="$(mktemp)"
|
||||
git ls-remote --heads "https://github.com/${REPOSITORY}.git" 'refs/heads/release/v*' > "${heads}"
|
||||
# The newest stable's line and newer are active. If the lookup fails,
|
||||
# every release line is audited rather than none.
|
||||
latest_stable="$(gh api "repos/${REPOSITORY}/releases/latest" --jq '.tag_name' 2>/dev/null || true)"
|
||||
branches="$(python3 scripts/dependency_advisory_watch.py lines --heads "${heads}" --latest-stable "${latest_stable}")"
|
||||
rm -f "${heads}"
|
||||
echo "Newest stable: ${latest_stable:-unknown}; auditing ${branches}"
|
||||
python3 scripts/write_github_output.py branches "${branches}"
|
||||
|
||||
audit:
|
||||
name: Audit frontend dependencies (${{ matrix.branch }})
|
||||
needs: lines
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
branch: ${{ fromJSON(needs.lines.outputs.branches) }}
|
||||
steps:
|
||||
- name: Checkout watch helpers
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Read the audited branch's lockfile inputs
|
||||
env:
|
||||
BRANCH: ${{ matrix.branch }}
|
||||
AUDITED: ${{ runner.temp }}/audited
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "${AUDITED}"
|
||||
git fetch --no-tags --depth=1 origin "+refs/heads/${BRANCH}:refs/remotes/audited/head"
|
||||
for file in package.json package-lock.json; do
|
||||
git show "refs/remotes/audited/head:frontend-modern/${file}" > "${AUDITED}/${file}"
|
||||
done
|
||||
|
||||
# Same Node.js and audit runner as the required build-and-test Frontend
|
||||
# job. npm audit reads the lockfile, so no install is needed and no
|
||||
# package from the audited branch is fetched or run. The runner always
|
||||
# comes from this workflow's own commit, so a release line that predates
|
||||
# it is held to the same verdict main's required check applies.
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Audit complete frontend dependency graph
|
||||
id: audit
|
||||
working-directory: ${{ runner.temp }}/audited
|
||||
env:
|
||||
NPM_AUDIT_REQUIRE_RESULT: 'true'
|
||||
run: |
|
||||
set -o pipefail
|
||||
bash "$GITHUB_WORKSPACE/scripts/npm-audit-retry.sh" all 2>&1 | tee "$RUNNER_TEMP/npm-audit.log"
|
||||
|
||||
- name: Report advisories and fix
|
||||
if: ${{ !cancelled() }}
|
||||
env:
|
||||
BRANCH: ${{ matrix.branch }}
|
||||
AUDIT_OUTCOME: ${{ steps.audit.outcome }}
|
||||
run: |
|
||||
python3 scripts/dependency_advisory_watch.py summary \
|
||||
--branch "${BRANCH}" \
|
||||
--outcome "${AUDIT_OUTCOME}" \
|
||||
--log "$RUNNER_TEMP/npm-audit.log" \
|
||||
--summary-file "$GITHUB_STEP_SUMMARY"
|
||||
Reference in New Issue
Block a user