Skip to content

ci: vendor governance-enforce — this repo was never in the A_BLOCK ruleset - #54

Open
yakimoto wants to merge 7 commits into
mainfrom
ci/vendor-governance-enforce
Open

ci: vendor governance-enforce — this repo was never in the A_BLOCK ruleset#54
yakimoto wants to merge 7 commits into
mainfrom
ci/vendor-governance-enforce

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Adds the governance-enforce A_BLOCK gate (secrets / hardcoded-paths, diff-scoped) to this repo. Part of claude-workstation#1624 E4 T4.9a, following the wave-av/cli#20 pilot.

Why this repo had no secrets scan

The org ruleset governance-a-block-enforce (17901847) requires an enforce check across the fleet. Its scope is an explicit include list of 112 hand-maintained repository names — and every one of them matches wave-*.

The 16 public repos absent from that list are exactly the 16 not named wave-*: .github, adk, api-spec, cli, companion-module-wave, create-wave-app, crest-console, dispatch-edge, examples, mcp-server, obs-wave-plugin, sdk, sdk-python, sdks, vmix-wave-integration, workflow-sdk.

Read the intersection: the repos that publish our npm packages are precisely the repos running with no A_BLOCK secrets scan. Nobody excluded them. A naming convention silently became a security boundary, and it drew the line in the worst possible place.

Why the workflow lands before the ruleset entry

Adding a repo to a required_status_checks ruleset before it emits that check is a permanent deadlock — a required check that never reports can never go green, and every PR on the repo becomes unmergeable. So the order is: vendor the workflow, observe it green, then extend the list. Doing it the intuitive way round would have bricked all sixteen.

This PR is also its own liveness drill. The workflow triggers on pull_request, so it runs on the PR that adds it. If enforce reports green here, the vendored shape works in this repo. If it does not, nothing was required and nothing is blocked — which is the point of this ordering.

Proven before fan-out, not assumed

@wave-av/governance is an internal-visibility package owned by claude-workstation, so whether a public repo's GITHUB_TOKEN can read it was the one real assumption. Rather than fan out on the inference, it was piloted on a single repo first:

wave-av/cli#20 — workflow run 31011943790, conclusion success.

That is the receipt this PR rides on. The shape is copied verbatim from wave-av/wave-moq-edge (public, 12/12 green), which matters because auto-approve.yml fails silently on every public repo — it calls a reusable workflow in the private wave-foundation, and a public repo cannot do that (parse-time failure, zero jobs, no annotation). This workflow calls nothing cross-repo, so that trap does not apply.

Security properties, unchanged from the source:

  • actions SHA-pinned (actions/checkout@df4cb1c, actions/setup-node@48b55a0)
  • persist-credentials: false on checkout
  • least privilege: contents: read + packages: read
  • isolated install into RUNNER_TEMP, --no-save, so nothing touches this repo's dependency tree
  • the .npmrc is written with a literal ${NODE_AUTH_TOKEN} (single-quoted printf) which npm expands at run time — no secret value is ever written to disk or a log
  • both ${{ }} inputs (base.sha, event.before) are routed through env: and read as "$VAR", never interpolated into the script body

Diff-scoped by design: it blocks new violations without failing on legacy debt.

Refs wave-av/claude-workstation#1624.


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.


Note

Low Risk
CI-only addition with read-only permissions and an isolated package install; no application runtime or merge gate until the ruleset is updated after a green run.

Overview
Adds .github/workflows/governance-enforce.yml, bringing this repo under the same diff-scoped A_BLOCK gate (secrets in git, Doppler sourcing, hardcoded paths) that org ruleset governance-a-block-enforce expects via an enforce job check.

The workflow runs on PRs and pushes to main/master: full-history checkout, isolated install of @wave-av/governance@0.4.6 with --ignore-scripts, then enforce.mjs --changed <base>. Hardening baked into the vendored copy includes step-scoped registry token, push concurrency that does not cancel in-progress (so push diffs are not skipped), validation of the diff base with fail-closed fallback to the empty tree when no base resolves (instead of a no-op HEAD diff).

The workflow is intentionally landed before adding this repo to the ruleset so a required check that never reports cannot deadlock merges.

Reviewed by Cursor Bugbot for commit 4c24433. Configure here.

Review in cubic

Note

Add governance enforcement workflow to scan diffs on PRs and pushes to main

Adds governance-enforce.yml, which runs @wave-av/governance@0.4.6 on pull requests, merge groups, and pushes to main/master. The enforcer scans only changed files using a computed diff base; if no valid base is found, it falls back to the empty tree and scans the full repository. Push and merge group runs are not cancelable to ensure no committed ranges are skipped.

Macroscope summarized 4b9480e.

@yakimoto yakimoto added the rr:skip-cubic RF.P1 reviewer routing (#1039) label Aug 5, 2026
@cursor

cursor Bot commented Aug 5, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_59a3cb6c-4dcb-4439-ac3f-1077b5e30083)

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 44 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 00cf66c9-2a3b-4e9f-8c3a-db2cf14e25c8

📥 Commits

Reviewing files that changed from the base of the PR and between bae994f and 4b9480e.

📒 Files selected for processing (1)
  • .github/workflows/governance-enforce.yml

Comment @coderabbitai help to get the list of available commands.

@macroscopeapp

macroscopeapp Bot commented Aug 5, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

Unable to check for correctness in 4b9480e. This PR adds a CI workflow file that the author does not own (owned by wave-av/sdk-owners), and there is an unresolved comment requesting verification that the empty-tree fallback path works correctly with the governance package. The designated owners should review this addition to their workflow infrastructure.

You can customize Macroscope's approvability policy. Learn more.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

CI: add governance-enforce A_BLOCK diff gate (secrets + hardcoded paths)

⚙️ Configuration changes ✨ Enhancement 🕐 10-20 Minutes

Grey Divider

AI Description

• Add governance-enforce workflow to run A_BLOCK checks on PR diffs.
• Fetch @wave-av/governance from GitHub Packages with least-privilege permissions.
• Emit the enforce status check required by the org ruleset rollout.
Diagram

graph TD
  PR["Pull request / Push"] --> WF["Workflow: governance-enforce"] --> RUN["GitHub Actions runner"] --> NPM[("GitHub Packages")]
  NPM --> GOV["@wave-av/governance enforce.mjs"] --> CHECK["Status check: enforce"] --> RULE["Org ruleset: A_BLOCK"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Extend existing repo-local scanners (gitleaks/content-policy)
  • ➕ No dependency on GitHub Packages access to internal npm package
  • ➕ Keeps all enforcement logic fully vendored and reviewable in-repo
  • ➖ Harder to standardize across the org and align with a single required check name
  • ➖ Likely scans the whole tree rather than diff-scoped policy (more noise/legacy failures)
2. Use a reusable workflow from a central repo
  • ➕ Single source of truth; updates roll out without copying files
  • ➕ Less per-repo workflow maintenance
  • ➖ Public repos cannot call private reusable workflows (risk of parse-time failures/zero jobs)
  • ➖ Creates a potential central-control risk if the workflow changes out-of-band
3. Rely on GitHub Advanced Security secret scanning / code scanning
  • ➕ Native platform integration; less custom scripting
  • ➕ Can provide UI annotations and centralized reporting
  • ➖ May not cover org-specific rules like hardcoded path policies
  • ➖ May require licensing/config and still not provide the exact enforce check the ruleset expects

Recommendation: Keep the current vendored governance-enforce workflow. It matches the org-required enforce check name, remains compatible with public-repo constraints (no cross-repo reusable workflow calls), and enforces diff-scoped rules to avoid failing on legacy debt. Re-evaluate central reusable workflows only if GitHub’s public/private reuse constraints are no longer a blocker.

Files changed (1) +59 / -0

Other (1) +59 / -0
governance-enforce.ymlAdd A_BLOCK governance enforcement workflow (diff-scoped) +59/-0

Add A_BLOCK governance enforcement workflow (diff-scoped)

• Introduces a new GitHub Actions workflow that runs on pull requests and pushes to main/master. The job installs '@wave-av/governance' from GitHub Packages using 'GITHUB_TOKEN' and executes its 'enforce.mjs' against the computed base SHA to report the 'enforce' status check.

.github/workflows/governance-enforce.yml

devin-ai-integration[bot]

This comment was marked as resolved.

@qodo-code-review

qodo-code-review Bot commented Aug 5, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Lifecycle scripts enabled ✓ Resolved 🐞 Bug ⛨ Security
Description
The workflow installs @wave-av/governance via npm install without disabling lifecycle scripts,
so any install-time scripts in that package (or its dependencies) can execute while registry
credentials are present. This regresses from the repo’s existing CI stance of ignore-scripts=true,
increasing supply-chain blast radius for this required gate.
Code

.github/workflows/governance-enforce.yml[R46-48]

+          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
Evidence
The new workflow sets a token for npm auth and performs npm install without any ignore-scripts
configuration, while the repo’s CI config explicitly sets ignore-scripts=true and is applied in
other workflows.

.github/workflows/governance-enforce.yml[34-48]
.npmrc.ci[1-1]
.github/workflows/publish-npm.yml[40-43]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The governance-enforce workflow runs `npm install` without disabling lifecycle scripts, which allows arbitrary code execution during install while registry auth is available.

### Issue Context
This repo already carries a CI npm config that disables scripts (`.npmrc.ci`), and other workflows explicitly apply it.

### Fix Focus Areas
- .github/workflows/governance-enforce.yml[34-48]

### Suggested change
In the `fetch governance enforcer (isolated install)` step, enforce `ignore-scripts` for this isolated install by **either**:
- appending the repo CI config into the temp `.npmrc`:
 - `cat "$GITHUB_WORKSPACE/.npmrc.ci" >> .npmrc`
- or adding the flag:
 - `npm install ... --ignore-scripts`

Keep the install otherwise isolated as it is today.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Auth .npmrc not cleaned ✓ Resolved 🐞 Bug ⛨ Security
Description
The workflow writes an .npmrc containing an auth token into $RUNNER_TEMP/gov and leaves it there
for the remainder of the job. This unnecessarily extends the window during which subsequent commands
(including the installed tool) can read registry credentials from disk.
Code

.github/workflows/governance-enforce.yml[47]

+          printf '@wave-av:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}\n' > .npmrc
Evidence
The workflow writes a token-containing .npmrc but has no cleanup step; later steps run in the same
job after the file is created.

.github/workflows/governance-enforce.yml[44-59]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The token-bearing `.npmrc` is left on disk after the install step, expanding credential exposure beyond what’s needed.

### Issue Context
The `.npmrc` is only required to download `@wave-av/governance`. After install, the enforcer can run without leaving the token file lying around.

### Fix Focus Areas
- .github/workflows/governance-enforce.yml[44-59]

### Suggested change
After `npm install`, delete the `.npmrc` (and optionally scope the token env to the install step only), e.g.:
- add `rm -f .npmrc` after install
- optionally use a `trap 'rm -f .npmrc' EXIT` to ensure cleanup on failure
- move `NODE_AUTH_TOKEN` from job-level `env:` to only the install step `env:`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Enforcer version not pinned ✓ Resolved 🐞 Bug ☼ Reliability
Description
The workflow installs @wave-av/governance@^0.4.4, which permits automatically picking up newer
0.4.x releases without a PR. That can change required-gate behavior unexpectedly across runs,
reducing determinism of this enforcement check.
Code

.github/workflows/governance-enforce.yml[48]

+          npm install @wave-av/governance@^0.4.4 --no-save --no-audit --no-fund
Evidence
The workflow explicitly installs the enforcer with a caret range, allowing the installed version to
change over time without changes in this repo.

.github/workflows/governance-enforce.yml[44-49]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The workflow uses a semver range (`^0.4.4`) for a required enforcement tool, so behavior may change without any repo change.

### Issue Context
For `0.x` packages, `^0.4.4` can float to any version `<0.5.0` (i.e., newer `0.4.*`), which is still an unreviewed behavior change for a required gate.

### Fix Focus Areas
- .github/workflows/governance-enforce.yml[48-48]

### Suggested change
Change:
- `npm install @wave-av/governance@^0.4.4 ...`
To an exact version:
- `npm install @wave-av/governance@0.4.4 ...`

Then bump intentionally via PR when you want gate changes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
Review mode: ⚖️ Balanced: This adds a security-sensitive CI governance gate with package installation, token permissions, diff-base selection, and workflow-trigger behavior; despite one localized file, a careful full review is warranted.

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

qodo-code-review[bot]

This comment was marked as resolved.

@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

✅ Merged (0) · ☑ Fixed (0)

Process

  • No fixes were applied (no_fixes_applied)

…ken, npmrc cleanup)

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…ll failure

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

