fix(reporting): reject unusable dependency-report locations

A supplied code_locations list that normalizes to nothing now errors
(same guard as the vulnerability-report update path) instead of filing
silently without the evidence; the docstring example includes the
required end_line; and tests cover an accepted fix pair persisting
code_locations + fix_verification.
This commit is contained in:
yoni 2026-09-25 05:45:27 +00:00
parent 40efae8095
commit 976a4e05d9
2 changed files with 86 additions and 1 deletions

View file

@ -1952,6 +1952,11 @@ async def _do_create_dependency( # noqa: PLR0912, PLR0915
parsed_locations = _normalize_code_locations(code_locations)
if parsed_locations:
errors.extend(_validate_code_locations(parsed_locations))
elif code_locations:
errors.append(
"code_locations were dropped as unusable - every location needs a relative "
"'file' and an integer 'start_line'"
)
errors.extend(_validate_fix_verification(parsed_locations, fix_verification))
reachability = (reachability or "unknown").strip().lower()
@ -2232,7 +2237,7 @@ async def create_dependency_report(
the evidence behind your ``reachability`` claim. List of dicts,
same shape as ``create_vulnerability_report``::
{"file": "src/api/client.ts", "start_line": 14,
{"file": "src/api/client.ts", "start_line": 14, "end_line": 14,
"snippet": "const x = require('pkg')", "label": "imports it"}
Cite the repo-relative ``file`` and the exact 1-based

View file

@ -486,6 +486,86 @@ async def test_dependency_report_fix_locations_require_verification(
assert not report_state.vulnerability_reports
async def test_dependency_report_fix_locations_persist_with_verification(
report_state: ReportState,
) -> None:
"""A fix-pair location files when fix_verification is supplied, and both
fields persist on the stored report."""
result = await _do_create_dependency(
title="CVE-2021-23337 in lodash 4.17.20",
description="Command injection via template.",
target="repo/package.json",
cve="CVE-2021-23337",
package_name="lodash",
installed_version="4.17.20",
impact="Arbitrary command execution.",
remediation_steps="Upgrade to 4.17.21.",
assumptions="Assumes the template sink is reachable.",
package_ecosystem="npm",
manifest_path="package-lock.json",
fixed_version="4.17.21",
cwe="CWE-94",
advisory_cvss=7.2,
technical_analysis=None,
fix_effort="trivial",
reachability="imported",
reachability_evidence=_DEP_EVIDENCE,
code_locations=[
{
"file": "package-lock.json",
"start_line": 10,
"end_line": 10,
"fix_before": '"lodash": "4.17.20"',
"fix_after": '"lodash": "4.17.21"',
}
],
fix_verification="Bump verified: lockfile parses and the sink is unreachable.",
contextual_cvss_breakdown=_DEP_CONTEXT,
contextual_cvss_reasoning=_DEP_REASONING,
)
assert result["success"] is True
report = report_state.vulnerability_reports[0]
loc = report["code_locations"][0]
assert loc["fix_before"] == '"lodash": "4.17.20"'
assert loc["fix_after"] == '"lodash": "4.17.21"'
assert report["fix_verification"] == (
"Bump verified: lockfile parses and the sink is unreachable."
)
async def test_dependency_report_rejects_unusable_locations(
report_state: ReportState,
) -> None:
"""A supplied list that normalizes to nothing errors instead of filing
silently without the requested usage evidence."""
result = await _do_create_dependency(
title="CVE-2021-23337 in lodash 4.17.20",
description="Command injection via template.",
target="repo/package.json",
cve="CVE-2021-23337",
package_name="lodash",
installed_version="4.17.20",
impact="Arbitrary command execution.",
remediation_steps="Upgrade to 4.17.21.",
assumptions="Assumes the template sink is reachable.",
package_ecosystem="npm",
manifest_path="package-lock.json",
fixed_version="4.17.21",
cwe="CWE-94",
advisory_cvss=7.2,
technical_analysis=None,
fix_effort="trivial",
reachability="imported",
reachability_evidence=_DEP_EVIDENCE,
code_locations=[{"file": "/abs/path.ts", "snippet": "require('lodash')"}],
contextual_cvss_breakdown=_DEP_CONTEXT,
contextual_cvss_reasoning=_DEP_REASONING,
)
assert result["success"] is False
assert any("dropped as unusable" in e for e in result["errors"])
assert not report_state.vulnerability_reports
async def test_dependency_report_omits_blank_chain_fields(report_state: ReportState) -> None:
result = await _do_create_dependency(
title="CVE-2024-0001 in sample 1.0.0",