From d4f618dc28c4806b491a1caa3c32d28aeb7009bb Mon Sep 17 00:00:00 2001 From: Jake Fineman Date: Wed, 5 Aug 2026 15:10:54 -0400 Subject: [PATCH 1/7] =?UTF-8?q?ci:=20vendor=20governance-enforce=20(A=5FBL?= =?UTF-8?q?OCK=20secrets=20scan)=20=E2=80=94=20this=20repo=20was=20never?= =?UTF-8?q?=20in=20the=20ruleset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/governance-enforce.yml | 59 ++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/governance-enforce.yml diff --git a/.github/workflows/governance-enforce.yml b/.github/workflows/governance-enforce.yml new file mode 100644 index 0000000..4263300 --- /dev/null +++ b/.github/workflows/governance-enforce.yml @@ -0,0 +1,59 @@ +name: governance-enforce + +# A_BLOCK gate: no-secrets-in-git / secrets-from-doppler / no-hardcoded-paths, enforced on the +# PR diff via the @wave-av/governance package (the org fan-out channel). Diff-scoped: blocks NEW +# violations without failing on legacy debt. Isolated install bypasses any min-release-age policy. +# The org ruleset `governance-a-block-enforce` requires this job's `enforce` check. +# +# VENDORED 2026-08-05 (claude-workstation#1624, E4 T4.9a). This repo was never in that ruleset's +# include list, because the list is 112 hand-maintained names and every one of them matches +# `wave-*`. A naming convention had silently become a security boundary: the repos that publish +# our npm packages — cli, sdk, adk, mcp-server, workflow-sdk — were the ones running with no +# A_BLOCK secrets scan at all. Copied verbatim from wave-moq-edge, and proven on a PUBLIC repo +# before fan-out: wave-av/cli#20, run 31011943790, conclusion success. +# +# DO NOT add this repo to `governance-a-block-enforce` until this check is observed green here. +# A required status check that never reports is a permanent deadlock, not a stricter gate. + +on: + pull_request: + push: + branches: [main, master] + +permissions: + contents: read + packages: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + enforce: + runs-on: ubuntu-latest + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + - name: fetch governance enforcer (isolated install) + run: | + mkdir -p "$RUNNER_TEMP/gov" && cd "$RUNNER_TEMP/gov" + printf '@wave-av:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}\n' > .npmrc + npm install @wave-av/governance@^0.4.4 --no-save --no-audit --no-fund + - name: A_BLOCK enforce (secrets + hardcoded paths on the diff) + env: + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PUSH_BEFORE_SHA: ${{ github.event.before }} + run: | + BASE="${PR_BASE_SHA:-$PUSH_BEFORE_SHA}" + if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ]; then + BASE=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD) + fi + echo "diffing against $BASE" + node "$RUNNER_TEMP/gov/node_modules/@wave-av/governance/bin/enforce.mjs" --changed "$BASE" From 70ac4c9916385a2735966582620c56913ff851f6 Mon Sep 17 00:00:00 2001 From: yakimoto Date: Wed, 5 Aug 2026 19:15:01 +0000 Subject: [PATCH 2/7] ci: fall back to HEAD~1 when the push base SHA is unreachable (force-push) Co-authored-by: Codesmith --- .github/workflows/governance-enforce.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/governance-enforce.yml b/.github/workflows/governance-enforce.yml index 4263300..9941075 100644 --- a/.github/workflows/governance-enforce.yml +++ b/.github/workflows/governance-enforce.yml @@ -52,7 +52,10 @@ jobs: PUSH_BEFORE_SHA: ${{ github.event.before }} run: | BASE="${PR_BASE_SHA:-$PUSH_BEFORE_SHA}" - if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ]; then + # Empty/all-zero base (branch creation) or a base made unreachable by a force-push: + # fall back to HEAD~1 instead of handing enforce.mjs a SHA git can't resolve. + if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ] \ + || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then BASE=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD) fi echo "diffing against $BASE" From e6e12edffa07c57f92c13569c81c4ed240baba55 Mon Sep 17 00:00:00 2001 From: yakimoto Date: Wed, 5 Aug 2026 19:30:44 +0000 Subject: [PATCH 3/7] ci: scope NODE_AUTH_TOKEN to the install step; diff empty tree when HEAD has no parent Co-authored-by: Codesmith --- .github/workflows/governance-enforce.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/governance-enforce.yml b/.github/workflows/governance-enforce.yml index 9941075..519b43e 100644 --- a/.github/workflows/governance-enforce.yml +++ b/.github/workflows/governance-enforce.yml @@ -31,8 +31,6 @@ concurrency: jobs: enforce: runs-on: ubuntu-latest - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: @@ -42,6 +40,10 @@ jobs: with: node-version: "22" - name: fetch governance enforcer (isolated install) + # Token scoped to this step only: the enforce step executes downloaded code and + # must not see NODE_AUTH_TOKEN. + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | mkdir -p "$RUNNER_TEMP/gov" && cd "$RUNNER_TEMP/gov" printf '@wave-av:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}\n' > .npmrc @@ -54,9 +56,11 @@ jobs: BASE="${PR_BASE_SHA:-$PUSH_BEFORE_SHA}" # Empty/all-zero base (branch creation) or a base made unreachable by a force-push: # fall back to HEAD~1 instead of handing enforce.mjs a SHA git can't resolve. + # If HEAD has no parent (initial commit), diff against the empty tree so the gate + # scans everything rather than comparing HEAD with itself and passing vacuously. if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ] \ || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then - BASE=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD) + BASE=$(git rev-parse HEAD~1 2>/dev/null || git hash-object -t tree /dev/null) fi echo "diffing against $BASE" node "$RUNNER_TEMP/gov/node_modules/@wave-av/governance/bin/enforce.mjs" --changed "$BASE" From 67d046c06e21099d8f99759295b524b43360f9b9 Mon Sep 17 00:00:00 2001 From: yakimoto <66892052+yakimoto@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:16:20 -0400 Subject: [PATCH 4/7] fix(ci): the vendored A_BLOCK gate could report PASS having scanned nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five defects, none of them cosmetic. Refs wave-av/claude-workstation#1747. 1. FAIL-OPEN DIFF BASE. `BASE=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD)` — on a root commit `git rev-parse HEAD~1` prints its unresolved argument to stdout AND fails, so the `||` branch appends and BASE becomes a two-line string. `git diff` then exits 128, and the pinned enforcer turned that into zero files and a green check. Now: a reachability-checked base (a force-push can leave `github.event.before` pointing at a commit this checkout does not have), and with no resolvable base at all it diffs against the EMPTY TREE so the whole repo is scanned rather than nothing. 2. THE PINNED ENFORCER ITSELF FAILED OPEN. `^0.4.4` resolved to 0.4.4, whose file lister is `catch { return []; }` — any git error became zero files and rendered as `OK[enforce]: 0 changed file(s) scanned — 0 A_BLOCK violations`. A git error and a clean diff were byte-identical in the output. The fix had sat unreleased on claude-workstation main since 2026-07-29 because no `governance-v*` tag was ever pushed. Released now as 0.4.6 and pinned exactly here. 3. TOKEN IN SCOPE FOR THE WRONG STEPS. `NODE_AUTH_TOKEN` was job-level, so it was also in the environment of the step that executes the downloaded package. Now step-scoped, and the .npmrc holding it is removed on exit. 4. INSTALL SCRIPTS RAN WITH THAT TOKEN. `npm install` runs preinstall/postinstall by default. Added `--ignore-scripts`. 5. CANCELLED PUSH RUNS WERE SCANNED BY NOBODY. `cancel-in-progress: true` applied to push runs, and each push run only diffs its own before..HEAD range — so a cancelled run's commits were never examined by anything. Now PR-only. Also: `timeout-minutes: 10` and `set -euo pipefail`. Receipt, against a scratch repo whose root commit carries a no-hardcoded-paths violation, simulating a branch-creation push (`before` = all zeros): old logic -> malformed base -> caught error -> [] -> OK, 0 files scanned, PASS new logic -> "no diff base resolved ... scanning the whole tree" -> BLOCK, exit 1 Credit where it is due: several of these were found by the review bots on the sibling vendoring PRs and are folded in here — the step-scoped token, the .npmrc cleanup, the exact pin, `--ignore-scripts`, the force-push reachability check, `timeout-minutes`, and the concurrency hole (5), which was crest-console#7's catch and which I had missed entirely. --- .github/workflows/governance-enforce.yml | 57 +++++++++++++++++------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/.github/workflows/governance-enforce.yml b/.github/workflows/governance-enforce.yml index 519b43e..6e9d0ae 100644 --- a/.github/workflows/governance-enforce.yml +++ b/.github/workflows/governance-enforce.yml @@ -9,8 +9,11 @@ name: governance-enforce # include list, because the list is 112 hand-maintained names and every one of them matches # `wave-*`. A naming convention had silently become a security boundary: the repos that publish # our npm packages — cli, sdk, adk, mcp-server, workflow-sdk — were the ones running with no -# A_BLOCK secrets scan at all. Copied verbatim from wave-moq-edge, and proven on a PUBLIC repo -# before fan-out: wave-av/cli#20, run 31011943790, conclusion success. +# A_BLOCK secrets scan at all. +# +# HARDENED 2026-08-05 (claude-workstation#1747), before any of the fan-out merged. The copy first +# vendored here could report PASS having examined nothing. Five fixes, each marked at its site +# below. A gate may not return a passing value for input it did not examine. # # DO NOT add this repo to `governance-a-block-enforce` until this check is observed green here. # A required status check that never reports is a permanent deadlock, not a stricter gate. @@ -24,13 +27,17 @@ permissions: contents: read packages: read +# FIX 4 — a cancelled push run's commits were scanned by NOBODY. Every push run diffs only its +# own before..HEAD range, so cancelling run N when run N+1 starts leaves N's commits permanently +# unexamined. PR runs are safe to supersede: each one re-diffs the whole branch against its base. concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: enforce: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: @@ -40,27 +47,47 @@ jobs: with: node-version: "22" - name: fetch governance enforcer (isolated install) - # Token scoped to this step only: the enforce step executes downloaded code and - # must not see NODE_AUTH_TOKEN. + # FIX 1 — token scoped to THIS STEP. At job level it was also in scope for the step that + # executes the downloaded package, and for anything else the job ever grows. env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + set -euo pipefail mkdir -p "$RUNNER_TEMP/gov" && cd "$RUNNER_TEMP/gov" + trap 'rm -f "$RUNNER_TEMP/gov/.npmrc"' EXIT printf '@wave-av:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}\n' > .npmrc - npm install @wave-av/governance@^0.4.4 --no-save --no-audit --no-fund + # FIX 2 — --ignore-scripts. npm runs preinstall/install/postinstall by default, so this + # step would execute dependency-authored code with the registry token in its environment. + # FIX 3 — exact pin, and 0.4.6 specifically. `^0.4.4` resolved to 0.4.4, whose file lister + # is `catch { return []; }` — ANY git error became zero files and rendered as + # `OK[enforce]: 0 changed file(s) scanned`. 0.4.6 fails closed on a git error instead. + # A caret is also a standing authorization for whatever is published next; a bump is now + # a visible commit in this file. + npm install @wave-av/governance@0.4.6 --no-save --no-audit --no-fund --ignore-scripts - name: A_BLOCK enforce (secrets + hardcoded paths on the diff) env: PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} PUSH_BEFORE_SHA: ${{ github.event.before }} run: | - BASE="${PR_BASE_SHA:-$PUSH_BEFORE_SHA}" - # Empty/all-zero base (branch creation) or a base made unreachable by a force-push: - # fall back to HEAD~1 instead of handing enforce.mjs a SHA git can't resolve. - # If HEAD has no parent (initial commit), diff against the empty tree so the gate - # scans everything rather than comparing HEAD with itself and passing vacuously. + set -euo pipefail + ENFORCE="$RUNNER_TEMP/gov/node_modules/@wave-av/governance/bin/enforce.mjs" + BASE="${PR_BASE_SHA:-}" + [ -n "$BASE" ] || BASE="${PUSH_BEFORE_SHA:-}" + # A base can be PRESENT and still unusable: a force-push leaves `github.event.before` + # pointing at a commit this checkout no longer contains. if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ] \ - || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then - BASE=$(git rev-parse HEAD~1 2>/dev/null || git hash-object -t tree /dev/null) + || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then + BASE="$(git rev-parse --verify --quiet 'HEAD~1' || true)" + fi + # FIX 5 — fail CLOSED. This previously fell back to `BASE=HEAD`, and `--changed HEAD` + # diffs HEAD against itself: an empty diff, zero files scanned, job green. With no + # resolvable base, diff against the EMPTY TREE so every tracked file reads as added and + # the whole repo is scanned. (`--all` also exists in 0.4.6 and would do most of this, but + # it is documented as NOT covering the diff-scoped over-grant detectors. Routing through + # the diff path with an empty base keeps every detector in play.) + if [ -z "$BASE" ]; then + BASE="$(git hash-object -t tree /dev/null)" + echo "::notice::no diff base resolved (root commit or unreachable before-sha) — scanning the whole tree against the empty tree" fi echo "diffing against $BASE" - node "$RUNNER_TEMP/gov/node_modules/@wave-av/governance/bin/enforce.mjs" --changed "$BASE" + exec node "$ENFORCE" --changed "$BASE" From 7cea87b193270f7892204dc4b18d61d6366a9d64 Mon Sep 17 00:00:00 2001 From: yakimoto Date: Thu, 6 Aug 2026 00:24:05 +0000 Subject: [PATCH 5/7] fix(ci): recover a force-push-orphaned diff base instead of narrowing the scan to HEAD~1 Co-authored-by: Codesmith --- .github/workflows/governance-enforce.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/governance-enforce.yml b/.github/workflows/governance-enforce.yml index 6e9d0ae..724d8e0 100644 --- a/.github/workflows/governance-enforce.yml +++ b/.github/workflows/governance-enforce.yml @@ -73,11 +73,17 @@ jobs: ENFORCE="$RUNNER_TEMP/gov/node_modules/@wave-av/governance/bin/enforce.mjs" BASE="${PR_BASE_SHA:-}" [ -n "$BASE" ] || BASE="${PUSH_BEFORE_SHA:-}" - # A base can be PRESENT and still unusable: a force-push leaves `github.event.before` - # pointing at a commit this checkout no longer contains. - if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ] \ - || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then + if [ "$BASE" = "0000000000000000000000000000000000000000" ]; then + # Branch creation: no before-SHA exists. Diff the previous commit if there is one. BASE="$(git rev-parse --verify --quiet 'HEAD~1' || true)" + elif [ -n "$BASE" ] && ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then + # A base can be PRESENT and still missing locally: a force-push leaves + # `github.event.before` pointing at a commit this checkout no longer contains. + # Narrowing to HEAD~1 here would leave every earlier commit of the force-push + # unscanned, so first try to recover the real base — GitHub still serves + # force-push-orphaned objects when fetched by SHA. If it is truly gone, clear + # BASE and let FIX 5 fail closed on the whole tree. + git fetch --quiet origin "$BASE" 2>/dev/null || BASE="" fi # FIX 5 — fail CLOSED. This previously fell back to `BASE=HEAD`, and `--changed HEAD` # diffs HEAD against itself: an empty diff, zero files scanned, job green. With no From 8124645f2bfbd6e6c4a5a5e9ce58ec9d0878cac6 Mon Sep 17 00:00:00 2001 From: yakimoto Date: Thu, 6 Aug 2026 00:24:30 +0000 Subject: [PATCH 6/7] style(ci): punctuation cleanup in the diff-base recovery comment Co-authored-by: Codesmith --- .github/workflows/governance-enforce.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/governance-enforce.yml b/.github/workflows/governance-enforce.yml index 724d8e0..a14187c 100644 --- a/.github/workflows/governance-enforce.yml +++ b/.github/workflows/governance-enforce.yml @@ -80,7 +80,7 @@ jobs: # A base can be PRESENT and still missing locally: a force-push leaves # `github.event.before` pointing at a commit this checkout no longer contains. # Narrowing to HEAD~1 here would leave every earlier commit of the force-push - # unscanned, so first try to recover the real base — GitHub still serves + # unscanned, so first try to recover the real base: GitHub still serves # force-push-orphaned objects when fetched by SHA. If it is truly gone, clear # BASE and let FIX 5 fail closed on the whole tree. git fetch --quiet origin "$BASE" 2>/dev/null || BASE="" From 6cf0c3748f1d9a596f26b87c69887cbd086db33d Mon Sep 17 00:00:00 2001 From: yakimoto <66892052+yakimoto@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:29:22 -0400 Subject: [PATCH 7/7] =?UTF-8?q?fix(ci):=20the=20empty-tree=20fallback,=20n?= =?UTF-8?q?ot=20HEAD~1=20=E2=80=94=20a=20partial=20scan=20is=20still=20a?= =?UTF-8?q?=20false=20pass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correction to the previous commit on this branch. Refs wave-av/claude-workstation#1747. That commit replaced the fail-open `BASE=HEAD` with a fallback to `HEAD~1`. `HEAD~1` is also wrong: it scans exactly ONE commit, so a five-commit push whose base is indeterminate (branch creation, force-push, unreachable `github.event.before`) examines the last commit and reports a confident pass on the other four. A narrowed scan reported as a full pass is the same defect in a quieter costume. Receipt — scratch repo, five-commit push, violation planted in commit 1: HEAD~1 base -> OK[enforce]: 1 changed file(s) scanned -> PASS (never saw it) empty-tree base -> 5 changed file(s) scanned -> BLOCK[enforce]: no-hardcoded-paths, exit 1 Now: with no resolvable base of any kind, diff against git's empty-tree object so every tracked file reads as added and the whole repo is scanned. Loud, never partial, never empty. Credit: wave-av/wave-rig's copy on main already had this right, with the reasoning in a comment ("HEAD~1 would skip earlier commits in a multi-commit push and let a violation through"). The fan-out copied the broken shape from elsewhere and I did not check the one repo that had already solved it. Also from wave-rig: `merge_group` is now a declared trigger and `github.event.merge_group. base_sha` joins the base chain. None of these repos runs a merge queue today, so the trigger is inert — but a required check that never reports on an event the repo actually uses is a permanent deadlock, and this closes that in advance rather than after someone hits it. --- .github/workflows/governance-enforce.yml | 48 ++++++++++++++---------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/.github/workflows/governance-enforce.yml b/.github/workflows/governance-enforce.yml index a14187c..98dee26 100644 --- a/.github/workflows/governance-enforce.yml +++ b/.github/workflows/governance-enforce.yml @@ -20,6 +20,10 @@ name: governance-enforce on: pull_request: + # A required check that never reports on an event the repo actually uses is a permanent + # deadlock, not a stricter gate. None of these repos runs a merge queue today; declaring + # `merge_group` costs nothing until one does, and closes that hole in advance. + merge_group: push: branches: [main, master] @@ -67,33 +71,37 @@ jobs: - name: A_BLOCK enforce (secrets + hardcoded paths on the diff) env: PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + MERGE_BASE_SHA: ${{ github.event.merge_group.base_sha }} PUSH_BEFORE_SHA: ${{ github.event.before }} run: | set -euo pipefail ENFORCE="$RUNNER_TEMP/gov/node_modules/@wave-av/governance/bin/enforce.mjs" BASE="${PR_BASE_SHA:-}" + [ -n "$BASE" ] || BASE="${MERGE_BASE_SHA:-}" [ -n "$BASE" ] || BASE="${PUSH_BEFORE_SHA:-}" - if [ "$BASE" = "0000000000000000000000000000000000000000" ]; then - # Branch creation: no before-SHA exists. Diff the previous commit if there is one. - BASE="$(git rev-parse --verify --quiet 'HEAD~1' || true)" - elif [ -n "$BASE" ] && ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then - # A base can be PRESENT and still missing locally: a force-push leaves - # `github.event.before` pointing at a commit this checkout no longer contains. - # Narrowing to HEAD~1 here would leave every earlier commit of the force-push - # unscanned, so first try to recover the real base: GitHub still serves - # force-push-orphaned objects when fetched by SHA. If it is truly gone, clear - # BASE and let FIX 5 fail closed on the whole tree. - git fetch --quiet origin "$BASE" 2>/dev/null || BASE="" - fi - # FIX 5 — fail CLOSED. This previously fell back to `BASE=HEAD`, and `--changed HEAD` - # diffs HEAD against itself: an empty diff, zero files scanned, job green. With no - # resolvable base, diff against the EMPTY TREE so every tracked file reads as added and - # the whole repo is scanned. (`--all` also exists in 0.4.6 and would do most of this, but - # it is documented as NOT covering the diff-scoped over-grant detectors. Routing through - # the diff path with an empty base keeps every detector in play.) - if [ -z "$BASE" ]; then + # FIX 5 — fail CLOSED, and do not settle for a PARTIAL range either. + # + # This previously fell back to `BASE=HEAD`, and `--changed HEAD` diffs HEAD against + # itself: an empty diff, zero files scanned, job green. + # + # `HEAD~1` is the obvious replacement and is ALSO wrong — it scans exactly one commit, + # so a five-commit push whose base is indeterminate would examine the last one and + # report a confident pass on the other four. A narrowed scan reported as a full pass is + # the same defect in a quieter costume. (This is wave-av/wave-rig's reasoning, already + # correct on its main; the fan-out copied the broken shape from elsewhere.) + # + # A base can also be PRESENT and still unusable: a force-push leaves + # `github.event.before` pointing at a commit this checkout no longer contains. + # + # So: no resolvable base of any kind → diff against the EMPTY TREE, which makes every + # tracked file read as added and scans the whole repo. Loud, never partial, never empty. + # (`--all` also exists in 0.4.6 and would do most of this, but it is documented as NOT + # covering the diff-scoped over-grant detectors. Routing through the diff path with an + # empty base keeps every detector in play.) + if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ] \ + || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then BASE="$(git hash-object -t tree /dev/null)" - echo "::notice::no diff base resolved (root commit or unreachable before-sha) — scanning the whole tree against the empty tree" + echo "::warning::indeterminate diff base (root commit, branch creation, or unreachable before-sha) — scanning the full tree against the empty-tree object so no commit is skipped" fi echo "diffing against $BASE" exec node "$ENFORCE" --changed "$BASE"