mirror of
https://github.com/usestrix/strix.git
synced 2026-08-18 17:52:32 +02:00
fix(reporting): require advisory_cvss for dependency findings + add SCA TUI renderer (#753)
Co-authored-by: Ahmed Allam <ahmed39652003@gmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Ahmed Allam
parent
24279e3279
commit
a87bfb4881
@@ -371,6 +371,19 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
text.append("Target: ", style=self.FIELD_STYLE)
|
||||
text.append(target)
|
||||
|
||||
dep_meta = vuln.get("dependency_metadata") or {}
|
||||
for label, key in (
|
||||
("Package", "package_name"),
|
||||
("Ecosystem", "package_ecosystem"),
|
||||
("Installed Version", "installed_version"),
|
||||
("Fixed Version", "fixed_version"),
|
||||
):
|
||||
value = dep_meta.get(key)
|
||||
if value:
|
||||
text.append("\n\n")
|
||||
text.append(f"{label}: ", style=self.FIELD_STYLE)
|
||||
text.append(str(value))
|
||||
|
||||
endpoint = vuln.get("endpoint", "")
|
||||
if endpoint:
|
||||
text.append("\n\n")
|
||||
@@ -389,6 +402,18 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
text.append("CVE: ", style=self.FIELD_STYLE)
|
||||
text.append(cve)
|
||||
|
||||
cwe = vuln.get("cwe", "")
|
||||
if cwe:
|
||||
text.append("\n\n")
|
||||
text.append("CWE: ", style=self.FIELD_STYLE)
|
||||
text.append(cwe)
|
||||
|
||||
fix_effort = vuln.get("fix_effort", "")
|
||||
if fix_effort:
|
||||
text.append("\n\n")
|
||||
text.append("Fix Effort: ", style=self.FIELD_STYLE)
|
||||
text.append(str(fix_effort).title())
|
||||
|
||||
cvss_breakdown = vuln.get("cvss_breakdown", {})
|
||||
if cvss_breakdown:
|
||||
cvss_parts = []
|
||||
@@ -434,6 +459,13 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
text.append("\n")
|
||||
text.append(technical_analysis)
|
||||
|
||||
evidence = vuln.get("evidence", "")
|
||||
if evidence:
|
||||
text.append("\n\n")
|
||||
text.append("Evidence", style=self.FIELD_STYLE)
|
||||
text.append("\n")
|
||||
text.append(evidence)
|
||||
|
||||
poc_description = vuln.get("poc_description", "")
|
||||
if poc_description:
|
||||
text.append("\n\n")
|
||||
@@ -455,6 +487,13 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
text.append("\n")
|
||||
text.append(remediation_steps)
|
||||
|
||||
assumptions = vuln.get("assumptions", "")
|
||||
if assumptions:
|
||||
text.append("\n\n")
|
||||
text.append("Assumptions", style=self.FIELD_STYLE)
|
||||
text.append("\n")
|
||||
text.append(assumptions)
|
||||
|
||||
return text
|
||||
|
||||
def _get_markdown_report(self) -> str:
|
||||
@@ -476,14 +515,27 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
lines.append(f"**Agent:** {vuln['agent_name']}")
|
||||
if vuln.get("target"):
|
||||
lines.append(f"**Target:** {vuln['target']}")
|
||||
dep_meta = vuln.get("dependency_metadata") or {}
|
||||
if dep_meta.get("package_name"):
|
||||
lines.append(f"**Package:** {dep_meta['package_name']}")
|
||||
if dep_meta.get("package_ecosystem"):
|
||||
lines.append(f"**Ecosystem:** {dep_meta['package_ecosystem']}")
|
||||
if dep_meta.get("installed_version"):
|
||||
lines.append(f"**Installed Version:** {dep_meta['installed_version']}")
|
||||
if dep_meta.get("fixed_version"):
|
||||
lines.append(f"**Fixed Version:** {dep_meta['fixed_version']}")
|
||||
if vuln.get("endpoint"):
|
||||
lines.append(f"**Endpoint:** {vuln['endpoint']}")
|
||||
if vuln.get("method"):
|
||||
lines.append(f"**Method:** {vuln['method']}")
|
||||
if vuln.get("cve"):
|
||||
lines.append(f"**CVE:** {vuln['cve']}")
|
||||
if vuln.get("cwe"):
|
||||
lines.append(f"**CWE:** {vuln['cwe']}")
|
||||
if vuln.get("cvss") is not None:
|
||||
lines.append(f"**CVSS:** {vuln['cvss']}")
|
||||
if vuln.get("fix_effort"):
|
||||
lines.append(f"**Fix Effort:** {str(vuln['fix_effort']).title()}")
|
||||
|
||||
cvss_breakdown = vuln.get("cvss_breakdown", {})
|
||||
if cvss_breakdown:
|
||||
@@ -514,6 +566,9 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
if vuln.get("technical_analysis"):
|
||||
lines.extend(["", "## Technical Analysis", "", vuln["technical_analysis"]])
|
||||
|
||||
if vuln.get("evidence"):
|
||||
lines.extend(["", "## Evidence", "", vuln["evidence"]])
|
||||
|
||||
if vuln.get("poc_description") or vuln.get("poc_script_code"):
|
||||
lines.extend(["", "## Proof of Concept", ""])
|
||||
if vuln.get("poc_description"):
|
||||
@@ -552,6 +607,9 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
if vuln.get("remediation_steps"):
|
||||
lines.extend(["", "## Remediation", "", vuln["remediation_steps"]])
|
||||
|
||||
if vuln.get("assumptions"):
|
||||
lines.extend(["", "## Assumptions", "", vuln["assumptions"]])
|
||||
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@@ -256,3 +256,176 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
|
||||
|
||||
css_classes = cls.get_css_classes("completed")
|
||||
return Static(padded, classes=css_classes)
|
||||
|
||||
|
||||
@register_tool_renderer
|
||||
class CreateDependencyReportRenderer(BaseToolRenderer):
|
||||
tool_name: ClassVar[str] = "create_dependency_report"
|
||||
css_classes: ClassVar[list[str]] = ["tool-call", "reporting-tool"]
|
||||
|
||||
SEVERITY_COLORS: ClassVar[dict[str, str]] = {
|
||||
"critical": "#dc2626",
|
||||
"high": "#ea580c",
|
||||
"medium": "#d97706",
|
||||
"low": "#65a30d",
|
||||
"info": "#0284c7",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _get_cvss_color(cls, cvss_score: float) -> str:
|
||||
if cvss_score >= 9.0:
|
||||
return "#dc2626"
|
||||
if cvss_score >= 7.0:
|
||||
return "#ea580c"
|
||||
if cvss_score >= 4.0:
|
||||
return "#d97706"
|
||||
if cvss_score >= 0.1:
|
||||
return "#65a30d"
|
||||
return "#6b7280"
|
||||
|
||||
@classmethod
|
||||
def _render_unsuccessful(cls, args: dict[str, Any], result: dict[str, Any]) -> Static:
|
||||
text = Text()
|
||||
text.append("📦 ")
|
||||
text.append("Dependency (SCA) Report", style="bold #ea580c")
|
||||
title = args.get("title", "")
|
||||
if title:
|
||||
text.append("\n\n")
|
||||
text.append("Title: ", style=FIELD_STYLE)
|
||||
text.append(title)
|
||||
|
||||
warning = result.get("warning")
|
||||
if result.get("success") is False:
|
||||
errors = result.get("errors")
|
||||
detail = (
|
||||
"; ".join(errors) if isinstance(errors, list) and errors else result.get("error")
|
||||
)
|
||||
label, style = "✗ Not created: ", "bold #dc2626"
|
||||
fallback = "Report was not created."
|
||||
else:
|
||||
detail = warning
|
||||
label, style = "⚠ Not persisted: ", "bold #d97706"
|
||||
fallback = "Report could not be persisted."
|
||||
text.append("\n\n")
|
||||
text.append(label, style=style)
|
||||
text.append(str(detail or fallback))
|
||||
|
||||
padded = Text()
|
||||
padded.append("\n\n")
|
||||
padded.append_text(text)
|
||||
padded.append("\n\n")
|
||||
return Static(padded, classes=cls.get_css_classes("failed"))
|
||||
|
||||
@classmethod
|
||||
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912, PLR0915
|
||||
args = tool_data.get("args", {})
|
||||
result = tool_data.get("result", {})
|
||||
|
||||
if isinstance(result, dict) and (result.get("success") is False or result.get("warning")):
|
||||
return cls._render_unsuccessful(args, result)
|
||||
|
||||
title = args.get("title", "")
|
||||
description = args.get("description", "")
|
||||
impact = args.get("impact", "")
|
||||
target = args.get("target", "")
|
||||
technical_analysis = args.get("technical_analysis", "")
|
||||
remediation_steps = args.get("remediation_steps", "")
|
||||
assumptions = args.get("assumptions", "")
|
||||
|
||||
package_name = args.get("package_name", "")
|
||||
package_ecosystem = args.get("package_ecosystem", "")
|
||||
installed_version = args.get("installed_version", "")
|
||||
fixed_version = args.get("fixed_version", "")
|
||||
cve = args.get("cve", "")
|
||||
cwe = args.get("cwe", "")
|
||||
advisory_cvss = args.get("advisory_cvss")
|
||||
fix_effort = args.get("fix_effort", "")
|
||||
|
||||
severity = ""
|
||||
if isinstance(result, dict):
|
||||
severity = result.get("severity", "")
|
||||
|
||||
text = Text()
|
||||
text.append("📦 ")
|
||||
text.append("Dependency (SCA) Report", style="bold #ea580c")
|
||||
|
||||
if title:
|
||||
text.append("\n\n")
|
||||
text.append("Title: ", style=FIELD_STYLE)
|
||||
text.append(title)
|
||||
|
||||
if severity:
|
||||
text.append("\n\n")
|
||||
text.append("Severity: ", style=FIELD_STYLE)
|
||||
severity_color = cls.SEVERITY_COLORS.get(severity.lower(), "#6b7280")
|
||||
text.append(severity.upper(), style=f"bold {severity_color}")
|
||||
|
||||
if advisory_cvss is not None:
|
||||
text.append("\n\n")
|
||||
text.append("Advisory CVSS: ", style=FIELD_STYLE)
|
||||
try:
|
||||
score = float(advisory_cvss)
|
||||
text.append(str(score), style=f"bold {cls._get_cvss_color(score)}")
|
||||
except (TypeError, ValueError):
|
||||
text.append(str(advisory_cvss), style=DIM_STYLE)
|
||||
|
||||
if cve:
|
||||
text.append("\n\n")
|
||||
text.append("CVE: ", style=FIELD_STYLE)
|
||||
text.append(cve)
|
||||
|
||||
if cwe:
|
||||
text.append("\n\n")
|
||||
text.append("CWE: ", style=FIELD_STYLE)
|
||||
text.append(cwe)
|
||||
|
||||
if package_name:
|
||||
text.append("\n\n")
|
||||
text.append("Package: ", style=FIELD_STYLE)
|
||||
text.append(package_name, style=FILE_STYLE)
|
||||
if package_ecosystem:
|
||||
text.append(f" ({package_ecosystem})", style=DIM_STYLE)
|
||||
|
||||
if installed_version:
|
||||
text.append("\n\n")
|
||||
text.append("Installed: ", style=FIELD_STYLE)
|
||||
text.append(installed_version, style=BEFORE_STYLE)
|
||||
if fixed_version:
|
||||
text.append(" → ", style=DIM_STYLE)
|
||||
text.append("Fixed: ", style=FIELD_STYLE)
|
||||
text.append(fixed_version, style=AFTER_STYLE)
|
||||
|
||||
if fix_effort:
|
||||
text.append("\n\n")
|
||||
text.append("Fix Effort: ", style=FIELD_STYLE)
|
||||
text.append(fix_effort)
|
||||
|
||||
if target:
|
||||
text.append("\n\n")
|
||||
text.append("Target: ", style=FIELD_STYLE)
|
||||
text.append(target)
|
||||
|
||||
for label, value in [
|
||||
("Description", description),
|
||||
("Impact", impact),
|
||||
("Technical Analysis", technical_analysis),
|
||||
("Assumptions", assumptions),
|
||||
("Remediation", remediation_steps),
|
||||
]:
|
||||
if value:
|
||||
text.append("\n\n")
|
||||
text.append(label, style=FIELD_STYLE)
|
||||
text.append("\n")
|
||||
text.append(value)
|
||||
|
||||
if not title:
|
||||
text.append("\n ")
|
||||
text.append("Creating dependency report...", style="dim")
|
||||
|
||||
padded = Text()
|
||||
padded.append("\n\n")
|
||||
padded.append_text(text)
|
||||
padded.append("\n\n")
|
||||
|
||||
css_classes = cls.get_css_classes("completed")
|
||||
return Static(padded, classes=css_classes)
|
||||
|
||||
Reference in New Issue
Block a user