yakimoto and others added 2 commits August 5, 2026 19:27
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…othing

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.
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_c8d09276-1324-439e-8fab-346d8f918aee)

macroscopeapp[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +88 to +93
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"
exec node "$ENFORCE" --changed "$BASE"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Pinned version differs from PR description; verify 0.4.6 accepts a tree object as --changed base

The workflow pins @wave-av/governance@0.4.6 while the PR description states ^0.4.4 — the inline FIX 3 comment explains the change, so this is intentional drift in the description, not the code. The one thing worth verifying externally: the fail-closed path passes the empty tree hash (git hash-object -t tree /dev/null) as the --changed base. That works with a two-dot git diff <tree> <commit>, but if enforce.mjs internally uses three-dot (BASE...HEAD) or git merge-base, a tree object is rejected and the step would error. Since this path is the deliberate fail-closed branch, an error is loud rather than silently green, but it would produce a confusing failure rather than a whole-tree scan. Worth confirming against the 0.4.6 implementation.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified against the 0.4.6 source: both enforce.mjs and enforce-ramp.mjs build the diff via changedArgs(), which uses the two-argument form git diff <base> HEAD (explicitly documented as accepting trees, chosen precisely because A...B rejects the empty-tree object this fallback supplies). The empty-tree base yields a genuine whole-tree scan, so no change is needed.

yakimoto and others added 2 commits August 6, 2026 00:19
…reachable

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…l a false pass

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rr:skip-cubic RF.P1 reviewer routing (#1039)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant