-
Notifications
You must be signed in to change notification settings - Fork 0
fix(triage): gate the remediation planner on the triage verdict (ADR-0051 §6) #276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2be0d4e
7f639b8
d331d38
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -334,6 +334,48 @@ async def run_all_pipeline( | |
| executor = request.app.state.agent_executor | ||
| context_builder = request.app.state.context_builder | ||
|
|
||
| # The Plan gate (ADR-0051 §6 / ADR-0054): a finding triaged as *not real* | ||
| # never gets a remediation plan — clearing noise with reasoning is the | ||
| # product's value, and an alarmist plan on a non-reachable finding is a | ||
| # false-positive-shaped result. The CLI (`cliffsec fix`) gates on the | ||
| # verdict before it ever calls run-all; this is the server-side backstop so | ||
| # NO caller (web, API) can drive the planner on noise. Defence-in-depth, so | ||
| # it is intentionally narrow: it engages only when triage has run AND | ||
| # returned a non-real verdict AND there is no human-approved plan overriding | ||
| # it. A finding that was never triaged behaves exactly as before. | ||
| sidebar = await get_sidebar(db, workspace_id) | ||
| triage = sidebar.triage if sidebar and isinstance(sidebar.triage, dict) else None | ||
| # Gate only on a CONCRETE non-real verdict. A missing/partial triage write | ||
| # ({} or no ``verdict``) leaves ``verdict`` empty and falls through to the | ||
| # normal pipeline — only a real dismissal/needs_review verdict blocks the | ||
| # planner, so an in-flight or never-triaged finding behaves as before. | ||
| verdict = (triage.get("verdict") or "").lower() if triage else "" | ||
| if verdict and verdict != "real": | ||
| # ...but a HUMAN can override a non-real verdict. The web "Looks real — | ||
| # remediate" action confirms the finding by advancing its status to | ||
| # ``triaged`` (ADR-0051 §6 — ``triaged`` means "confirmed real") BEFORE | ||
| # firing run-all; an already-approved plan is the same signal. Only an | ||
| # unconfirmed finding still sitting at ``new`` with a non-real verdict is | ||
| # blocked, so the gate stops auto-planning on noise without breaking the | ||
| # human confirm-real path. (The CLI `real` path passes the verdict check | ||
| # above and never reaches here.) | ||
| finding = await get_finding(db, workspace.finding_id) | ||
| human_confirmed = finding is not None and finding.status != "new" | ||
| plan_approved = bool((sidebar.plan or {}).get("approved")) if sidebar else False | ||
| if not human_confirmed and not plan_approved: | ||
|
Comment on lines
+362
to
+365
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Want Baz to fix this for you? Activate Fixer Other fix methodsPrompt for AI AgentsHeads up! Your free trial ends tomorrow. |
||
| logger.info( | ||
| "run-all gated for workspace %s: triaged %r, finding still new — no plan", | ||
| workspace_id, | ||
| verdict, | ||
| ) | ||
| return RunAllResponse( | ||
| status="gated", | ||
| message=( | ||
| f"Triaged as {verdict} — no remediation plan needed. " | ||
| f"Confirm it's real to plan a fix." | ||
| ), | ||
| ) | ||
|
|
||
| try: | ||
| await executor.check_not_busy(db, workspace_id) | ||
| except AgentBusyError as exc: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
triage.get("verdict").lower()is called unconditionally, butsidebar.triagecan be arbitrary JSON fromPUT /workspaces/{workspace_id}/sidebar, so a non-string or missingverdictraisesAttributeErrorand 500sPOST /pipeline/run-all— should we guard withisinstance(verdict, str)and fall back to''when not?Want Baz to fix this for you? Activate Fixer
Other fix methods
Prompt for AI Agents
Heads up!
Your free trial ends tomorrow.
To keep getting your PRs reviewed by Baz, update your team's subscription