@biffo/cli 0.315.6 → 0.315.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_skeletons/plugin-template/scripts/js-dependency-audit-classification.test.sh +304 -0
- package/_skeletons/plugin-template/scripts/js-dependency-audit.sh +163 -6
- package/_skeletons/sibling-template/.github/workflows/ci.yml +50 -87
- package/_skeletons/sibling-template/scripts/js-dependency-audit-classification.test.sh +304 -0
- package/_skeletons/sibling-template/scripts/js-dependency-audit.sh +163 -6
- package/dist/index.js +0 -4
- package/package.json +2 -4
- package/scripts/runner-drop-forensics.mjs +77 -5
- package/scripts/verify.sh +5 -12
- package/scripts/practices-corpus.mjs +0 -202
- package/scripts/practices-metrics.mjs +0 -2768
|
@@ -53,7 +53,14 @@ jobs:
|
|
|
53
53
|
run:
|
|
54
54
|
working-directory: apps/frontend
|
|
55
55
|
steps:
|
|
56
|
+
# `fetch-depth: 0` (biffo-template#2040) — the Dependency audit step
|
|
57
|
+
# below needs `origin/${GITHUB_BASE_REF}` to resolve locally for its
|
|
58
|
+
# pre-existing-vs-introduced comparison (biffo-template#1673's Python
|
|
59
|
+
# design, now shared by the JS script too), and the default shallow
|
|
60
|
+
# checkout does not carry it.
|
|
56
61
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
62
|
+
with:
|
|
63
|
+
fetch-depth: 0
|
|
57
64
|
- uses: pnpm/action-setup@v4
|
|
58
65
|
with:
|
|
59
66
|
version: 9.15.9
|
|
@@ -74,6 +81,27 @@ jobs:
|
|
|
74
81
|
- name: Test
|
|
75
82
|
if: ${{ !cancelled() }}
|
|
76
83
|
run: pnpm run test
|
|
84
|
+
# Runs IN this required job again, rather than in a separate
|
|
85
|
+
# `continue-on-error` job, as of biffo-template#2040 — reverting the
|
|
86
|
+
# biffo-template#1880 split (see that repo's root `ci.yml` `js` job
|
|
87
|
+
# for the full reasoning this mirrors). #1880 moved this step out
|
|
88
|
+
# because a registry-side INCONCLUSIVE result could block real,
|
|
89
|
+
# unrelated PRs; that risk is unchanged and still accepted here. What
|
|
90
|
+
# changed: js-dependency-audit.sh now classifies findings as
|
|
91
|
+
# "pre-existing on the base branch" vs "introduced by this diff", so
|
|
92
|
+
# the routine case #1880 was reacting to — an advisory published
|
|
93
|
+
# against something already on `dev`, unrelated to any PR's diff — no
|
|
94
|
+
# longer blocks, and only a finding this diff actually introduced or
|
|
95
|
+
# upgraded into does.
|
|
96
|
+
#
|
|
97
|
+
# Path is relative: this job's working-directory is apps/frontend,
|
|
98
|
+
# which is exactly the tree to audit, and the script audits its own
|
|
99
|
+
# cwd — it is byte-identical to biffo-template's copy and to every
|
|
100
|
+
# other satellite's, distributed by that repo's shared-sync.sh, so it
|
|
101
|
+
# cannot assume a layout. Do not edit it here; edit it upstream.
|
|
102
|
+
- name: Dependency audit
|
|
103
|
+
if: ${{ !cancelled() }}
|
|
104
|
+
run: sh ../../scripts/js-dependency-audit.sh
|
|
77
105
|
# Run with NO NEXT_PUBLIC_CORE_COGNITO_* in scope — this is now the
|
|
78
106
|
# PERMANENT state, not a build-guard quirk. The frontend NEVER reads those
|
|
79
107
|
# vars: it resolves the core's Cognito identity at runtime from
|
|
@@ -126,8 +154,8 @@ jobs:
|
|
|
126
154
|
if: ${{ !cancelled() }}
|
|
127
155
|
# This job's working-directory default is apps/frontend (top of this
|
|
128
156
|
# job), but scripts/biffo.sh lives at the repo root -- same relative-
|
|
129
|
-
# path convention as
|
|
130
|
-
# `sh ../../scripts/js-dependency-audit.sh`
|
|
157
|
+
# path convention as this job's own Dependency audit step's
|
|
158
|
+
# `sh ../../scripts/js-dependency-audit.sh` above. A bare
|
|
131
159
|
# `scripts/biffo.sh` here fails unconditionally with "No such file",
|
|
132
160
|
# on every run, regardless of whether there is a real
|
|
133
161
|
# lambda-output/terraform-input violation (biffo-template#1983).
|
|
@@ -193,51 +221,6 @@ jobs:
|
|
|
193
221
|
- name: E2E tests
|
|
194
222
|
run: pnpm run e2e
|
|
195
223
|
|
|
196
|
-
# Split out of the `js` job deliberately (biffo-template#1880): `pnpm
|
|
197
|
-
# audit`'s registry call has been chronically, intermittently unreachable
|
|
198
|
-
# since npm/GitHub retired the legacy audit endpoints on 2026-07-15, an
|
|
199
|
-
# ongoing upstream infrastructure condition unrelated to this repo's own
|
|
200
|
-
# code. Mirrors biffo-template's own root `ci.yml` split; see that repo's
|
|
201
|
-
# `js-audit` job and #1880 for the full evidence. `js` (required) no
|
|
202
|
-
# longer runs this step, so a registry hang can never again block this
|
|
203
|
-
# required check; this job runs the exact same script informationally on
|
|
204
|
-
# every push/PR (`continue-on-error: true`) and is deliberately NOT part
|
|
205
|
-
# of DEFAULT_STATUS_CHECKS/SIBLING_STATUS_CHECKS.
|
|
206
|
-
js-audit:
|
|
207
|
-
name: JS Dependency Audit (non-blocking, #1880)
|
|
208
|
-
runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }}
|
|
209
|
-
timeout-minutes: 20
|
|
210
|
-
continue-on-error: true
|
|
211
|
-
defaults:
|
|
212
|
-
run:
|
|
213
|
-
working-directory: apps/frontend
|
|
214
|
-
steps:
|
|
215
|
-
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
216
|
-
- uses: pnpm/action-setup@v4
|
|
217
|
-
with:
|
|
218
|
-
version: 9.15.9
|
|
219
|
-
- uses: actions/setup-node@v4
|
|
220
|
-
with:
|
|
221
|
-
node-version: ${{ env.NODE_VERSION }}
|
|
222
|
-
cache: pnpm
|
|
223
|
-
cache-dependency-path: apps/frontend/pnpm-lock.yaml
|
|
224
|
-
- run: pnpm install --frozen-lockfile
|
|
225
|
-
- name: Dependency audit
|
|
226
|
-
if: ${{ !cancelled() }}
|
|
227
|
-
# Fails on a real high/critical advisory, but treats a broken audit
|
|
228
|
-
# registry (non-JSON response, or the chronic #1880 timeout) as
|
|
229
|
-
# INCONCLUSIVE rather than blocking (#591, #743) — and this job's own
|
|
230
|
-
# non-required status is what stops that inconclusive result from
|
|
231
|
-
# blocking the merge queue in the first place.
|
|
232
|
-
#
|
|
233
|
-
# The relative path is deliberate. This job's working-directory is
|
|
234
|
-
# apps/frontend, which is exactly the tree to audit, and the script
|
|
235
|
-
# audits its own cwd — it is byte-identical to biffo-template's copy
|
|
236
|
-
# and to every other satellite's, distributed by that repo's
|
|
237
|
-
# shared-sync.sh, so it cannot assume a layout. Do not edit it here;
|
|
238
|
-
# edit it upstream.
|
|
239
|
-
run: sh ../../scripts/js-dependency-audit.sh
|
|
240
|
-
|
|
241
224
|
python:
|
|
242
225
|
name: Python (lint, types, test, security)
|
|
243
226
|
runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }}
|
|
@@ -246,14 +229,14 @@ jobs:
|
|
|
246
229
|
run:
|
|
247
230
|
working-directory: services/api
|
|
248
231
|
steps:
|
|
249
|
-
# `fetch-depth: 0` is
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
# split out of this required job. Nothing remaining in this job reads
|
|
255
|
-
# `origin/` state, so the default shallow checkout is correct here now.
|
|
232
|
+
# `fetch-depth: 0` is required again (biffo-template#2040, reverting
|
|
233
|
+
# #1882's removal of it): the Dependency audit step below (folded back
|
|
234
|
+
# into this required job) needs `origin/${GITHUB_BASE_REF}` to resolve
|
|
235
|
+
# locally for py-dependency-audit.sh's pre-existing-vs-introduced
|
|
236
|
+
# comparison (#1673).
|
|
256
237
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
238
|
+
with:
|
|
239
|
+
fetch-depth: 0
|
|
257
240
|
- uses: astral-sh/setup-uv@v5
|
|
258
241
|
with:
|
|
259
242
|
enable-cache: true
|
|
@@ -264,6 +247,19 @@ jobs:
|
|
|
264
247
|
# committed lock just stops describing what actually ran
|
|
265
248
|
# (biffo-template#1731).
|
|
266
249
|
- run: uv sync --all-groups --locked
|
|
250
|
+
# Runs IN this required job again, rather than in a separate
|
|
251
|
+
# `continue-on-error` job, as of biffo-template#2040 — reverting the
|
|
252
|
+
# biffo-template#1882 split (mirroring the identical `js`-side revert
|
|
253
|
+
# above; see that job's own comment for the full reasoning). What
|
|
254
|
+
# changed: py-dependency-audit.sh has classified findings as
|
|
255
|
+
# "pre-existing on the base branch" vs "introduced by this diff"
|
|
256
|
+
# since #1673, so the routine case #1882 was reacting to no longer
|
|
257
|
+
# blocks, and only a finding this diff actually introduced or
|
|
258
|
+
# upgraded into does. cwd is services/api, which is the environment
|
|
259
|
+
# `uv sync` installed and so the one to audit.
|
|
260
|
+
- name: Dependency audit
|
|
261
|
+
if: ${{ !cancelled() }}
|
|
262
|
+
run: sh ../../scripts/py-dependency-audit.sh
|
|
267
263
|
- name: Lint
|
|
268
264
|
if: ${{ !cancelled() }}
|
|
269
265
|
run: uv run ruff check .
|
|
@@ -367,39 +363,6 @@ jobs:
|
|
|
367
363
|
# *.test.sh guard is added with no run: line anywhere.
|
|
368
364
|
run: sh scripts/guard-self-test-wiring.sh
|
|
369
365
|
|
|
370
|
-
# Split out of the `python` job deliberately (biffo-template#1882, mirroring
|
|
371
|
-
# #1880/#1881's identical JS-side fix): `pip-audit`'s advisory lookups are
|
|
372
|
-
# exactly as registry-dependent and chronically unreliable as `pnpm audit`'s
|
|
373
|
-
# were. `python` (required) no longer runs this step, so a registry-side
|
|
374
|
-
# advisory event can never again block that job; this job runs the exact
|
|
375
|
-
# same script informationally on every push/PR (`continue-on-error: true`)
|
|
376
|
-
# and is deliberately NOT part of DEFAULT_STATUS_CHECKS/SIBLING_STATUS_CHECKS.
|
|
377
|
-
python-audit:
|
|
378
|
-
name: Python Dependency Audit (non-blocking, #1882)
|
|
379
|
-
runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }}
|
|
380
|
-
timeout-minutes: 20
|
|
381
|
-
continue-on-error: true
|
|
382
|
-
defaults:
|
|
383
|
-
run:
|
|
384
|
-
working-directory: services/api
|
|
385
|
-
steps:
|
|
386
|
-
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
387
|
-
with:
|
|
388
|
-
fetch-depth: 0
|
|
389
|
-
- uses: astral-sh/setup-uv@v5
|
|
390
|
-
with:
|
|
391
|
-
enable-cache: true
|
|
392
|
-
- run: uv sync --all-groups --locked
|
|
393
|
-
- name: Dependency audit
|
|
394
|
-
if: ${{ !cancelled() }}
|
|
395
|
-
# Same treatment as the JS job's Dependency audit: fails on a real
|
|
396
|
-
# advisory, INCONCLUSIVE on an unreachable PyPI/OSV (#591, #743) —
|
|
397
|
-
# and this job's own non-required status is what stops that
|
|
398
|
-
# inconclusive result from blocking the merge queue in the first
|
|
399
|
-
# place. cwd is services/api, which is the environment `uv sync`
|
|
400
|
-
# installed and so the one to audit.
|
|
401
|
-
run: sh ../../scripts/py-dependency-audit.sh
|
|
402
|
-
|
|
403
366
|
security-secrets:
|
|
404
367
|
name: Secret Scan
|
|
405
368
|
runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
#
|
|
3
|
+
# Proves js-dependency-audit.sh (#2040) distinguishes "this PR's diff
|
|
4
|
+
# introduced or upgraded to a vulnerable package version" from "an advisory
|
|
5
|
+
# was published against a version already sitting on the base branch,
|
|
6
|
+
# unrelated to this diff" -- rather than reporting an identical red for both.
|
|
7
|
+
# This is the JS side of the fix py-dependency-audit.sh already got in
|
|
8
|
+
# #1673; see that file's own classification test for the Python case table
|
|
9
|
+
# this one mirrors.
|
|
10
|
+
#
|
|
11
|
+
# The real incident this guards against: two routine npm advisories
|
|
12
|
+
# (GHSA-p293-qw3h-jr36 / Next.js, GHSA-82fw-gwwq-j7x9 / vitest) were
|
|
13
|
+
# published against packages already on `dev` on 2026-09-08/09, with no code
|
|
14
|
+
# change anywhere -- and turned the JS lane red on every open PR at once,
|
|
15
|
+
# producing 89 fleet dispatches across 61 units in 8 repos and 10 duplicate
|
|
16
|
+
# tickets for one classifier bug (#2040's own cost accounting). Case 1 below
|
|
17
|
+
# reconstructs exactly that shape: base and PR both pinned at the flagged
|
|
18
|
+
# version, PR diff untouched.
|
|
19
|
+
#
|
|
20
|
+
# ## Real `pnpm audit --json` shape, captured live
|
|
21
|
+
#
|
|
22
|
+
# Run against this repo's own workspace (pnpm 9.15.9, 2026-09-10):
|
|
23
|
+
#
|
|
24
|
+
# $ pnpm audit --json
|
|
25
|
+
# {
|
|
26
|
+
# "advisories": {
|
|
27
|
+
# "1090893": {
|
|
28
|
+
# "severity": "low",
|
|
29
|
+
# "module_name": "cli",
|
|
30
|
+
# "github_advisory_id": "GHSA-6cpc-mj5c-m9rq",
|
|
31
|
+
# "findings": [{"version": "0.0.0", "paths": []}],
|
|
32
|
+
# ...
|
|
33
|
+
# }
|
|
34
|
+
# },
|
|
35
|
+
# "metadata": {
|
|
36
|
+
# "vulnerabilities": {"info": 0, "low": 1, "moderate": 0, "high": 0, "critical": 0},
|
|
37
|
+
# "totalDependencies": 929
|
|
38
|
+
# }
|
|
39
|
+
# }
|
|
40
|
+
#
|
|
41
|
+
# `.advisories` is an OBJECT keyed by an internal numeric id, not an array --
|
|
42
|
+
# `.advisories[]?` iterates its values the same as an array would. Each
|
|
43
|
+
# value carries `.severity`, `.module_name` and `.github_advisory_id`, which
|
|
44
|
+
# is exactly what js-dependency-audit.sh's classification reads. The fixture
|
|
45
|
+
# JSON below is shaped to match this real structure, not invented -- the
|
|
46
|
+
# OLD version of js-dependency-audit-parallel.test.sh's stub JSON carried
|
|
47
|
+
# only `.metadata.vulnerabilities` with no `.advisories` key at all, which
|
|
48
|
+
# classification silently read as zero findings (fixed alongside this file;
|
|
49
|
+
# see that test's own updated `_fail_json` comment).
|
|
50
|
+
#
|
|
51
|
+
# Also proves `pnpm audit` runs against a BARE lockfile copy with no install
|
|
52
|
+
# and no workspace context -- verified directly, not assumed:
|
|
53
|
+
#
|
|
54
|
+
# $ mkdir /tmp/probe && cp pnpm-lock.yaml /tmp/probe/ && cd /tmp/probe
|
|
55
|
+
# $ pnpm audit --json --ignore-workspace # exits 0, real advisory output
|
|
56
|
+
#
|
|
57
|
+
# which is why the base-branch side of this comparison is a second real
|
|
58
|
+
# `pnpm audit` call against a `git show`-extracted copy of the lockfile,
|
|
59
|
+
# not a hand-rolled pnpm-lock.yaml parser.
|
|
60
|
+
#
|
|
61
|
+
# Builds a real, tiny, throwaway git repo under mktemp (never under /tmp as
|
|
62
|
+
# a full worktree copy -- this is a from-scratch repo with a handful of
|
|
63
|
+
# files, not a copy of this repository's own object store -- see AGENTS.md's
|
|
64
|
+
# "never create a git worktree ... under /tmp", which this is not) with a
|
|
65
|
+
# base branch and a PR-head state, and stubs `pnpm` on PATH so `pnpm audit
|
|
66
|
+
# --json` returns a canned finding selected by a marker comment inside
|
|
67
|
+
# whichever pnpm-lock.yaml the stub is invoked against -- no network, no
|
|
68
|
+
# real registry. `jq`, `git` and `grep` are used for real -- they are
|
|
69
|
+
# exactly what the target script itself depends on, so stubbing them would
|
|
70
|
+
# test nothing.
|
|
71
|
+
#
|
|
72
|
+
# Run: sh scripts/js-dependency-audit-classification.test.sh
|
|
73
|
+
|
|
74
|
+
set -u
|
|
75
|
+
|
|
76
|
+
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
77
|
+
TARGET="$SCRIPT_DIR/js-dependency-audit.sh"
|
|
78
|
+
|
|
79
|
+
REPO_DIR=$(mktemp -d)
|
|
80
|
+
STUB_DIR=$(mktemp -d)
|
|
81
|
+
TMPROOT_DIR=$(mktemp -d)
|
|
82
|
+
OUT_FILE=$(mktemp)
|
|
83
|
+
trap 'rm -rf "$REPO_DIR" "$STUB_DIR" "$TMPROOT_DIR"; rm -f "$OUT_FILE"' EXIT
|
|
84
|
+
|
|
85
|
+
FAILURES=0
|
|
86
|
+
|
|
87
|
+
# --- pnpm stub ---------------------------------------------------------------
|
|
88
|
+
# Selected by a `# STATE:<tag>` marker comment inside whatever
|
|
89
|
+
# pnpm-lock.yaml is in the CURRENT DIRECTORY when invoked -- the target
|
|
90
|
+
# script always `cd`s into the tree it is auditing (the repo's own working
|
|
91
|
+
# tree for the HEAD run, or a scratch dir holding a `git show`-extracted
|
|
92
|
+
# copy for the BASE run), so the marker travels with whichever commit's
|
|
93
|
+
# content is actually being read, exactly like a real lockfile diff would.
|
|
94
|
+
cat > "$STUB_DIR/pnpm" <<'STUB'
|
|
95
|
+
#!/usr/bin/env sh
|
|
96
|
+
if [ "$1" = "audit" ]; then
|
|
97
|
+
state=$(grep -o 'STATE:[A-Za-z0-9_-]*' pnpm-lock.yaml 2>/dev/null | head -1 | cut -d: -f2)
|
|
98
|
+
out_file="$STUB_DIR_ENV/output-${state:-clean}.json"
|
|
99
|
+
if [ -f "$out_file" ]; then
|
|
100
|
+
cat "$out_file"
|
|
101
|
+
else
|
|
102
|
+
cat "$STUB_DIR_ENV/output-clean.json"
|
|
103
|
+
fi
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
echo "pnpm stub: unexpected invocation: $*" >&2
|
|
107
|
+
exit 99
|
|
108
|
+
STUB
|
|
109
|
+
chmod +x "$STUB_DIR/pnpm"
|
|
110
|
+
|
|
111
|
+
# --- fixture JSON, one per STATE tag ----------------------------------------
|
|
112
|
+
cat > "$STUB_DIR/output-clean.json" <<'JSON'
|
|
113
|
+
{"metadata":{"vulnerabilities":{"critical":0,"high":0,"moderate":0,"low":0},"totalDependencies":5}}
|
|
114
|
+
JSON
|
|
115
|
+
|
|
116
|
+
# One high-severity advisory against vuln-pkg-a.
|
|
117
|
+
cat > "$STUB_DIR/output-vuln-a.json" <<'JSON'
|
|
118
|
+
{"metadata":{"vulnerabilities":{"critical":0,"high":1,"moderate":0,"low":0},"totalDependencies":5},"advisories":{"1":{"severity":"high","github_advisory_id":"GHSA-test-0001","module_name":"vuln-pkg-a","findings":[{"version":"1.2.3","paths":[]}]}}}
|
|
119
|
+
JSON
|
|
120
|
+
|
|
121
|
+
# Only vuln-pkg-b's advisory (used as the mixed case's BASE state -- pkg-a
|
|
122
|
+
# is absent entirely, pkg-b is present and already flagged).
|
|
123
|
+
cat > "$STUB_DIR/output-vuln-b-only.json" <<'JSON'
|
|
124
|
+
{"metadata":{"vulnerabilities":{"critical":0,"high":1,"moderate":0,"low":0},"totalDependencies":5},"advisories":{"2":{"severity":"high","github_advisory_id":"GHSA-test-0002","module_name":"vuln-pkg-b","findings":[{"version":"2.0.0","paths":[]}]}}}
|
|
125
|
+
JSON
|
|
126
|
+
|
|
127
|
+
# Both advisories together (the mixed case's HEAD state).
|
|
128
|
+
cat > "$STUB_DIR/output-vuln-a-b.json" <<'JSON'
|
|
129
|
+
{"metadata":{"vulnerabilities":{"critical":0,"high":2,"moderate":0,"low":0},"totalDependencies":5},"advisories":{"1":{"severity":"high","github_advisory_id":"GHSA-test-0001","module_name":"vuln-pkg-a","findings":[{"version":"1.2.3","paths":[]}]},"2":{"severity":"high","github_advisory_id":"GHSA-test-0002","module_name":"vuln-pkg-b","findings":[{"version":"2.0.0","paths":[]}]}}}
|
|
130
|
+
JSON
|
|
131
|
+
|
|
132
|
+
# --- repo scaffolding --------------------------------------------------------
|
|
133
|
+
# One workspace-level pnpm-lock.yaml so discovery finds exactly one tree,
|
|
134
|
+
# matching the real incident (the workspace root, not a vendored tree).
|
|
135
|
+
_write_lock() {
|
|
136
|
+
# $1 = destination file, $2 = STATE tag
|
|
137
|
+
cat > "$1" <<LOCK
|
|
138
|
+
lockfileVersion: '9.0'
|
|
139
|
+
# STATE:$2
|
|
140
|
+
LOCK
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_init_repo() {
|
|
144
|
+
# $1 = base STATE tag (what "dev" / origin/<base> holds)
|
|
145
|
+
# $2 = head STATE tag (what the PR branch under test holds)
|
|
146
|
+
base_state=$1
|
|
147
|
+
head_state=$2
|
|
148
|
+
|
|
149
|
+
rm -rf "$REPO_DIR"
|
|
150
|
+
mkdir -p "$REPO_DIR"
|
|
151
|
+
( cd "$REPO_DIR" \
|
|
152
|
+
&& git init -q -b trunk \
|
|
153
|
+
&& git config user.email test@example.com \
|
|
154
|
+
&& git config user.name "Test" )
|
|
155
|
+
|
|
156
|
+
_write_lock "$REPO_DIR/pnpm-lock.yaml" "$base_state"
|
|
157
|
+
( cd "$REPO_DIR" && git add -A && git commit -q -m base )
|
|
158
|
+
# A literal branch named "origin/<base>" -- git tolerates slashes in
|
|
159
|
+
# branch names, so this resolves via `git show origin/<base>:<path>`
|
|
160
|
+
# exactly like a real remote-tracking ref would, with no remote required.
|
|
161
|
+
( cd "$REPO_DIR" && git branch -q "origin/dev" )
|
|
162
|
+
|
|
163
|
+
if [ "$head_state" != "$base_state" ]; then
|
|
164
|
+
_write_lock "$REPO_DIR/pnpm-lock.yaml" "$head_state"
|
|
165
|
+
( cd "$REPO_DIR" && git add -A && git commit -q -m "pr change" )
|
|
166
|
+
fi
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# --- runner ------------------------------------------------------------------
|
|
170
|
+
_run() {
|
|
171
|
+
# Runs the target script with GITHUB_BASE_REF set from $1 (empty string
|
|
172
|
+
# means unset -- a push/workflow_dispatch/merge_group context), cwd inside
|
|
173
|
+
# the synthetic repo, pnpm stubbed, PATH otherwise untouched (jq/git/grep
|
|
174
|
+
# are the real system tools, same as production).
|
|
175
|
+
base_ref=$1
|
|
176
|
+
(
|
|
177
|
+
cd "$REPO_DIR" || exit 97
|
|
178
|
+
PATH="$STUB_DIR:$PATH"
|
|
179
|
+
STUB_DIR_ENV="$STUB_DIR"
|
|
180
|
+
TMPDIR="$TMPROOT_DIR"
|
|
181
|
+
export PATH STUB_DIR_ENV TMPDIR
|
|
182
|
+
if [ -n "$base_ref" ]; then
|
|
183
|
+
GITHUB_BASE_REF="$base_ref"
|
|
184
|
+
export GITHUB_BASE_REF
|
|
185
|
+
else
|
|
186
|
+
unset GITHUB_BASE_REF
|
|
187
|
+
fi
|
|
188
|
+
sh "$TARGET"
|
|
189
|
+
) >"$OUT_FILE" 2>&1
|
|
190
|
+
LAST_RC=$?
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
_assert_exit() {
|
|
194
|
+
name=$1
|
|
195
|
+
expected=$2
|
|
196
|
+
if [ "$LAST_RC" -eq "$expected" ]; then
|
|
197
|
+
echo "PASS: $name (exit $LAST_RC)"
|
|
198
|
+
else
|
|
199
|
+
echo "FAIL: $name -- expected exit $expected, got $LAST_RC"
|
|
200
|
+
echo "--- output ---"
|
|
201
|
+
cat "$OUT_FILE"
|
|
202
|
+
echo "--------------"
|
|
203
|
+
FAILURES=$((FAILURES + 1))
|
|
204
|
+
fi
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_assert_output_contains() {
|
|
208
|
+
name=$1
|
|
209
|
+
needle=$2
|
|
210
|
+
if grep -qF "$needle" "$OUT_FILE"; then
|
|
211
|
+
echo "PASS: $name mentions '$needle'"
|
|
212
|
+
else
|
|
213
|
+
echo "FAIL: $name -- expected output to mention '$needle'"
|
|
214
|
+
echo "--- output ---"
|
|
215
|
+
cat "$OUT_FILE"
|
|
216
|
+
echo "--------------"
|
|
217
|
+
FAILURES=$((FAILURES + 1))
|
|
218
|
+
fi
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# ==============================================================================
|
|
222
|
+
# Case table (must-NOT-block first, then must-block), each run against the
|
|
223
|
+
# real target script -- not a reimplementation of its logic.
|
|
224
|
+
# ==============================================================================
|
|
225
|
+
|
|
226
|
+
# 1. PRE-EXISTING, PR context, advisory UNCHANGED from base (the
|
|
227
|
+
# GHSA-p293-qw3h-jr36 / 2026-09-08 shape: same version on both sides of
|
|
228
|
+
# the diff). Must NOT block, and must say so as pre-existing rather than
|
|
229
|
+
# a flat red.
|
|
230
|
+
_init_repo "vuln-a" "vuln-a"
|
|
231
|
+
_run dev
|
|
232
|
+
_assert_exit "pre-existing, advisory unchanged by diff" 0
|
|
233
|
+
_assert_output_contains "pre-existing case names it as pre-existing" "pre-existing"
|
|
234
|
+
_assert_output_contains "pre-existing case cites #2040" "#2040"
|
|
235
|
+
|
|
236
|
+
# 2. INTRODUCED via upgrade: base had a clean lockfile, this diff's
|
|
237
|
+
# pnpm-lock.yaml moved it to the flagged version. Must block.
|
|
238
|
+
_init_repo "clean" "vuln-a"
|
|
239
|
+
_run dev
|
|
240
|
+
_assert_exit "introduced by upgrade" 1
|
|
241
|
+
_assert_output_contains "introduced-by-upgrade case names it as introduced" "introduced or upgraded by this diff"
|
|
242
|
+
|
|
243
|
+
# 3. INTRODUCED via new dependency: base's lockfile carries no such package
|
|
244
|
+
# at all; this diff added it at an already-vulnerable version. For the
|
|
245
|
+
# advisory-ID-diffing design this is mechanically identical to case 2
|
|
246
|
+
# (base's audit reports no matching id either way) -- kept as its own
|
|
247
|
+
# case for documentation, matching py-dependency-audit-classification's
|
|
248
|
+
# own case 3, which IS mechanically distinct there because Python
|
|
249
|
+
# classifies per-package lockfile version rather than by diffing two
|
|
250
|
+
# full audit runs.
|
|
251
|
+
_init_repo "clean" "vuln-a"
|
|
252
|
+
_run dev
|
|
253
|
+
_assert_exit "introduced via brand-new dependency" 1
|
|
254
|
+
_assert_output_contains "new-dependency case names it as introduced" "introduced or upgraded by this diff"
|
|
255
|
+
|
|
256
|
+
# 4. Non-PR context (push to the integration branch itself, or
|
|
257
|
+
# workflow_dispatch/merge_group -- GITHUB_BASE_REF unset). No diff exists
|
|
258
|
+
# to attribute the finding to, so the classification must NOT apply: any
|
|
259
|
+
# finding blocks, exactly as before this fix. Deliberately the SAME
|
|
260
|
+
# lockfile shape as case 1 (unchanged advisory) to prove the difference
|
|
261
|
+
# in outcome is driven by PR-context alone -- the scheduled base-branch
|
|
262
|
+
# scan added alongside this file (#2040) relies on exactly this: every
|
|
263
|
+
# current finding on `dev` must be reported, not just new ones.
|
|
264
|
+
_init_repo "vuln-a" "vuln-a"
|
|
265
|
+
_run ""
|
|
266
|
+
_assert_exit "no PR context -- always blocks" 1
|
|
267
|
+
|
|
268
|
+
# 5. PR context, but the base ref cannot be resolved locally (e.g. a shallow
|
|
269
|
+
# checkout, or a distributed copy running where fetch-depth: 0 was not
|
|
270
|
+
# honoured). Comparison fails CLOSED: blocks, same as before this fix,
|
|
271
|
+
# rather than silently waving a real regression through because the
|
|
272
|
+
# comparison itself could not be made.
|
|
273
|
+
_init_repo "vuln-a" "vuln-a"
|
|
274
|
+
_run "some-branch-that-was-never-fetched"
|
|
275
|
+
_assert_exit "base ref unresolvable -- fails closed to blocking" 1
|
|
276
|
+
_assert_output_contains "base ref unresolvable -- warns why" "does not resolve locally"
|
|
277
|
+
|
|
278
|
+
# 6. Mixed tree: one pre-existing finding (vuln-pkg-b, unchanged from base)
|
|
279
|
+
# and one introduced finding (vuln-pkg-a, new to this diff) in the same
|
|
280
|
+
# run. Must still block overall (the introduced one), while the
|
|
281
|
+
# pre-existing one is still named as such rather than folded into an
|
|
282
|
+
# undifferentiated total.
|
|
283
|
+
_init_repo "vuln-b-only" "vuln-a-b"
|
|
284
|
+
_run dev
|
|
285
|
+
_assert_exit "mixed tree -- introduced finding still blocks" 1
|
|
286
|
+
_assert_output_contains "mixed tree -- introduced vuln-pkg-a is named" "vuln-pkg-a advisory GHSA-test-0001"
|
|
287
|
+
_assert_output_contains "mixed tree -- pre-existing vuln-pkg-b is named separately" "vuln-pkg-b advisory GHSA-test-0002"
|
|
288
|
+
_assert_output_contains "mixed tree -- pre-existing vuln-pkg-b says so" "pre-existing"
|
|
289
|
+
|
|
290
|
+
# 7. Sanity: a clean run (no vulnerabilities at all) is unaffected by any of
|
|
291
|
+
# the above -- still exits 0, still audits normally with the new
|
|
292
|
+
# 5-arg audit_dir signature.
|
|
293
|
+
_init_repo "clean" "clean"
|
|
294
|
+
_run dev
|
|
295
|
+
_assert_exit "clean run unaffected" 0
|
|
296
|
+
|
|
297
|
+
echo
|
|
298
|
+
if [ "$FAILURES" -eq 0 ]; then
|
|
299
|
+
echo "js-dependency-audit-classification.test.sh: all checks passed."
|
|
300
|
+
exit 0
|
|
301
|
+
else
|
|
302
|
+
echo "js-dependency-audit-classification.test.sh: $FAILURES check(s) failed."
|
|
303
|
+
exit 1
|
|
304
|
+
fi
|