mirror of
https://github.com/usestrix/strix.git
synced 2026-10-15 04:18:10 +00:00
feat(viewer): show confidence, CWE/CVE and the reasoning fields in the PDF report
The per-finding markdown report carries the CVE, CWE and confidence, plus the counterevidence, the confidence rationale, what would change the severity and the assumptions. The PDF rendered none of them: a reader of the PDF saw a CVSS score and a severity but not how sure the agent was, what argued against the finding or what would change its rating. Add Confidence, CWE and CVE to the finding's header line, and the four reasoning fields as blocks between the evidence and the remediation, in the same wording the markdown report uses. Fields a finding does not have are skipped, like the existing ones. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TavwVMH7PvrD68JTDfd6Hb
This commit is contained in:
parent
f1386cad37
commit
768b101615
2 changed files with 68 additions and 0 deletions
|
|
@ -570,6 +570,16 @@ def _finding_flowables(
|
|||
meta_bits = []
|
||||
if vuln.get("cvss") is not None:
|
||||
meta_bits.append(f"<b>CVSS</b> {_esc(vuln.get('cvss'))}")
|
||||
confidence = str(vuln.get("confidence") or "").strip().title()
|
||||
meta_bits.extend(
|
||||
f"<b>{label}</b> {_esc(value)}"
|
||||
for label, value in (
|
||||
("Confidence", confidence),
|
||||
("CWE", vuln.get("cwe")),
|
||||
("CVE", vuln.get("cve")),
|
||||
)
|
||||
if value
|
||||
)
|
||||
meta_bits.extend(
|
||||
f"<b>{key.title()}</b> {_esc(vuln.get(key))}"
|
||||
for key in ("target", "endpoint", "method")
|
||||
|
|
@ -592,6 +602,15 @@ def _finding_flowables(
|
|||
poc_script = _strip_code_fence(vuln.get("poc_script_code"))
|
||||
story.extend(_field_block(styles, "PoC script", poc_script, code=True))
|
||||
story.extend(_field_block(styles, "Evidence", vuln.get("evidence"), code=True))
|
||||
# What a reader needs to weigh the finding, as in the per-finding markdown report.
|
||||
story.extend(_field_block(styles, "Counterevidence", vuln.get("counterevidence")))
|
||||
story.extend(_field_block(styles, "Confidence rationale", vuln.get("confidence_rationale")))
|
||||
story.extend(
|
||||
_field_block(
|
||||
styles, "What would change this severity", vuln.get("severity_change_conditions")
|
||||
)
|
||||
)
|
||||
story.extend(_field_block(styles, "Assumptions", vuln.get("assumptions")))
|
||||
|
||||
remediation = vuln.get("remediation_steps")
|
||||
if isinstance(remediation, list):
|
||||
|
|
|
|||
|
|
@ -77,6 +77,55 @@ def test_generate_report_pdf_has_pdf_header(tmp_path: Path) -> None:
|
|||
assert len(pdf) > 1000
|
||||
|
||||
|
||||
def test_pdf_shows_what_a_reader_needs_to_weigh_a_finding(tmp_path: Path) -> None:
|
||||
run_dir = _make_run(tmp_path)
|
||||
vulns = json.loads((run_dir / "vulnerabilities.json").read_text(encoding="utf-8"))
|
||||
vulns[0].update(
|
||||
{
|
||||
"confidence": "high",
|
||||
"cwe": "CWE-89",
|
||||
"cve": "CVE-2024-0001",
|
||||
"counterevidence": "No WAF in front of the endpoint rewrote the payload.",
|
||||
"confidence_rationale": "The injected sleep delayed the response three times.",
|
||||
"severity_change_conditions": "Lower to high if the database user is read-only.",
|
||||
"assumptions": "The staging database mirrors production.",
|
||||
}
|
||||
)
|
||||
(run_dir / "vulnerabilities.json").write_text(json.dumps(vulns), encoding="utf-8")
|
||||
|
||||
text = " ".join(_pdf_text(generate_report_pdf(run_dir)).split())
|
||||
|
||||
for expected in (
|
||||
"Confidence High",
|
||||
"CWE CWE-89",
|
||||
"CVE CVE-2024-0001",
|
||||
"COUNTEREVIDENCE",
|
||||
"No WAF in front of the endpoint rewrote the payload.",
|
||||
"CONFIDENCE RATIONALE",
|
||||
"The injected sleep delayed the response three times.",
|
||||
"WHAT WOULD CHANGE THIS SEVERITY",
|
||||
"Lower to high if the database user is read-only.",
|
||||
"ASSUMPTIONS",
|
||||
"The staging database mirrors production.",
|
||||
):
|
||||
assert expected in text
|
||||
|
||||
|
||||
def test_pdf_leaves_out_the_trust_fields_a_finding_does_not_have(tmp_path: Path) -> None:
|
||||
text = " ".join(_pdf_text(generate_report_pdf(_make_run(tmp_path))).split())
|
||||
|
||||
assert "SQL Injection" in text
|
||||
for label in (
|
||||
"Confidence",
|
||||
"CWE",
|
||||
"COUNTEREVIDENCE",
|
||||
"CONFIDENCE RATIONALE",
|
||||
"WHAT WOULD CHANGE THIS SEVERITY",
|
||||
"ASSUMPTIONS",
|
||||
):
|
||||
assert label not in text
|
||||
|
||||
|
||||
def test_generate_password_is_long_and_random() -> None:
|
||||
first = generate_password()
|
||||
second = generate_password()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue