feat: Redesign vulnerability reporting with nested XML code locations and CVSS

Replace 12 flat parameters (code_file, code_before, code_after, code_diff,
and 8 CVSS fields) with structured nested XML fields: code_locations with
co-located fix_before/fix_after per location, cvss_breakdown, and cwe.

This enables multi-file vulnerability locations, per-location fixes with
precise line numbers, data flow representation (source/sink), CWE
classification, and compatibility with GitHub/GitLab PR review APIs.
This commit is contained in:
0xallam
2026-02-15 16:40:26 -08:00
committed by Ahmed Allam
parent 2b94633212
commit d6e9b3b7cf
6 changed files with 404 additions and 210 deletions

View File

@@ -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)

View File

@@ -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"):

View File

@@ -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:

View File

@@ -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")

View File

@@ -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"<location>(.*?)</location>", 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 {

View File

@@ -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.
</description>
<parameters>
@@ -58,51 +58,28 @@ Professional, customer-facing report rules (PDF-ready):
<parameter name="remediation_steps" type="string" required="true">
<description>Specific, actionable steps to fix the vulnerability</description>
</parameter>
<parameter name="attack_vector" type="string" required="true">
<description>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)</description>
</parameter>
<parameter name="attack_complexity" type="string" required="true">
<description>CVSS Attack Complexity - Conditions beyond attacker's control:
L = Low (no special conditions)
H = High (special conditions must exist)</description>
</parameter>
<parameter name="privileges_required" type="string" required="true">
<description>CVSS Privileges Required - Level of privileges needed:
N = None (no privileges needed)
L = Low (basic user privileges)
H = High (admin privileges)</description>
</parameter>
<parameter name="user_interaction" type="string" required="true">
<description>CVSS User Interaction - Does exploit require user action:
N = None (no user interaction needed)
R = Required (user must perform some action)</description>
</parameter>
<parameter name="scope" type="string" required="true">
<description>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)</description>
</parameter>
<parameter name="confidentiality" type="string" required="true">
<description>CVSS Confidentiality Impact - Impact to confidentiality:
N = None (no impact)
L = Low (some information disclosure)
H = High (all information disclosed)</description>
</parameter>
<parameter name="integrity" type="string" required="true">
<description>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)</description>
</parameter>
<parameter name="availability" type="string" required="true">
<description>CVSS Availability Impact - Impact to availability:
N = None (no impact)
L = Low (reduced performance or interruptions)
H = High (total loss of availability)</description>
<parameter name="cvss_breakdown" type="string" required="true">
<description>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)</description>
<format>
<attack_vector>N</attack_vector>
<attack_complexity>L</attack_complexity>
<privileges_required>N</privileges_required>
<user_interaction>N</user_interaction>
<scope>U</scope>
<confidentiality>H</confidentiality>
<integrity>H</integrity>
<availability>N</availability>
</format>
</parameter>
<parameter name="endpoint" type="string" required="false">
<description>API endpoint(s) or URL path(s) (e.g., "/api/login") - for web vulnerabilities, or Git repository path(s) - for code vulnerabilities</description>
@@ -111,19 +88,64 @@ H = High (total loss of availability)</description>
<description>HTTP method(s) (GET, POST, etc.) - for web vulnerabilities.</description>
</parameter>
<parameter name="cve" type="string" required="false">
<description>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.</description>
<description>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.</description>
</parameter>
<parameter name="code_file" type="string" required="false">
<description>MANDATORY for white-box testing: exact affected source file path(s).</description>
<parameter name="cwe" type="string" required="false">
<description>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.</description>
</parameter>
<parameter name="code_before" type="string" required="false">
<description>MANDATORY for white-box testing: actual vulnerable code snippet(s) copied verbatim from the repository.</description>
</parameter>
<parameter name="code_after" type="string" required="false">
<description>MANDATORY for white-box testing: corrected code snippet(s) exactly as they should appear after the fix.</description>
</parameter>
<parameter name="code_diff" type="string" required="false">
<description>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.</description>
<parameter name="code_locations" type="string" required="false">
<description>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).</description>
<format>
<location>
<file>src/db/queries.ts</file>
<start_line>42</start_line>
<end_line>42</end_line>
<snippet>db.query(`SELECT * FROM users WHERE id = ${id}`)</snippet>
<label>Unsanitized input used in SQL query (sink)</label>
<fix_before>db.query(`SELECT * FROM users WHERE id = ${id}`)</fix_before>
<fix_after>db.query('SELECT * FROM users WHERE id = $1', [id])</fix_after>
</location>
<location>
<file>src/routes/users.ts</file>
<start_line>15</start_line>
<end_line>15</end_line>
<snippet>const id = req.params.id</snippet>
<label>User input from request parameter (source)</label>
</location>
</format>
</parameter>
</parameters>
<returns type="Dict[str, Any]">
@@ -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.</parameter>
<parameter=poc_script_code>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.</parameter>
<parameter=attack_vector>N</parameter>
<parameter=attack_complexity>L</parameter>
<parameter=privileges_required>L</parameter>
<parameter=user_interaction>N</parameter>
<parameter=scope>C</parameter>
<parameter=confidentiality>H</parameter>
<parameter=integrity>H</parameter>
<parameter=availability>L</parameter>
<parameter=cvss_breakdown>
<attack_vector>N</attack_vector>
<attack_complexity>L</attack_complexity>
<privileges_required>L</privileges_required>
<user_interaction>N</user_interaction>
<scope>C</scope>
<confidentiality>H</confidentiality>
<integrity>H</integrity>
<availability>L</availability>
</parameter>
<parameter=endpoint>/api/v1/link-preview</parameter>
<parameter=method>POST</parameter>
<parameter=cwe>CWE-918</parameter>
<parameter=code_locations>
<location>
<file>src/services/link-preview.ts</file>
<start_line>47</start_line>
<end_line>47</end_line>
<snippet>const response = await fetch(userUrl)</snippet>
<label>Unvalidated user URL passed to server-side fetch (sink)</label>
<fix_before>const response = await fetch(userUrl)</fix_before>
<fix_after>const validated = await validateAndResolveUrl(userUrl)
if (!validated) throw new ForbiddenError('URL not allowed')
const response = await fetch(validated)</fix_after>
</location>
<location>
<file>src/routes/api/v1/links.ts</file>
<start_line>12</start_line>
<end_line>12</end_line>
<snippet>const userUrl = req.body.url</snippet>
<label>User-controlled URL from request body (source)</label>
</location>
</parameter>
</function>
</examples>
</tool>