diff --git a/strix/tools/reporting/tool.py b/strix/tools/reporting/tool.py index 87185fc02..023202ad3 100644 --- a/strix/tools/reporting/tool.py +++ b/strix/tools/reporting/tool.py @@ -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 diff --git a/tests/test_reporting_fields.py b/tests/test_reporting_fields.py index 3f24014e5..64facaeb1 100644 --- a/tests/test_reporting_fields.py +++ b/tests/test_reporting_fields.py @@ -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",