feat(risk): granular per-rule toggles within policy categories#2994
feat(risk): granular per-rule toggles within policy categories#2994mfbx9da4 wants to merge 2 commits into
Conversation
Adds a nullable TEXT[] column for canonical rule_ids the policy author has unchecked within an otherwise-enabled category. Empty/NULL means every rule in the selected categories runs. This is an expand-only migration: existing rows get NULL, which the scanner treats as the no-op default.
Policy categories like Secrets and PII each cover many underlying detection rules. Authors couldn't pick a subset without giving up the whole category. Now each rule within a category is independently toggleable from the policy editor. Backend - Goa: new optional disabled_rules ([]string) on RiskPolicy / create / update payloads. Canonical rule_ids (e.g. secret.aws_access_token, pii.credit_card) the user has unchecked within otherwise-enabled categories. - Scanner: a shared DisabledRuleSet drops matching findings from gitleaks, presidio, and prompt-injection scanners on both the real-time enforcement path (server/internal/risk/scanner.go) and the batch analyzer (analyze_batch.go). Detection still runs all rules; the filter applies post-scan so dedup decisions aren't affected. - Persistence: SQLc create/update threaded through, and disabled_rules participates in policy.version bumps so a per-rule change re-drains. Dashboard - PolicyCenter: expanded category panel now renders one checkbox per rule. Unchecking a rule adds its canonical id to disabled_rules. Category header checkbox becomes indeterminate when only some rules in the category are enabled; "Enable all" / "Disable all" affordances inside the panel for fast bulk edits. - Internal scanner names (gitleaks, presidio) are not surfaced in any new UI strings; only canonical rule_ids and user-facing labels. Drive-by - "Get Started" empty-state now creates a policy with autoName=true instead of hardcoding "Risk Scanner", so first policies pick up a generated name like every other create path.
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
adaam2
left a comment
There was a problem hiding this comment.
All goodo except the migration in here
vishalg0wda
left a comment
There was a problem hiding this comment.
LGTM overall! Approved with comments.
| } | ||
|
|
||
| // Drop findings whose canonical rule_id has been unchecked by the policy | ||
| // author. Done after the dedup pass so an enabled secret finding still |
There was a problem hiding this comment.
A disabled presidio rule will mask overlapping findings from other sources that feature later in the priority order.
nit: let's make sure to add a test for this case if we agree to adapt dedup logic.
Closes AGE-2461.
Demo
https://github.com/speakeasy-api/gram/releases/download/_gh-attach-assets/granular-rule-toggles-2994.mp4
Why
Risk policies expose category-level toggles only — "Secrets" covers all 221 underlying gitleaks rules, "PII" covers every Presidio entity. Authors couldn't disable a noisy rule (e.g. a wildcard generic-api-key match) without giving up the whole category. This adds per-rule toggles inside each expandable category so the policy editor matches the granularity the scanner already supports server-side.
The categories.go ↔ rule_id mapping (
secret.*,pii.*) and the existing dashboard rule catalog already had the data; the API and UI just didn't expose it.What changed
Before
After
N/M onbadge.disabled_rules: []stringfield onRiskPolicy(canonical ids likesecret.aws_access_token,pii.credit_card).server/internal/risk/scanner.go) and the batch analyzer (server/internal/background/activities/risk_analysis/analyze_batch.go). Filter runs after dedup so an enabled secret finding still wins overlap against a disabled presidio finding.Drive-by
autoName: trueinstead of hardcoding the name"Risk Scanner", so the LLM-generated name applies to first policies like every other create path.Migration
Single PR with two commits:
feat(risk): add disabled_rules column to risk_policies— expand-onlyADD COLUMN ... NULL, fully backward-compatible.feat(risk): per-rule toggles within policy categories— Goa, scanner, dashboard.Happy to split into two PRs if preferred.
Test plan
mise lint:servercleango test ./server/internal/risk/... ./server/internal/background/activities/risk_analysis/...passes (verified locally)/rpc/hooks.claudewith content matching that rule and another enabled rule. Confirm only the enabled rule produces a finding/block.