Skip to content

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

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

ci: vendor governance-enforce — this repo was never in the A_BLOCK ruleset#20
yakimoto wants to merge 5 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 change with hardened install and fail-closed diff logic; no application runtime or merge rules until the org ruleset is updated after a green run.

Overview
Adds governance-enforce CI so this repo gets an A_BLOCK diff scan (secrets and hardcoded paths via @wave-av/governance) before it is added to the org governance-a-block-enforce ruleset.

The workflow runs on PRs and pushes to main/master, installs @wave-av/governance@0.4.6 into an isolated temp directory (scoped registry token, --ignore-scripts, SHA-pinned actions), and runs enforce.mjs --changed against the PR base or push range. Concurrency cancels in-progress PR runs but not push runs, so cancelled push jobs do not leave commits unscanned. If no diff base resolves, it fails closed by diffing against the empty tree instead of reporting green with zero files scanned.

CHANGELOG records the new workflow under [Unreleased].

Reviewed by Cursor Bugbot for commit 90c2969. Configure here.

Note

Add governance-enforce CI workflow to scan for secrets and hardcoded paths

  • Adds governance-enforce.yml that runs @wave-av/governance@0.4.6 on pull requests, merge groups, and pushes to main/master.
  • The tool is installed with --ignore-scripts and a temporary .npmrc scoped to that step; the diff base is resolved from the PR/merge-group/push SHA, falling back to the empty-tree (full-repo scan) if no valid base is found.
  • Push runs are not canceled by the concurrency group, ensuring all pushed commits are scanned; only PR runs are superseded.
  • This is vendored ahead of the repo being added to the org-level A_BLOCK ruleset.

Macroscope summarized 9bdfc9f.

Review in cubic

@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_b9b25fb3-4074-47de-a626-245148ac95f8)

@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: fd2f863a-f7da-4dc3-be69-072076e944c4

📥 Commits

Reviewing files that changed from the base of the PR and between 3f87432 and 9bdfc9f.

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

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add governance-enforce CI gate for diff-scoped secrets/hardcoded-path checks

⚙️ Configuration changes ✨ Enhancement 🕐 10-20 Minutes

Grey Divider

AI Description

• Add a required enforce status check workflow for A_BLOCK governance scanning.
• Run diff-scoped secrets and hardcoded-path detection on PRs and main/master pushes.
• Install and execute @wave-av/governance from GitHub Packages with least-privilege token access.
Diagram

graph TD
  A["GitHub Events"] --> B["governance-enforce workflow"] --> C["Job: enforce"] --> D["Checkout (full history)"] --> E["Setup Node 22"] --> F["Install @wave-av/governance"] --> G["Run enforce.mjs on diff"]

  subgraph Legend
    direction LR
    _evt(["Event"]) ~~~ _wf["Workflow"] ~~~ _job["Job"] ~~~ _step["Step"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Central reusable workflow (org-level)
  • ➕ Single place to patch/pin action SHAs and enforcement logic
  • ➕ Consistent behavior across repositories
  • ➖ Public repos may be unable to call private reusable workflows (parse-time failure)
  • ➖ Cross-repo dependency can create availability/permission coupling
2. Commit an internal CLI wrapper into the repo
  • ➕ No runtime install from GitHub Packages; fewer auth/package-read concerns
  • ➕ Easier to version-lock behavior to the repo
  • ➖ Duplicates logic across repos or requires ongoing sync
  • ➖ Harder to ensure fleet-wide policy updates land quickly
3. Run full-repo scan instead of diff-scoped
  • ➕ Catches legacy violations immediately
  • ➕ Simpler base-SHA logic
  • ➖ Likely to fail on existing debt and block merges
  • ➖ Higher runtime cost; worse developer experience during rollout

Recommendation: The PR’s vendored, diff-scoped workflow is the best fit for safe rollout: it avoids cross-repo reusable-workflow pitfalls for public repos, minimizes deadlock risk with required checks, and prevents legacy-debt breakage by focusing on new violations. Consider periodically bumping @wave-av/governance and optionally pinning to an exact version once rollout stabilizes.

Files changed (1) +59 / -0

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

Add A_BLOCK governance-enforce workflow (diff-scoped)

• Introduces a new GitHub Actions workflow that runs on pull requests and pushes to main/master to produce an 'enforce' status check. It installs '@wave-av/governance' from GitHub Packages in an isolated temp directory and runs 'enforce.mjs' against the computed base SHA to scan only the changed diff for secrets and hardcoded-path violations.

.github/workflows/governance-enforce.yml

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 5, 2026
@macroscopeapp

macroscopeapp Bot commented Aug 5, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved 9bdfc9f

Adds CI security scanning workflow. Author owns both changed files. The open review comments appear to misread the implementation (code actually falls back to empty-tree scanning, not HEAD~1) or raise theoretical concerns that fail safely rather than silently. CI-only changes with no production runtime impact.

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

devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
@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


Remediation recommended

1. Unpinned enforcer version ✓ Resolved 🐞 Bug ⛨ Security
Description
The workflow installs @wave-av/governance using a caret range (^0.4.4), so the exact code run by
this required security gate can change without any PR in this repo, leading to non-reproducible
enforcement and unexpected gate behavior changes.
Code

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

+          npm install @wave-av/governance@^0.4.4 --no-save --no-audit --no-fund
Evidence
The workflow currently installs a floating semver range, making each run potentially execute
different code. The repo’s existing security gate explicitly emphasizes determinism and avoiding
out-of-band reprogramming, which this pattern violates.

.github/workflows/governance-enforce.yml[44-49]
.github/workflows/public-repo-guard.yml[9-14]

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 installs `@wave-av/governance@^0.4.4`, which allows newer releases to be pulled automatically. This makes the enforcement gate non-deterministic and allows gate behavior to change out-of-band.

## Issue Context
This repo already documents a preference for deterministic, reviewable security gates (see `public-repo-guard.yml` comments). This gate is similarly security-relevant and likely to become a required check.

## Fix Focus Areas
- .github/workflows/governance-enforce.yml[44-49]
- .github/workflows/public-repo-guard.yml[9-14]

## Suggested fix
- Replace `@wave-av/governance@^0.4.4` with an exact pinned version (e.g., `@wave-av/governance@0.4.4`).
- If you need stronger immutability, add an integrity/verification mechanism (e.g., lockfile with integrity, or a vendored/verified artifact) appropriate to your org’s standards.

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



Informational

2. Token scoped too broadly ✓ Resolved 🐞 Bug ⛨ Security
Description
NODE_AUTH_TOKEN is set at the job level, making the repo’s GITHUB_TOKEN available to every step in
the job even though only the isolated npm-install step needs it; this increases exposure to
accidental logging or future added tooling.
Code

.github/workflows/governance-enforce.yml[R34-36]

+    env:
+      NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+    steps:
Evidence
The token is currently injected at the job scope, but only the isolated npm install uses it via the
generated .npmrc, so scoping it to that step reduces unnecessary credential propagation.

.github/workflows/governance-enforce.yml[34-36]
.github/workflows/governance-enforce.yml[44-48]

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

## Issue description
`NODE_AUTH_TOKEN` is configured as a job-level environment variable, so it is inherited by all steps. Only the `npm install` step needs it to authenticate to GitHub Packages.

## Issue Context
While the workflow permissions are read-only and GitHub masks the token in logs, scoping credentials to the minimum necessary steps is better defense-in-depth and reduces accidental exposure as the workflow evolves.

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

## Suggested fix
Move `NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}` from `jobs.enforce.env` to the `fetch governance enforcer (isolated install)` step’s `env` block so only that step receives the token.

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


3. Weak base SHA fallback ✓ Resolved 🐞 Bug ≡ Correctness
Description
When the event base SHA is missing or all-zero (e.g., first push/branch creation), the workflow
falls back to HEAD~1 (or HEAD), which can under-scan changes by not representing the real prior ref
state.
Code

.github/workflows/governance-enforce.yml[R55-57]

+          if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ]; then
+            BASE=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD)
+          fi
Evidence
The script explicitly treats the all-zero SHA as unknown and then uses HEAD~1/HEAD as a
substitute, which is not necessarily the correct prior state of the ref for edge cases like first
push.

.github/workflows/governance-enforce.yml[54-57]

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

## Issue description
If `BASE` is empty or `000…000`, the workflow falls back to `HEAD~1` (or `HEAD`). In scenarios where the true prior ref SHA is unavailable (commonly first push/branch creation), this may produce an incomplete diff baseline.

## Issue Context
This workflow is intended to enforce diff-scoped checks. When the baseline is unknown, the safest behavior is typically to either compute a more appropriate baseline or run a broader scan rather than potentially missing new violations.

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

## Suggested fix
Change the fallback behavior for unknown base to a fail-closed strategy, such as:
- resolving a baseline against the repository default branch (if applicable for your event), or
- running the enforcement tool in a mode that scans the whole tree / does not rely on `--changed` when `BASE` cannot be determined.

ⓘ 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 enforcement workflow with package authentication, diff-base selection, and a future required-check contract; one hunk is localized but its behavior and liveness merit a complete review.

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

No findings are within the configured fix scope. To change which findings are fixed, adjust the setting on your Qodo configuration page.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 5, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

…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_d05dca0d-325e-40b6-aea2-b5b48baae926)

# 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High workflows/governance-enforce.yml:79

When github.event.before points to an unreachable commit (e.g. after a force-push), the fallback resolves BASE to HEAD~1, so --changed scans only the final commit. Any secret or hardcoded path introduced in earlier commits of the force-pushed range is silently skipped while the job still reports success. Instead of narrowing an unresolved base to a single parent, this fallback should fail closed to the empty tree (the same path used when no base is resolved at all) so the entire tree is scanned.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @.github/workflows/governance-enforce.yml around line 79:

When `github.event.before` points to an unreachable commit (e.g. after a force-push), the fallback resolves `BASE` to `HEAD~1`, so `--changed` scans only the final commit. Any secret or hardcoded path introduced in earlier commits of the force-pushed range is silently skipped while the job still reports success. Instead of narrowing an unresolved base to a single parent, this fallback should fail closed to the empty tree (the same path used when no base is resolved at all) so the entire tree is scanned.

Evidence trail:
.github/workflows/governance-enforce.yml:67-93 @ 90c2969

devin-ai-integration[bot]

This comment was marked as resolved.

…ble, not HEAD~1

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@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 +91 to +94
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Empty-tree base assumes the enforcer uses a two-dot diff

The fail-closed fallback substitutes the empty tree object id as the diff base. This only produces "every tracked file reads as added" if @wave-av/governance internally runs git diff <base> HEAD (two-dot). If it runs git diff <base>...HEAD or git merge-base, the empty tree is not a commit and git errors out. Per the comment at .github/workflows/governance-enforce.yml:61-63, 0.4.6 fails closed on git errors, so the worst case is a red job rather than a false PASS — but it would be a hard, unexplained failure on force-pushes and root commits. Worth confirming the enforcer's diff invocation against 0.4.6.

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.

Confirmed against the @wave-av/governance 0.4.6 source: enforce.mjs --changed uses the two-argument form git diff <base> HEAD (governance/lib/git-files.mjs:104), which git accepts for tree objects, and a regression test explicitly pins that the empty-tree base works ("changedArgs uses two-dot, which accepts a TREE"). The three-dot/merge-base failure mode cannot occur.

…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