Skip to content

ci: block only on confirmed scanner findings - #222

Merged
CrimsonSoul merged 11 commits into
testfrom
codex/finding-only-scanner-gates
Jul 31, 2026
Merged

ci: block only on confirmed scanner findings#222
CrimsonSoul merged 11 commits into
testfrom
codex/finding-only-scanner-gates

Conversation

@CrimsonSoul

@CrimsonSoul CrimsonSoul commented Jul 31, 2026

Copy link
Copy Markdown
Owner

What changed

  • add shared clean, finding, unavailable, and configuration outcomes for Sonar and Snyk
  • keep confirmed findings and configuration failures merge-blocking
  • convert documented rate limits, service outages, bounded timeouts, and Snyk temporary exits 69/75 into visible warning-success results
  • make CodeRabbit findings block through Request Changes and unresolved conversations while skipping drafts and pausing rapid incremental reviews
  • preserve scanner-independent Windows packaging and stable required check names

Why

Fast pushes should not lose the newest Windows build merely because a free external service is rate-limited or temporarily unavailable. A green unavailable result explicitly says that no security decision was produced; release revisions still require real clean scanner decisions.

Validation

  • npm run typecheck
  • npm run lint
  • npm run format:check
  • npm test (5,624 passed; one existing skip)
  • npm run build
  • npm audit --audit-level=high --omit=dev (0 vulnerabilities)
  • focused scanner/workflow/CodeRabbit/Windows contract suite (104 passed)
  • independent read-only code review: ready to merge, no critical or important findings

Summary by CodeRabbit

  • Security & CI

    • Added streamlined SonarQube and Snyk security gates with bounded execution and clearer pass, finding, unavailable, and configuration outcomes.
    • Security checks now provide safer error reporting, timeout handling, and secret redaction.
    • Windows packaging remains independent of scanner availability.
  • Documentation

    • Updated security and development guidance for scanner requirements, outcomes, pull-request reviews, and merge checks.
    • Added implementation and design documentation for the new security-gate process.
  • Quality

    • Added comprehensive automated coverage for scanner behavior, workflow contracts, timeouts, and error handling.

@CrimsonSoul
CrimsonSoul marked this pull request as ready for review July 31, 2026 21:11
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@CrimsonSoul, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3480e8ab-bd25-4296-a5fd-196e760273e3

📥 Commits

Reviewing files that changed from the base of the PR and between 3befa0e and 295f1e0.

📒 Files selected for processing (13)
  • docs/DEVELOPMENT.md
  • docs/SECURITY.md
  • docs/superpowers/specs/2026-07-31-finding-only-scanner-gates-design.md
  • scripts/coderabbit-config-contract.test.mjs
  • scripts/run-snyk-ci.mjs
  • scripts/run-snyk-ci.test.mjs
  • scripts/run-sonar-ci.mjs
  • scripts/run-sonar-ci.test.mjs
  • scripts/scanner-gate-policy.mjs
  • scripts/scanner-gate-policy.test.mjs
  • scripts/security-workflow-contract.test.mjs
  • scripts/sonar-open-findings.test.mjs
  • scripts/sonar-reviewed-issues.test.mjs
📝 Walkthrough

Walkthrough

The pull request adds finding-only Sonar and Snyk CI gates with shared outcome classification, bounded execution, timeout handling, sanitized reporting, workflow contracts, CodeRabbit settings, tests, and security documentation.

Changes

Scanner gate implementation

Layer / File(s) Summary
Shared scanner policy
scripts/scanner-gate-policy.mjs, scripts/scanner-gate-policy.test.mjs, docs/superpowers/specs/..., docs/superpowers/plans/...
Defines clean, finding, unavailable, and configuration outcomes. Adds bounded commands, timeout handling, process-tree termination, output limits, redaction, and GitHub warning reports.
Sonar CI gate
scripts/run-sonar-ci.mjs, scripts/sonar-*.mjs, scripts/*sonar*.test.mjs
Adds validated Sonar orchestration for upload, analysis, reviewed-issue reconciliation, open findings, and quality gates. Adds shared deadlines and classified errors.
Snyk CI gate
scripts/run-snyk-ci.mjs, scripts/run-snyk-ci.test.mjs, package.json, .github/workflows/security.yml
Runs Snyk Open Source and Code scans sequentially. Runs monitoring on clean test branch pushes. Applies validation, deadlines, outcome handling, and sanitized reporting.
Workflow and review contracts
.coderabbit.yaml, .github/workflows/security.yml, scripts/*contract.test.mjs, docs/DEVELOPMENT.md, docs/SECURITY.md, docs/superpowers/plans/...
Adds scanner job timeouts and unified commands. Updates CodeRabbit review settings, workflow assertions, scanner rules, merge requirements, Windows independence, and acceptance criteria.

Estimated code review effort: 5 (Critical) | ~90+ minutes

Sequence Diagram(s)

sequenceDiagram
  participant PullRequest as Pull request
  participant Workflow as GitHub Actions
  participant SonarGate as runSonarCi
  participant SnykGate as runSnykCi
  participant ScannerPolicy as scanner-gate-policy
  PullRequest->>Workflow: start security jobs
  Workflow->>SonarGate: run security:sonar:ci
  SonarGate->>ScannerPolicy: execute bounded Sonar command
  ScannerPolicy-->>SonarGate: classify and sanitize result
  Workflow->>SnykGate: run security:snyk:ci
  SnykGate->>ScannerPolicy: execute bounded Snyk phases
  ScannerPolicy-->>SnykGate: classify and sanitize result
  SonarGate-->>Workflow: gate outcome
  SnykGate-->>Workflow: gate outcome
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main CI change to block merges only for confirmed scanner findings, while omitting secondary configuration-failure details.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/finding-only-scanner-gates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@CrimsonSoul
CrimsonSoul enabled auto-merge (squash) July 31, 2026 21:18
@CrimsonSoul
CrimsonSoul disabled auto-merge July 31, 2026 21:20

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
docs/superpowers/specs/2026-07-31-finding-only-scanner-gates-design.md (1)

57-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fix the outcome-count mismatch: this doc calls it a "three-state policy" but describes four outcomes.

Line 57 states "an explicit three-state policy" and lists Clean, Finding, Unavailable. Lines 66-69 then describe configuration/contract failures as a distinct, separately blocking category — a fourth outcome, not a variant of the three listed.

This contradicts other files in the same PR:

  • The implementation plan states: "four explicit states: clean, finding, unavailable, and configuration failure."
  • scripts/scanner-gate-policy.mjs defines SCANNER_OUTCOME with four frozen values.
  • docs/SECURITY.md documents a four-row Clean/Finding/Unavailable/Configuration table.

Update the wording here to say "four-state policy" (or explicitly number Configuration as state 4) so this spec matches the plan, the code, and docs/SECURITY.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-07-31-finding-only-scanner-gates-design.md`
around lines 57 - 69, Update the policy description in the scanner-gate outcome
section to identify four states, explicitly including configuration or contract
failure as the fourth state alongside Clean, Finding, and Unavailable; keep the
existing outcome behavior and blocking semantics unchanged.
scripts/sonar-reviewed-issues.test.mjs (1)

680-697: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace source-text substring matching with a behavioral assertion.

This test reads the raw text of run-sonar-ci.mjs and locates literal substrings ("if ('branch' in scope)", 'await reconcile({', 'await readIssues') to prove that reconciliation only runs for branch scope and precedes the open-finding gate. This is fragile: a harmless refactor (renaming scope, reformatting, changing quote style) breaks the test without any real regression, and a change that reorders behavior while keeping these exact substrings intact would not be caught.

scripts/run-sonar-ci.test.mjs already proves this same guarantee behaviorally ("reconciles reviewed findings exactly once only for the test branch" via mocked reconcile/readIssues/checkGate call-order assertions). Import runSonarCi here too and assert on mocked call order instead of indexing into source text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/sonar-reviewed-issues.test.mjs` around lines 680 - 697, Replace the
source-text inspection in the Sonar CI runner test with a behavioral test using
the exported runSonarCi function. Import runSonarCi, mock reconcile, readIssues,
and checkGate, then assert reconciliation occurs exactly once only for the
test-branch scope and in the required call order before the open-finding gate,
matching the existing behavioral coverage in scripts/run-sonar-ci.test.mjs.
scripts/security-workflow-contract.test.mjs (1)

15-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Guard job before reading job.steps.

If a job key is renamed or removed, job is undefined and line 16 throws TypeError: Cannot read properties of undefined. The test then reports a type error instead of the intended contract failure.

♻️ Proposed guard
 const findStep = (job, name) => {
+  assert.ok(job?.steps, `missing workflow job for step: ${name}`);
   const step = job.steps.find((candidate) => candidate.name === name);
   assert.ok(step, `missing workflow step: ${name}`);
   return step;
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/security-workflow-contract.test.mjs` around lines 15 - 19, Update
findStep to validate that job exists before accessing job.steps, using the
existing assertion style to report the missing job as a contract failure;
preserve the current step lookup and missing-step assertion once job has been
validated.
scripts/sonar-reviewed-issues.mjs (1)

11-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider moving the shared timeout bounds and clock helper into scanner-gate-policy.mjs.

DEFAULT_REQUEST_TIMEOUT_MS, MAX_REQUEST_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, and monotonicNow are now duplicated across the Sonar and Snyk scripts. A single export keeps the bounds aligned when one value changes.

Also applies to: 41-41

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/sonar-reviewed-issues.mjs` around lines 11 - 14, Move the duplicated
timeout constants and monotonicNow helper from the Sonar and Snyk scripts into
scanner-gate-policy.mjs, export them there, and update both consumers to import
and reuse those shared exports. Remove the local duplicate definitions while
preserving existing timeout behavior.
scripts/run-snyk-ci.mjs (1)

96-100: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider moving phaseTimeout into scanner-gate-policy.mjs.

run-sonar-ci.mjs defines the same helper at lines 96-100 with an extra maximum parameter. One shared implementation keeps the deadline behavior identical for both gates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/run-snyk-ci.mjs` around lines 96 - 100, Move the shared phaseTimeout
implementation from run-snyk-ci.mjs into scanner-gate-policy.mjs, extending it
to accept the maximum timeout parameter used by run-sonar-ci.mjs. Update both
run-snyk-ci.mjs and run-sonar-ci.mjs to import and reuse this shared helper,
passing their respective command timeout limits while preserving the
aggregate-deadline behavior.
scripts/sonar-open-findings.test.mjs (1)

146-165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer behavior assertions over source-text assertions for the ordering contract.

These tests read run-sonar-ci.mjs as text and compare indexOf positions. The assertions pass even if the phases never execute, and they break on any rename or message edit. runSonarCi already accepts runCommand, waitAnalysis, reconcile, readIssues, and checkGate as injectable options. Inject fakes that push a label into an array, then assert the recorded order.

♻️ Sketch of an order-recording test
test('the Sonar CI gate runs phases in order', async () => {
  const calls = [];
  const result = await runSonarCi({
    argv: ['--branch=test'],
    env: { SONAR_TOKEN: TOKEN, SONAR_ORGANIZATION: 'org' },
    runCommand: async () => (calls.push('upload'), { code: 0, output: '' }),
    waitAnalysis: async () => calls.push('wait'),
    reconcile: async () => calls.push('reconcile'),
    readIssues: async () => (calls.push('issues'), { summary: { open: [] } }),
    checkGate: async () => calls.push('gate'),
  });
  assert.deepEqual(calls, ['upload', 'wait', 'reconcile', 'issues', 'gate']);
  assert.equal(result.outcome, SCANNER_OUTCOME.CLEAN);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/sonar-open-findings.test.mjs` around lines 146 - 165, Replace the
source-text index checks in the Sonar CI ordering test with behavioral
assertions using runSonarCi’s injectable runCommand, waitAnalysis, reconcile,
readIssues, and checkGate options. Record each fake’s invocation in order,
assert the expected phase sequence, and preserve validation of the scanner’s
non-blocking quality-gate configuration plus the clean outcome.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.coderabbit.yaml:
- Around line 2-7: Update the auto_review configuration in .coderabbit.yaml so
request_changes_workflow cannot remain blocked after fixes go unreviewed: raise
auto_pause_after_reviewed_commits above 2, or document the required manual
re-review step in docs/DEVELOPMENT.md. Preserve the existing automatic review
settings.

In `@scripts/run-snyk-ci.mjs`:
- Around line 22-31: Update the Snyk policy definitions around SCAN_POLICY and
MONITOR_POLICY so exit codes 2, 3, and missing-credential outcomes have explicit
documented policy handling consistent with the intended blocking behavior; do
not leave them to the generic UNAVAILABLE fallback. Ensure both policy outcomes
and their documentation clearly reflect how these cases are classified.

In `@scripts/scanner-gate-policy.mjs`:
- Around line 49-53: Preserve transient-pattern evidence independently of the
bounded output buffer: while processing command-output chunks, evaluate each
chunk against the configured transient pattern and retain a boolean streamed
match flag. Update classifyCommandResult to treat either the streamed flag or
policy.transientOutput matching the final bounded result as UNAVAILABLE. Add a
regression test in scanner-gate-policy.test.mjs covering transient text followed
by more than maxOutputBytes of filler and a non-zero, non-documented exit code.

In `@scripts/security-workflow-contract.test.mjs`:
- Line 24: Update the assertion for build.jobs['package-windows'].needs to use a
strict assertion that distinguishes undefined from null, ensuring the test
verifies the needs key is truly absent rather than merely unset.

---

Nitpick comments:
In `@docs/superpowers/specs/2026-07-31-finding-only-scanner-gates-design.md`:
- Around line 57-69: Update the policy description in the scanner-gate outcome
section to identify four states, explicitly including configuration or contract
failure as the fourth state alongside Clean, Finding, and Unavailable; keep the
existing outcome behavior and blocking semantics unchanged.

In `@scripts/run-snyk-ci.mjs`:
- Around line 96-100: Move the shared phaseTimeout implementation from
run-snyk-ci.mjs into scanner-gate-policy.mjs, extending it to accept the maximum
timeout parameter used by run-sonar-ci.mjs. Update both run-snyk-ci.mjs and
run-sonar-ci.mjs to import and reuse this shared helper, passing their
respective command timeout limits while preserving the aggregate-deadline
behavior.

In `@scripts/security-workflow-contract.test.mjs`:
- Around line 15-19: Update findStep to validate that job exists before
accessing job.steps, using the existing assertion style to report the missing
job as a contract failure; preserve the current step lookup and missing-step
assertion once job has been validated.

In `@scripts/sonar-open-findings.test.mjs`:
- Around line 146-165: Replace the source-text index checks in the Sonar CI
ordering test with behavioral assertions using runSonarCi’s injectable
runCommand, waitAnalysis, reconcile, readIssues, and checkGate options. Record
each fake’s invocation in order, assert the expected phase sequence, and
preserve validation of the scanner’s non-blocking quality-gate configuration
plus the clean outcome.

In `@scripts/sonar-reviewed-issues.mjs`:
- Around line 11-14: Move the duplicated timeout constants and monotonicNow
helper from the Sonar and Snyk scripts into scanner-gate-policy.mjs, export them
there, and update both consumers to import and reuse those shared exports.
Remove the local duplicate definitions while preserving existing timeout
behavior.

In `@scripts/sonar-reviewed-issues.test.mjs`:
- Around line 680-697: Replace the source-text inspection in the Sonar CI runner
test with a behavioral test using the exported runSonarCi function. Import
runSonarCi, mock reconcile, readIssues, and checkGate, then assert
reconciliation occurs exactly once only for the test-branch scope and in the
required call order before the open-finding gate, matching the existing
behavioral coverage in scripts/run-sonar-ci.test.mjs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 03498de7-1bf0-4c80-8cd5-3c2c194fcdab

📥 Commits

Reviewing files that changed from the base of the PR and between e46d654 and 3befa0e.

📒 Files selected for processing (21)
  • .coderabbit.yaml
  • .github/workflows/security.yml
  • docs/DEVELOPMENT.md
  • docs/SECURITY.md
  • docs/superpowers/plans/2026-07-31-finding-only-scanner-gates.md
  • docs/superpowers/specs/2026-07-31-finding-only-scanner-gates-design.md
  • package.json
  • scripts/coderabbit-config-contract.test.mjs
  • scripts/run-snyk-ci.mjs
  • scripts/run-snyk-ci.test.mjs
  • scripts/run-sonar-ci.mjs
  • scripts/run-sonar-ci.test.mjs
  • scripts/scanner-gate-policy.mjs
  • scripts/scanner-gate-policy.test.mjs
  • scripts/security-workflow-contract.test.mjs
  • scripts/sonar-open-findings.mjs
  • scripts/sonar-open-findings.test.mjs
  • scripts/sonar-quality-gate.mjs
  • scripts/sonar-quality-gate.test.mjs
  • scripts/sonar-reviewed-issues.mjs
  • scripts/sonar-reviewed-issues.test.mjs

Comment thread .coderabbit.yaml
Comment thread scripts/run-snyk-ci.mjs
Comment thread scripts/scanner-gate-policy.mjs
Comment thread scripts/security-workflow-contract.test.mjs Outdated
@CrimsonSoul

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sonarqubecloud

Copy link
Copy Markdown

@CrimsonSoul
CrimsonSoul merged commit 1b06414 into test Jul 31, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant