diff --git a/strix/interface/tool_components/reporting_renderer.py b/strix/interface/tool_components/reporting_renderer.py
index 98cc85d..898157d 100644
--- a/strix/interface/tool_components/reporting_renderer.py
+++ b/strix/interface/tool_components/reporting_renderer.py
@@ -6,6 +6,11 @@ from pygments.styles import get_style_by_name
from rich.text import Text
from textual.widgets import Static
+from strix.tools.reporting.reporting_actions import (
+ parse_code_locations_xml,
+ parse_cvss_xml,
+)
+
from .base_renderer import BaseToolRenderer
from .registry import register_tool_renderer
@@ -17,6 +22,13 @@ def _get_style_colors() -> dict[Any, str]:
FIELD_STYLE = "bold #4ade80"
+DIM_STYLE = "dim"
+FILE_STYLE = "bold #60a5fa"
+LINE_STYLE = "#facc15"
+LABEL_STYLE = "italic #a1a1aa"
+CODE_STYLE = "#e2e8f0"
+BEFORE_STYLE = "#ef4444"
+AFTER_STYLE = "#22c55e"
@register_tool_renderer
@@ -80,18 +92,13 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
poc_script_code = args.get("poc_script_code", "")
remediation_steps = args.get("remediation_steps", "")
- attack_vector = args.get("attack_vector", "")
- attack_complexity = args.get("attack_complexity", "")
- privileges_required = args.get("privileges_required", "")
- user_interaction = args.get("user_interaction", "")
- scope = args.get("scope", "")
- confidentiality = args.get("confidentiality", "")
- integrity = args.get("integrity", "")
- availability = args.get("availability", "")
+ cvss_breakdown_xml = args.get("cvss_breakdown", "")
+ code_locations_xml = args.get("code_locations", "")
endpoint = args.get("endpoint", "")
method = args.get("method", "")
cve = args.get("cve", "")
+ cwe = args.get("cwe", "")
severity = ""
cvss_score = None
@@ -140,38 +147,30 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
text.append("CVE: ", style=FIELD_STYLE)
text.append(cve)
- if any(
- [
- attack_vector,
- attack_complexity,
- privileges_required,
- user_interaction,
- scope,
- confidentiality,
- integrity,
- availability,
- ]
- ):
+ if cwe:
+ text.append("\n\n")
+ text.append("CWE: ", style=FIELD_STYLE)
+ text.append(cwe)
+
+ parsed_cvss = parse_cvss_xml(cvss_breakdown_xml) if cvss_breakdown_xml else None
+ if parsed_cvss:
text.append("\n\n")
cvss_parts = []
- if attack_vector:
- cvss_parts.append(f"AV:{attack_vector}")
- if attack_complexity:
- cvss_parts.append(f"AC:{attack_complexity}")
- if privileges_required:
- cvss_parts.append(f"PR:{privileges_required}")
- if user_interaction:
- cvss_parts.append(f"UI:{user_interaction}")
- if scope:
- cvss_parts.append(f"S:{scope}")
- if confidentiality:
- cvss_parts.append(f"C:{confidentiality}")
- if integrity:
- cvss_parts.append(f"I:{integrity}")
- if availability:
- cvss_parts.append(f"A:{availability}")
+ for key, prefix in [
+ ("attack_vector", "AV"),
+ ("attack_complexity", "AC"),
+ ("privileges_required", "PR"),
+ ("user_interaction", "UI"),
+ ("scope", "S"),
+ ("confidentiality", "C"),
+ ("integrity", "I"),
+ ("availability", "A"),
+ ]:
+ val = parsed_cvss.get(key)
+ if val:
+ cvss_parts.append(f"{prefix}:{val}")
text.append("CVSS Vector: ", style=FIELD_STYLE)
- text.append("/".join(cvss_parts), style="dim")
+ text.append("/".join(cvss_parts), style=DIM_STYLE)
if description:
text.append("\n\n")
@@ -191,6 +190,40 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
text.append("\n")
text.append(technical_analysis)
+ parsed_locations = (
+ parse_code_locations_xml(code_locations_xml) if code_locations_xml else None
+ )
+ if parsed_locations:
+ text.append("\n\n")
+ text.append("Code Locations", style=FIELD_STYLE)
+ for i, loc in enumerate(parsed_locations):
+ text.append("\n\n")
+ text.append(f" Location {i + 1}: ", style=DIM_STYLE)
+ text.append(loc.get("file", "unknown"), style=FILE_STYLE)
+ start = loc.get("start_line")
+ end = loc.get("end_line")
+ if start is not None:
+ if end and end != start:
+ text.append(f":{start}-{end}", style=LINE_STYLE)
+ else:
+ text.append(f":{start}", style=LINE_STYLE)
+ if loc.get("label"):
+ text.append(f"\n {loc['label']}", style=LABEL_STYLE)
+ if loc.get("snippet"):
+ text.append("\n ")
+ text.append(loc["snippet"], style=CODE_STYLE)
+ if loc.get("fix_before") or loc.get("fix_after"):
+ text.append("\n ")
+ text.append("Fix:", style=DIM_STYLE)
+ if loc.get("fix_before"):
+ text.append("\n ")
+ text.append("- ", style=BEFORE_STYLE)
+ text.append(loc["fix_before"], style=BEFORE_STYLE)
+ if loc.get("fix_after"):
+ text.append("\n ")
+ text.append("+ ", style=AFTER_STYLE)
+ text.append(loc["fix_after"], style=AFTER_STYLE)
+
if poc_description:
text.append("\n\n")
text.append("PoC Description", style=FIELD_STYLE)
diff --git a/strix/interface/tui.py b/strix/interface/tui.py
index 49a0489..cb1adff 100644
--- a/strix/interface/tui.py
+++ b/strix/interface/tui.py
@@ -531,16 +531,30 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
lines.append("```")
# Code Analysis
- if vuln.get("code_file") or vuln.get("code_diff"):
+ if vuln.get("code_locations"):
lines.extend(["", "## Code Analysis", ""])
- if vuln.get("code_file"):
- lines.append(f"**File:** {vuln['code_file']}")
+ for i, loc in enumerate(vuln["code_locations"]):
+ file_ref = loc.get("file", "unknown")
+ line_ref = ""
+ if loc.get("start_line") is not None:
+ if loc.get("end_line") and loc["end_line"] != loc["start_line"]:
+ line_ref = f" (lines {loc['start_line']}-{loc['end_line']})"
+ else:
+ line_ref = f" (line {loc['start_line']})"
+ lines.append(f"**Location {i + 1}:** `{file_ref}`{line_ref}")
+ if loc.get("label"):
+ lines.append(f" {loc['label']}")
+ if loc.get("snippet"):
+ lines.append(f"```\n{loc['snippet']}\n```")
+ if loc.get("fix_before") or loc.get("fix_after"):
+ lines.append("**Suggested Fix:**")
+ lines.append("```diff")
+ if loc.get("fix_before"):
+ lines.extend(f"- {line}" for line in loc["fix_before"].splitlines())
+ if loc.get("fix_after"):
+ lines.extend(f"+ {line}" for line in loc["fix_after"].splitlines())
+ lines.append("```")
lines.append("")
- if vuln.get("code_diff"):
- lines.append("**Changes:**")
- lines.append("```diff")
- lines.append(vuln["code_diff"])
- lines.append("```")
# Remediation
if vuln.get("remediation_steps"):
diff --git a/strix/interface/utils.py b/strix/interface/utils.py
index 8dbaebb..fe5bdfc 100644
--- a/strix/interface/utils.py
+++ b/strix/interface/utils.py
@@ -163,32 +163,34 @@ def format_vulnerability_report(report: dict[str, Any]) -> Text: # noqa: PLR091
text.append("\n")
text.append(poc_script_code, style="dim")
- code_file = report.get("code_file")
- if code_file:
+ code_locations = report.get("code_locations")
+ if code_locations:
text.append("\n\n")
- text.append("Code File: ", style=field_style)
- text.append(code_file)
-
- code_before = report.get("code_before")
- if code_before:
- text.append("\n\n")
- text.append("Code Before", style=field_style)
- text.append("\n")
- text.append(code_before, style="dim")
-
- code_after = report.get("code_after")
- if code_after:
- text.append("\n\n")
- text.append("Code After", style=field_style)
- text.append("\n")
- text.append(code_after, style="dim")
-
- code_diff = report.get("code_diff")
- if code_diff:
- text.append("\n\n")
- text.append("Code Diff", style=field_style)
- text.append("\n")
- text.append(code_diff, style="dim")
+ text.append("Code Locations", style=field_style)
+ for i, loc in enumerate(code_locations):
+ text.append("\n\n")
+ text.append(f" Location {i + 1}: ", style="dim")
+ text.append(loc.get("file", "unknown"), style="bold")
+ start = loc.get("start_line")
+ end = loc.get("end_line")
+ if start is not None:
+ if end and end != start:
+ text.append(f":{start}-{end}")
+ else:
+ text.append(f":{start}")
+ if loc.get("label"):
+ text.append(f"\n {loc['label']}", style="italic dim")
+ if loc.get("snippet"):
+ text.append("\n ")
+ text.append(loc["snippet"], style="dim")
+ if loc.get("fix_before") or loc.get("fix_after"):
+ text.append("\n Fix:")
+ if loc.get("fix_before"):
+ text.append("\n - ", style="dim")
+ text.append(loc["fix_before"], style="dim")
+ if loc.get("fix_after"):
+ text.append("\n + ", style="dim")
+ text.append(loc["fix_after"], style="dim")
remediation_steps = report.get("remediation_steps")
if remediation_steps:
diff --git a/strix/telemetry/tracer.py b/strix/telemetry/tracer.py
index 25af62c..ecf3ce8 100644
--- a/strix/telemetry/tracer.py
+++ b/strix/telemetry/tracer.py
@@ -89,10 +89,8 @@ class Tracer:
endpoint: str | None = None,
method: str | None = None,
cve: str | None = None,
- code_file: str | None = None,
- code_before: str | None = None,
- code_after: str | None = None,
- code_diff: str | None = None,
+ cwe: str | None = None,
+ code_locations: list[dict[str, Any]] | None = None,
) -> str:
report_id = f"vuln-{len(self.vulnerability_reports) + 1:04d}"
@@ -127,14 +125,10 @@ class Tracer:
report["method"] = method.strip()
if cve:
report["cve"] = cve.strip()
- if code_file:
- report["code_file"] = code_file.strip()
- if code_before:
- report["code_before"] = code_before.strip()
- if code_after:
- report["code_after"] = code_after.strip()
- if code_diff:
- report["code_diff"] = code_diff.strip()
+ if cwe:
+ report["cwe"] = cwe.strip()
+ if code_locations:
+ report["code_locations"] = code_locations
self.vulnerability_reports.append(report)
logger.info(f"Added vulnerability report: {report_id} - {title}")
@@ -323,6 +317,7 @@ class Tracer:
("Endpoint", report.get("endpoint")),
("Method", report.get("method")),
("CVE", report.get("cve")),
+ ("CWE", report.get("cwe")),
]
cvss_score = report.get("cvss")
if cvss_score is not None:
@@ -353,15 +348,33 @@ class Tracer:
f.write(f"{report['poc_script_code']}\n")
f.write("```\n\n")
- if report.get("code_file") or report.get("code_diff"):
+ if report.get("code_locations"):
f.write("## Code Analysis\n\n")
- if report.get("code_file"):
- f.write(f"**File:** {report['code_file']}\n\n")
- if report.get("code_diff"):
- f.write("**Changes:**\n")
- f.write("```diff\n")
- f.write(f"{report['code_diff']}\n")
- f.write("```\n\n")
+ for i, loc in enumerate(report["code_locations"]):
+ prefix = f"**Location {i + 1}:**"
+ file_ref = loc.get("file", "unknown")
+ line_ref = ""
+ if loc.get("start_line") is not None:
+ if loc.get("end_line") and loc["end_line"] != loc["start_line"]:
+ line_ref = f" (lines {loc['start_line']}-{loc['end_line']})"
+ else:
+ line_ref = f" (line {loc['start_line']})"
+ f.write(f"{prefix} `{file_ref}`{line_ref}\n")
+ if loc.get("label"):
+ f.write(f" {loc['label']}\n")
+ if loc.get("snippet"):
+ f.write(f" ```\n {loc['snippet']}\n ```\n")
+ if loc.get("fix_before") or loc.get("fix_after"):
+ f.write("\n **Suggested Fix:**\n")
+ f.write(" ```diff\n")
+ if loc.get("fix_before"):
+ for line in loc["fix_before"].splitlines():
+ f.write(f" - {line}\n")
+ if loc.get("fix_after"):
+ for line in loc["fix_after"].splitlines():
+ f.write(f" + {line}\n")
+ f.write(" ```\n")
+ f.write("\n")
if report.get("remediation_steps"):
f.write("## Remediation\n\n")
diff --git a/strix/tools/reporting/reporting_actions.py b/strix/tools/reporting/reporting_actions.py
index c307d93..b84797f 100644
--- a/strix/tools/reporting/reporting_actions.py
+++ b/strix/tools/reporting/reporting_actions.py
@@ -1,8 +1,120 @@
+import contextlib
+import re
+from pathlib import PurePosixPath
from typing import Any
from strix.tools.registry import register_tool
+_CVSS_FIELDS = (
+ "attack_vector",
+ "attack_complexity",
+ "privileges_required",
+ "user_interaction",
+ "scope",
+ "confidentiality",
+ "integrity",
+ "availability",
+)
+
+
+def parse_cvss_xml(xml_str: str) -> dict[str, str] | None:
+ if not xml_str or not xml_str.strip():
+ return None
+ result = {}
+ for field in _CVSS_FIELDS:
+ match = re.search(rf"<{field}>(.*?){field}>", xml_str, re.DOTALL)
+ if match:
+ result[field] = match.group(1).strip()
+ return result if result else None
+
+
+def parse_code_locations_xml(xml_str: str) -> list[dict[str, Any]] | None:
+ if not xml_str or not xml_str.strip():
+ return None
+ locations = []
+ for loc_match in re.finditer(r"(.*?)", xml_str, re.DOTALL):
+ loc: dict[str, Any] = {}
+ loc_content = loc_match.group(1)
+ for field in (
+ "file",
+ "start_line",
+ "end_line",
+ "snippet",
+ "label",
+ "fix_before",
+ "fix_after",
+ ):
+ field_match = re.search(rf"<{field}>(.*?){field}>", loc_content, re.DOTALL)
+ if field_match:
+ raw = field_match.group(1)
+ value = (
+ raw.strip("\n")
+ if field in ("snippet", "fix_before", "fix_after")
+ else raw.strip()
+ )
+ if field in ("start_line", "end_line"):
+ with contextlib.suppress(ValueError, TypeError):
+ loc[field] = int(value)
+ elif value:
+ loc[field] = value
+ if loc.get("file") and loc.get("start_line") is not None:
+ locations.append(loc)
+ return locations if locations else None
+
+
+def _validate_file_path(path: str) -> str | None:
+ if not path or not path.strip():
+ return "file path cannot be empty"
+ p = PurePosixPath(path)
+ if p.is_absolute():
+ return f"file path must be relative, got absolute: '{path}'"
+ if ".." in p.parts:
+ return f"file path must not contain '..': '{path}'"
+ return None
+
+
+def _validate_code_locations(locations: list[dict[str, Any]]) -> list[str]:
+ errors = []
+ for i, loc in enumerate(locations):
+ path_err = _validate_file_path(loc.get("file", ""))
+ if path_err:
+ errors.append(f"code_locations[{i}]: {path_err}")
+ start = loc.get("start_line")
+ if not isinstance(start, int) or start < 1:
+ errors.append(f"code_locations[{i}]: start_line must be a positive integer")
+ end = loc.get("end_line")
+ if end is None:
+ errors.append(f"code_locations[{i}]: end_line is required")
+ elif not isinstance(end, int) or end < 1:
+ errors.append(f"code_locations[{i}]: end_line must be a positive integer")
+ elif isinstance(start, int) and end < start:
+ errors.append(f"code_locations[{i}]: end_line ({end}) must be >= start_line ({start})")
+ return errors
+
+
+def _extract_cve(cve: str) -> str:
+ match = re.search(r"CVE-\d{4}-\d{4,}", cve)
+ return match.group(0) if match else cve.strip()
+
+
+def _validate_cve(cve: str) -> str | None:
+ if not re.match(r"^CVE-\d{4}-\d{4,}$", cve):
+ return f"invalid CVE format: '{cve}' (expected 'CVE-YYYY-NNNNN')"
+ return None
+
+
+def _extract_cwe(cwe: str) -> str:
+ match = re.search(r"CWE-\d+", cwe)
+ return match.group(0) if match else cwe.strip()
+
+
+def _validate_cwe(cwe: str) -> str | None:
+ if not re.match(r"^CWE-\d+$", cwe):
+ return f"invalid CWE format: '{cwe}' (expected 'CWE-NNN')"
+ return None
+
+
def calculate_cvss_and_severity(
attack_vector: str,
attack_complexity: str,
@@ -87,7 +199,7 @@ def _validate_cvss_parameters(**kwargs: str) -> list[str]:
@register_tool(sandbox_execution=False)
-def create_vulnerability_report(
+def create_vulnerability_report( # noqa: PLR0912
title: str,
description: str,
impact: str,
@@ -96,23 +208,12 @@ def create_vulnerability_report(
poc_description: str,
poc_script_code: str,
remediation_steps: str,
- # CVSS Breakdown Components
- attack_vector: str,
- attack_complexity: str,
- privileges_required: str,
- user_interaction: str,
- scope: str,
- confidentiality: str,
- integrity: str,
- availability: str,
- # Optional fields
+ cvss_breakdown: str,
endpoint: str | None = None,
method: str | None = None,
cve: str | None = None,
- code_file: str | None = None,
- code_before: str | None = None,
- code_after: str | None = None,
- code_diff: str | None = None,
+ cwe: str | None = None,
+ code_locations: str | None = None,
) -> dict[str, Any]:
validation_errors = _validate_required_fields(
title=title,
@@ -125,32 +226,32 @@ def create_vulnerability_report(
remediation_steps=remediation_steps,
)
- validation_errors.extend(
- _validate_cvss_parameters(
- attack_vector=attack_vector,
- attack_complexity=attack_complexity,
- privileges_required=privileges_required,
- user_interaction=user_interaction,
- scope=scope,
- confidentiality=confidentiality,
- integrity=integrity,
- availability=availability,
- )
- )
+ parsed_cvss = parse_cvss_xml(cvss_breakdown)
+ if not parsed_cvss:
+ validation_errors.append("cvss: could not parse CVSS breakdown XML")
+ else:
+ validation_errors.extend(_validate_cvss_parameters(**parsed_cvss))
+
+ parsed_locations = parse_code_locations_xml(code_locations) if code_locations else None
+
+ if parsed_locations:
+ validation_errors.extend(_validate_code_locations(parsed_locations))
+ if cve:
+ cve = _extract_cve(cve)
+ cve_err = _validate_cve(cve)
+ if cve_err:
+ validation_errors.append(cve_err)
+ if cwe:
+ cwe = _extract_cwe(cwe)
+ cwe_err = _validate_cwe(cwe)
+ if cwe_err:
+ validation_errors.append(cwe_err)
if validation_errors:
return {"success": False, "message": "Validation failed", "errors": validation_errors}
- cvss_score, severity, cvss_vector = calculate_cvss_and_severity(
- attack_vector,
- attack_complexity,
- privileges_required,
- user_interaction,
- scope,
- confidentiality,
- integrity,
- availability,
- )
+ assert parsed_cvss is not None
+ cvss_score, severity, cvss_vector = calculate_cvss_and_severity(**parsed_cvss)
try:
from strix.telemetry.tracer import get_global_tracer
@@ -196,17 +297,6 @@ def create_vulnerability_report(
"reason": dedupe_result.get("reason", ""),
}
- cvss_breakdown = {
- "attack_vector": attack_vector,
- "attack_complexity": attack_complexity,
- "privileges_required": privileges_required,
- "user_interaction": user_interaction,
- "scope": scope,
- "confidentiality": confidentiality,
- "integrity": integrity,
- "availability": availability,
- }
-
report_id = tracer.add_vulnerability_report(
title=title,
description=description,
@@ -218,14 +308,12 @@ def create_vulnerability_report(
poc_script_code=poc_script_code,
remediation_steps=remediation_steps,
cvss=cvss_score,
- cvss_breakdown=cvss_breakdown,
+ cvss_breakdown=parsed_cvss,
endpoint=endpoint,
method=method,
cve=cve,
- code_file=code_file,
- code_before=code_before,
- code_after=code_after,
- code_diff=code_diff,
+ cwe=cwe,
+ code_locations=parsed_locations,
)
return {
diff --git a/strix/tools/reporting/reporting_actions_schema.xml b/strix/tools/reporting/reporting_actions_schema.xml
index 56c6cc0..39e6a77 100644
--- a/strix/tools/reporting/reporting_actions_schema.xml
+++ b/strix/tools/reporting/reporting_actions_schema.xml
@@ -14,7 +14,7 @@ DO NOT USE:
- For reporting multiple vulnerabilities at once. Use a separate create_vulnerability_report for each vulnerability.
- To re-report a vulnerability that was already reported (even with different details)
-White-box requirement (when you have access to the code): You MUST include code_file, code_before, code_after, and code_diff. These must contain the actual code (before/after) and a complete, apply-able unified diff.
+White-box requirement (when you have access to the code): You MUST include code_locations with nested XML, including fix_before/fix_after on locations where a fix is proposed.
DEDUPLICATION: If this tool returns with success=false and mentions a duplicate, DO NOT attempt to re-submit. The vulnerability has already been reported. Move on to testing other areas.
@@ -30,7 +30,7 @@ Professional, customer-facing report rules (PDF-ready):
6) Impact
7) Remediation
8) Evidence (optional request/response excerpts, etc.) in the technical analysis field.
-- Numbered steps are allowed ONLY within the proof of concept. Elsewhere, use clear, concise paragraphs suitable for customer-facing reports.
+- Numbered steps are allowed ONLY within the proof of concept and remediation sections. Elsewhere, use clear, concise paragraphs suitable for customer-facing reports.
- Language must be precise and non-vague; avoid hedging.
@@ -58,51 +58,28 @@ Professional, customer-facing report rules (PDF-ready):
Specific, actionable steps to fix the vulnerability
-
- CVSS Attack Vector - How the vulnerability is exploited:
-N = Network (remotely exploitable)
-A = Adjacent (same network segment)
-L = Local (local access required)
-P = Physical (physical access required)
-
-
- CVSS Attack Complexity - Conditions beyond attacker's control:
-L = Low (no special conditions)
-H = High (special conditions must exist)
-
-
- CVSS Privileges Required - Level of privileges needed:
-N = None (no privileges needed)
-L = Low (basic user privileges)
-H = High (admin privileges)
-
-
- CVSS User Interaction - Does exploit require user action:
-N = None (no user interaction needed)
-R = Required (user must perform some action)
-
-
- CVSS Scope - Can the vulnerability affect resources beyond its security scope:
-U = Unchanged (only affects the vulnerable component)
-C = Changed (affects resources beyond vulnerable component)
-
-
- CVSS Confidentiality Impact - Impact to confidentiality:
-N = None (no impact)
-L = Low (some information disclosure)
-H = High (all information disclosed)
-
-
- CVSS Integrity Impact - Impact to integrity:
-N = None (no impact)
-L = Low (data can be modified but scope is limited)
-H = High (total loss of integrity)
-
-
- CVSS Availability Impact - Impact to availability:
-N = None (no impact)
-L = Low (reduced performance or interruptions)
-H = High (total loss of availability)
+
+ CVSS 3.1 base score breakdown as nested XML. All 8 metrics are required.
+
+Each metric element contains a single uppercase letter value:
+- attack_vector: N (Network), A (Adjacent), L (Local), P (Physical)
+- attack_complexity: L (Low), H (High)
+- privileges_required: N (None), L (Low), H (High)
+- user_interaction: N (None), R (Required)
+- scope: U (Unchanged), C (Changed)
+- confidentiality: N (None), L (Low), H (High)
+- integrity: N (None), L (Low), H (High)
+- availability: N (None), L (Low), H (High)
+
+ N
+ L
+ N
+ N
+ U
+ H
+ H
+ N
+ API endpoint(s) or URL path(s) (e.g., "/api/login") - for web vulnerabilities, or Git repository path(s) - for code vulnerabilities
@@ -111,19 +88,64 @@ H = High (total loss of availability)
HTTP method(s) (GET, POST, etc.) - for web vulnerabilities.
- CVE identifier (e.g., "CVE-2024-1234"). Make sure it's a valid CVE. Use web search or vulnerability databases to make sure it's a valid CVE number.
+ CVE identifier. ONLY the ID, e.g. "CVE-2024-1234" — do NOT include the name or description.
+You must be 100% certain of the exact CVE number. Do NOT guess, approximate, or hallucinate CVE IDs.
+If web_search is available, use it to verify the CVE exists and matches this vulnerability. If you cannot verify it, omit this field entirely.
-
- MANDATORY for white-box testing: exact affected source file path(s).
+
+ CWE identifier. ONLY the ID, e.g. "CWE-89" — do NOT include the name or parenthetical (wrong: "CWE-89 (SQL Injection)").
+
+You must be 100% certain of the exact CWE number. Do NOT guess or approximate.
+If web_search is available and you are unsure, use it to look up the correct CWE. If you cannot be certain, omit this field entirely.
+Always prefer the most specific child CWE over a broad parent.
+For example, use CWE-89 instead of CWE-74, or CWE-78 instead of CWE-77.
+
+Reference (ID only — names here are just for your reference, do NOT include them in the value):
+- Injection: CWE-79 XSS, CWE-89 SQLi, CWE-78 OS Command Injection, CWE-94 Code Injection, CWE-77 Command Injection
+- Auth/Access: CWE-287 Improper Authentication, CWE-862 Missing Authorization, CWE-863 Incorrect Authorization, CWE-306 Missing Authentication for Critical Function, CWE-639 Authorization Bypass Through User-Controlled Key
+- Web: CWE-352 CSRF, CWE-918 SSRF, CWE-601 Open Redirect, CWE-434 Unrestricted Upload of File with Dangerous Type
+- Memory: CWE-787 Out-of-bounds Write, CWE-125 Out-of-bounds Read, CWE-416 Use After Free, CWE-120 Classic Buffer Overflow
+- Data: CWE-502 Deserialization of Untrusted Data, CWE-22 Path Traversal, CWE-611 XXE
+- Crypto/Config: CWE-798 Use of Hard-coded Credentials, CWE-327 Use of Broken or Risky Cryptographic Algorithm, CWE-311 Missing Encryption of Sensitive Data, CWE-916 Password Hash With Insufficient Computational Effort
+
+Do NOT use broad/parent CWEs like CWE-74, CWE-20, CWE-200, CWE-284, or CWE-693.
-
- MANDATORY for white-box testing: actual vulnerable code snippet(s) copied verbatim from the repository.
-
-
- MANDATORY for white-box testing: corrected code snippet(s) exactly as they should appear after the fix.
-
-
- MANDATORY for white-box testing: unified diff showing the code changes. Must be a complete, apply-able unified diff (git format) covering all affected files, with proper file headers, line numbers, and sufficient context.
+
+ Nested XML list of code locations where the vulnerability exists. MANDATORY for white-box testing.
+
+Order: first location is where the issue manifests (typically the sink). Additional locations provide data flow context (source → propagation → sink).
+
+Each location element fields:
+- file (REQUIRED): Path relative to repository root. No leading slash, no absolute paths, no ".." traversal.
+ Correct: "src/db/queries.ts" or "app/routes/users.py"
+ Wrong: "/workspace/repo/src/db/queries.ts", "./src/db/queries.ts", "../../etc/passwd"
+- start_line (REQUIRED): Exact 1-based line number where the vulnerable code begins. Must be a positive integer. You must be certain of this number — do not guess or approximate. Go back and verify against the actual file content if needed.
+- end_line (REQUIRED): Exact 1-based line number where the vulnerable code ends. Must be >= start_line. Set equal to start_line if the vulnerability is on a single line.
+- snippet (optional): The actual source code at this location, copied verbatim from the file. Do not paraphrase or summarize code — paste it exactly as it appears.
+- label (optional): Short role description for this location in the data flow, e.g. "User input from request parameter (source)", "Unsanitized input passed to SQL query (sink)".
+- fix_before (optional): The vulnerable code to be replaced, copied verbatim. Must match the actual source exactly — do not paraphrase, summarize, or add/remove whitespace. Only include on locations where a fix is proposed.
+- fix_after (optional): The corrected code that should replace fix_before. Must be syntactically valid and ready to apply as a direct replacement. Only include on locations where a fix is proposed.
+
+Locations without fix_before/fix_after are informational context (e.g. showing the source of tainted data).
+Locations with fix_before/fix_after are actionable fixes (used for PR review suggestions).
+
+
+ src/db/queries.ts
+ 42
+ 42
+ db.query(`SELECT * FROM users WHERE id = ${id}`)
+
+ db.query(`SELECT * FROM users WHERE id = ${id}`)
+ db.query('SELECT * FROM users WHERE id = $1', [id])
+
+
+ src/routes/users.ts
+ 15
+ 15
+ const id = req.params.id
+
+
+
@@ -177,7 +199,6 @@ Impact validation:
- Use a controlled internal endpoint (or a benign endpoint that returns a distinct marker) to demonstrate that the request is performed by the server, not the client.
- If the application follows redirects, validate whether an allowlisted URL can redirect to a disallowed destination, and whether the redirected-to destination is still fetched.
import json
-import sys
import time
from urllib.parse import urljoin
@@ -262,16 +283,39 @@ if __name__ == "__main__":
6. Monitoring and alerting
- Log and alert on preview attempts to unusual destinations, repeated failures, high-frequency requests, or attempts to access blocked ranges.
-N
-L
-L
-N
-C
-H
-H
-L
+
+ N
+ L
+ L
+ N
+ C
+ H
+ H
+ L
+/api/v1/link-previewPOST
+CWE-918
+
+
+ src/services/link-preview.ts
+ 47
+ 47
+ const response = await fetch(userUrl)
+
+ const response = await fetch(userUrl)
+ const validated = await validateAndResolveUrl(userUrl)
+if (!validated) throw new ForbiddenError('URL not allowed')
+const response = await fetch(validated)
+
+
+ src/routes/api/v1/links.ts
+ 12
+ 12
+ const userUrl = req.body.url
+
+
+