Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions dojo/tools/snyk_issue_api/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,14 @@ def extract_coordinate_data(self, is_type_code, coordinates):
line = None # Always None for SCA
component_name = None
component_version = None
reachable = False # SCA only
reachability_values = [] # SCA only, raw values as reported by Snyk
impact_locations = []

for coordinate in coordinates:
if not is_type_code:
if coordinate.get("reachability") != "not-applicable":
reachable = True
reachability = coordinate.get("reachability")
if reachability:
reachability_values.append(reachability)

for representation in coordinate.get("representations", []):
if not is_type_code:
Expand Down Expand Up @@ -116,7 +117,7 @@ def extract_coordinate_data(self, is_type_code, coordinates):
if region:
line = start.get("line")

return file_path, line, component_name, component_version, reachable, impact_locations
return file_path, line, component_name, component_version, reachability_values, impact_locations

def get_exploit_details(self, exploit_details):
if exploit_details:
Expand Down Expand Up @@ -219,7 +220,7 @@ def get_finding(self, issue, test):

is_out_of_scope = False # attributes.get("is_out_of_scope", False)

file_path, line, component_name, component_version, reachable, impact_locations = self.extract_coordinate_data(issue_type == "code", attributes.get("coordinates", []))
file_path, line, component_name, component_version, reachability_values, impact_locations = self.extract_coordinate_data(issue_type == "code", attributes.get("coordinates", []))

# Locations (Code only)
if impact_locations:
Expand All @@ -235,7 +236,12 @@ def get_finding(self, issue, test):
"",
])

impact_details.append(f"Reachable: {'Yes' if reachable else 'No'}")
# Render the raw reachability values Snyk reported instead of a derived
# boolean: the previous "Reachable: Yes/No" counted "no-path-found" (Snyk
# affirmatively found NO path to the vulnerable code) and even a missing
# value as "Yes". When Snyk reports nothing, say nothing.
if reachability_values:
impact_details.append("Reachability: " + ", ".join(sorted(set(reachability_values))))

# Create finding
finding = Finding(
Expand Down
284 changes: 284 additions & 0 deletions unittests/scans/snyk_issue_api/snyk_sca_scan_api_reachability.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
{
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "/rest/orgs/11111111111-2222-3333333-4444/issues?version=2024-10-15&limit=100",
"first": "/rest/orgs/11111111111-2222-3333333-4444/issues?limit=100&version=2024-10-15",
"last": "/rest/orgs/11111111111-2222-3333333-4444/issues?ending_before=end&limit=100&version=2024-10-15",
"prev": "/rest/orgs/11111111111-2222-3333333-4444/issues?ending_before="
},
"data": [
{
"id": "0a4f2f6a-13b8-4a3c-9d2e-7f1a2b3c4d5e",
"type": "issue",
"attributes": {
"classes": [
{
"id": "CWE-502",
"source": "CWE",
"type": "weakness"
}
],
"coordinates": [
{
"is_fixable_manually": false,
"is_fixable_snyk": true,
"is_fixable_upstream": false,
"is_patchable": false,
"is_pinnable": false,
"is_upgradeable": true,
"reachability": "reachable",
"representations": [
{
"dependency": {
"package_name": "pyyaml",
"package_version": "5.3.1"
}
}
]
}
],
"created_at": "2025-09-11T18:25:22.457Z",
"effective_severity_level": "critical",
"ignored": false,
"key": "SNYK-PYTHON-PYYAML-590151",
"problems": [
{
"id": "SNYK-PYTHON-PYYAML-590151",
"source": "SNYK",
"type": "vulnerability",
"updated_at": "2025-09-19T09:01:27.147502Z"
}
],
"status": "open",
"title": "Arbitrary Code Execution",
"type": "package_vulnerability",
"updated_at": "2025-09-19T09:01:03.316Z"
},
"relationships": {
"organization": {
"data": {
"id": "3de2b524-eae7-4f3f-ba5f-1a07e1df44c2",
"type": "organization"
},
"links": {
"related": "/orgs/3de2b524-eae7-4f3f-ba5f-1a07e1df44c2"
}
},
"scan_item": {
"data": {
"id": "92867a20-30b8-44fd-a59f-da71d59b58f1",
"type": "project"
},
"links": {
"related": "/orgs/3de2b524-eae7-4f3f-ba5f-1a07e1df44c2/projects/92867a20-30b8-44fd-a59f-da71d59b58f1"
}
}
}
},
{
"id": "1b5e3a7b-24c9-4b4d-8e3f-8a2b3c4d5e6f",
"type": "issue",
"attributes": {
"classes": [
{
"id": "CWE-400",
"source": "CWE",
"type": "weakness"
}
],
"coordinates": [
{
"is_fixable_manually": false,
"is_fixable_snyk": false,
"is_fixable_upstream": false,
"is_patchable": false,
"is_pinnable": false,
"is_upgradeable": true,
"reachability": "package",
"representations": [
{
"dependency": {
"package_name": "pillow",
"package_version": "9.5.0"
}
}
]
}
],
"created_at": "2025-09-11T18:25:22.457Z",
"effective_severity_level": "high",
"ignored": false,
"key": "SNYK-PYTHON-PILLOW-6219986",
"problems": [
{
"id": "SNYK-PYTHON-PILLOW-6219986",
"source": "SNYK",
"type": "vulnerability",
"updated_at": "2025-09-19T09:01:27.147502Z"
}
],
"status": "open",
"title": "Denial of Service (DoS)",
"type": "package_vulnerability",
"updated_at": "2025-09-19T09:01:03.316Z"
},
"relationships": {
"organization": {
"data": {
"id": "3de2b524-eae7-4f3f-ba5f-1a07e1df44c2",
"type": "organization"
},
"links": {
"related": "/orgs/3de2b524-eae7-4f3f-ba5f-1a07e1df44c2"
}
},
"scan_item": {
"data": {
"id": "92867a20-30b8-44fd-a59f-da71d59b58f1",
"type": "project"
},
"links": {
"related": "/orgs/3de2b524-eae7-4f3f-ba5f-1a07e1df44c2/projects/92867a20-30b8-44fd-a59f-da71d59b58f1"
}
}
}
},
{
"id": "2c6f4b8c-35da-4c5e-9f4a-9b3c4d5e6f7a",
"type": "issue",
"attributes": {
"classes": [
{
"id": "CWE-94",
"source": "CWE",
"type": "weakness"
}
],
"coordinates": [
{
"is_fixable_manually": false,
"is_fixable_snyk": false,
"is_fixable_upstream": false,
"is_patchable": false,
"is_pinnable": true,
"is_upgradeable": false,
"reachability": "no-path-found",
"representations": [
{
"dependency": {
"package_name": "setuptools",
"package_version": "40.5.0"
}
}
]
}
],
"created_at": "2025-09-11T18:25:22.457Z",
"effective_severity_level": "medium",
"ignored": false,
"key": "SNYK-PYTHON-SETUPTOOLS-7448483",
"problems": [
{
"id": "SNYK-PYTHON-SETUPTOOLS-7448483",
"source": "SNYK",
"type": "vulnerability",
"updated_at": "2025-09-19T09:01:27.147502Z"
}
],
"status": "open",
"title": "Improper Control of Generation of Code ('Code Injection')",
"type": "package_vulnerability",
"updated_at": "2025-09-19T09:01:03.316Z"
},
"relationships": {
"organization": {
"data": {
"id": "3de2b524-eae7-4f3f-ba5f-1a07e1df44c2",
"type": "organization"
},
"links": {
"related": "/orgs/3de2b524-eae7-4f3f-ba5f-1a07e1df44c2"
}
},
"scan_item": {
"data": {
"id": "92867a20-30b8-44fd-a59f-da71d59b58f1",
"type": "project"
},
"links": {
"related": "/orgs/3de2b524-eae7-4f3f-ba5f-1a07e1df44c2/projects/92867a20-30b8-44fd-a59f-da71d59b58f1"
}
}
}
},
{
"id": "3d7a5c9d-46eb-4d00-a000-000000000004",
"type": "issue",
"attributes": {
"classes": [
{
"id": "CWE-502",
"source": "CWE",
"type": "weakness"
}
],
"coordinates": [
{
"is_fixable_manually": false,
"is_fixable_snyk": true,
"is_fixable_upstream": false,
"is_patchable": false,
"is_pinnable": false,
"is_upgradeable": true,
"representations": [
{
"dependency": {
"package_name": "pyyaml",
"package_version": "5.3.1"
}
}
]
}
],
"created_at": "2025-09-11T18:25:22.457Z",
"effective_severity_level": "critical",
"ignored": false,
"key": "3d7a5c9d-46eb-4d00-a000-000000000004",
"problems": [
{
"id": "SNYK-PYTHON-PYYAML-590151",
"source": "SNYK",
"type": "vulnerability",
"updated_at": "2025-09-19T09:01:27.147502Z"
}
],
"status": "open",
"title": "No reachability reported",
"type": "package_vulnerability",
"updated_at": "2025-09-19T09:01:03.316Z"
},
"relationships": {
"organization": {
"data": {
"id": "3de2b524-eae7-4f3f-ba5f-1a07e1df44c2",
"type": "organization"
},
"links": {
"related": "/orgs/3de2b524-eae7-4f3f-ba5f-1a07e1df44c2"
}
},
"scan_item": {
"data": {
"id": "92867a20-30b8-44fd-a59f-da71d59b58f1",
"type": "project"
},
"links": {
"related": "/orgs/3de2b524-eae7-4f3f-ba5f-1a07e1df44c2/projects/92867a20-30b8-44fd-a59f-da71d59b58f1"
}
}
}
}
]
}
11 changes: 11 additions & 0 deletions unittests/tools/test_govulncheck_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ def test_parse_issue_15033(self):
self.assertEqual(17, severities.count("Low"))
self.assertEqual(26, severities.count("Info"))

# Pin the description output format: these exact surfaces are part of
# the parser's observable contract (users and downstream tooling read
# them), so changes to them must be deliberate, not incidental.
high = next(f for f in findings if f.severity == "High")
self.assertIn("**Reachability:** symbol (High severity)", high.description)
self.assertIn("**Traces:**", high.description)
low = next(f for f in findings if f.severity == "Low")
self.assertIn("**Reachability:** package (Low severity)", low.description)
info = next(f for f in findings if f.severity == "Info")
self.assertIn("**Reachability:** module (Info severity)", info.description)

# Every finding maps to a vulnerable component.
self.assertTrue(all(f.component_name for f in findings))

Expand Down
24 changes: 23 additions & 1 deletion unittests/tools/test_snyk_issue_api_parser_with_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,28 @@
# attributes.title
self.assertEqual("Use of Hardcoded Passwords", finding.title)

def test_parse_sca_reachability_rendered_from_raw_values(self):
"""Pin the impact reachability format: raw Snyk values, no derived boolean.

Guards the fix for the old behavior where "no-path-found" (Snyk
affirmatively found NO path to the vulnerable code) and even a missing
value both rendered as "Reachable: Yes". The exact "Reachability: <raw>"
line is part of the parser's observable contract for downstream tooling.
"""

Check failure on line 173 in unittests/tools/test_snyk_issue_api_parser_with_json.py

View workflow job for this annotation

GitHub Actions / ruff-linting

ruff (multi-line-summary-second-line)

unittests/tools/test_snyk_issue_api_parser_with_json.py:167:9: multi-line-summary-second-line: Multi-line docstring summary should start at the second line help: Insert line break and indentation after opening quotes

Check failure on line 173 in unittests/tools/test_snyk_issue_api_parser_with_json.py

View workflow job for this annotation

GitHub Actions / ruff-linting

ruff (multi-line-summary-second-line)

unittests/tools/test_snyk_issue_api_parser_with_json.py:167:9: multi-line-summary-second-line: Multi-line docstring summary should start at the second line help: Insert line break and indentation after opening quotes
findings = self.parse_json("snyk_sca_scan_api_reachability.json")
self.assertEqual(4, len(findings))
impacts = {f.unique_id_from_tool: f.impact for f in findings}

self.assertIn("Reachability: reachable", impacts["0a4f2f6a-13b8-4a3c-9d2e-7f1a2b3c4d5e"])
self.assertIn("Reachability: package", impacts["1b5e3a7b-24c9-4b4d-8e3f-8a2b3c4d5e6f"])
self.assertIn("Reachability: no-path-found", impacts["2c6f4b8c-35da-4c5e-9f4a-9b3c4d5e6f7a"])

# When Snyk reports no reachability value at all, say nothing — the old
# derived boolean fabricated "Reachable: Yes" here.
no_value_impact = impacts["3d7a5c9d-46eb-4d00-a000-000000000004"]
self.assertNotIn("Reachability:", no_value_impact)
self.assertNotIn("Reachable:", no_value_impact)

def test_parse_sca_findings_status_open(self):
findings = self.parse_json("snyk_sca_scan_api_many_vuln.json")

Expand All @@ -176,7 +198,7 @@

# attributes.coordinates -> fix_available, component info, reachability
self.assertEqual(True, finding.fix_available)
self.assertIn("Reachable: No", finding.impact) # Not reachable
self.assertIn("Reachability: not-applicable", finding.impact) # Raw Snyk value, not a derived boolean
self.assertEqual("pillow", finding.component_name)
self.assertEqual("9.5.0", finding.component_version)
self.assertEqual("pillow", finding.file_path)
Expand Down
Loading
Loading