Skip to content

[BUG] A rejected vulnerability report renders in the TUI as a successfully filed one #834

Description

@thejesh23

Describe the bug

When create_vulnerability_report refuses to file a report, the TUI renders it as a complete, successfully-filed vulnerability report. The operator gets no indication that nothing was saved.

CreateVulnerabilityReportRenderer.render (strix/interface/tui/renderers/reporting_renderer.py:138) reads only severity and cvss_score out of result. Every other field it prints comes from args — what the model asked to file — so a rejected call still produces a full, well-formed report body. It then hard-codes the status:

css_classes = cls.get_css_classes("completed")
return Static(padded, classes=css_classes)

overriding the "failed" status that live_view._tool_status_from_result correctly computed.

The sibling renderer in the same file already handles exactly this. CreateDependencyReportRenderer.render (:320) opens with:

if isinstance(result, dict) and (result.get("success") is False or result.get("warning")):
    return cls._render_unsuccessful(args, result)

and both tools return the same payload shapes — create_vulnerability_report produces them at strix/tools/reporting/tool.py:228 (validation failed), :241 (persisted-state unavailable warning), :266 (dedupe rejection) and :303 (exception), mirroring create_dependency_report at :758, :783, :801, :832.

The three rejection paths are all routine during a scan. The dedupe one in particular fires whenever the agent re-reports a finding:

return {
    "success": False,
    "error": (
        f"Potential duplicate of '{duplicate_title}' "
        f"(id={duplicate_id[:8]}...) — do not re-report the same vulnerability"
    ),
    ...
}

To Reproduce

  1. Run a scan where the agent files a vulnerability, then files a near-identical one (or one that fails field validation).
  2. Watch the TUI.
  3. The second call renders as a full 🐞 Vulnerability Report with title, target, PoC and remediation, styled as completed.
  4. The finding is absent from vulnerabilities.json, the final report and the SARIF export.

Directly, without a scan:

from strix.interface.tui.renderers.reporting_renderer import CreateVulnerabilityReportRenderer

widget = CreateVulnerabilityReportRenderer.render({
    "args": {"title": "SQL injection in /api/login", "remediation_steps": "Use parameterized queries."},
    "result": {"success": False, "error": "Potential duplicate of 'SQLi in login' (id=8f2ab1c0...)"},
    "status": "failed",
})
print(widget.content.plain)   # full report body, no mention of the rejection
print(widget.classes)         # {'status-completed', ...}

Expected behavior
The rejection is shown, the way the dependency renderer shows it: ✗ Not created: <error>, with status-failed.

Actual behavior
A full success-styled report body, status-completed, and no mention of the error.

Note that .tool-call.status-failed and .tool-call.status-completed are visually identical in strix/interface/assets/tui_styles.tcss:378-394, so even the correct status class would not currently signal anything on its own.

System Information:

  • OS: macOS 15.3
  • Strix Version or Commit: 48b4821 (v1.2.0)
  • Python Version: 3.12
  • LLM Used: any — reproducible by calling the renderer directly

Additional context
I have a PR that adds the same guard the dependency renderer has, sharing one helper between the two so they cannot drift apart again, plus tests/test_reporting_renderer.py covering all three rejection payloads.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions