Skip to content
Open
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
29 changes: 23 additions & 6 deletions src/skillspector/nodes/analyzers/static_patterns_supply_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,20 +721,37 @@ def _sc4_from_osv(
worst_severity = v.severity
severity = _osv_severity_to_app(worst_severity)
confidence = _SEVERITY_CONFIDENCE.get(worst_severity.upper(), 0.75)
version_str = f"=={pkg_version}" if pkg_version else ""
vuln_desc = _format_vuln_ids(vulns)
if pkg_version:
message = (
f"Known Vulnerable Dependency: {pkg_name}=={pkg_version}"
f" — {len(vulns)} advisory(ies): {vuln_desc}"
)
matched_text = f"{pkg_name}=={pkg_version}"
else:
# No resolvable version: OSV was queried by name only, so these advisories are
# NOT matched against the release that will actually be installed — they are the
# package's history, and the worst of them may predate every version the range
# admits. Reporting that as the finding's severity turns "setuptools>=61" into a
# CRITICAL. The unpinned dependency itself is already reported by SC1, so what is
# left to say here is "could not verify", and it must not outrank a real match.
severity = Severity.LOW
confidence = 0.4
message = (
f"Unverifiable Dependency: {pkg_name} has {len(vulns)} known advisory(ies)"
f" ({vuln_desc}), but the manifest does not pin a version, so it is unknown"
" whether the installed release is affected"
)
matched_text = pkg_name
findings.append(
AnalyzerFinding(
rule_id="SC4",
message=(
f"Known Vulnerable Dependency: {pkg_name}{version_str}"
f" — {len(vulns)} advisory(ies): {vuln_desc}"
),
message=message,
severity=severity,
location=Location(file=file_path, start_line=line_num),
confidence=confidence,
tags=tag,
matched_text=f"{pkg_name}{version_str}" if version_str else pkg_name,
matched_text=matched_text,
)
)
return findings, covered
Expand Down
51 changes: 51 additions & 0 deletions tests/unit/test_patterns_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -1269,3 +1269,54 @@ def test_extract_packages_package_json(self) -> None:
names = [p[0] for p in sc_mod._extract_packages_from_package_json(content)]
assert "express" in names
assert "lodash" in names


class TestSC4UnresolvedVersion:
"""A name-only OSV query answers a different question than a version match."""

@staticmethod
def _vuln(severity: str = "CRITICAL"):
from skillspector.nodes.analyzers.osv_client import VulnResult

return VulnResult(
vuln_id="GHSA-xxxx-yyyy-zzzz",
summary="historical advisory",
severity=severity,
aliases=("CVE-2020-0001",),
)

def test_pinned_version_keeps_osv_severity(self) -> None:
from skillspector.models import Severity

with patch.object(sc_mod, "query_batch", return_value=[[self._vuln("CRITICAL")]]):
findings, covered = sc_mod._sc4_from_osv(
[("lodash", "4.17.20", 3)], "npm", "package.json", ["supply-chain"]
)
assert len(findings) == 1
assert findings[0].severity == Severity.CRITICAL
assert "lodash==4.17.20" in findings[0].message
assert covered == {"lodash"}

def test_unresolved_version_is_capped_and_reworded(self) -> None:
# "setuptools>=61" resolves to no version, so OSV is queried by name and returns the
# package's history. Reporting the worst of those as the finding's severity claims a
# vulnerability that the installed release may not have.
from skillspector.models import Severity

with patch.object(sc_mod, "query_batch", return_value=[[self._vuln("CRITICAL")]]):
findings, _ = sc_mod._sc4_from_osv(
[("setuptools", None, 2)], "PyPI", "pyproject.toml", ["supply-chain"]
)
assert len(findings) == 1
assert findings[0].severity == Severity.LOW
assert findings[0].confidence < 0.5
assert "does not pin a version" in findings[0].message
assert "==" not in findings[0].matched_text

def test_no_vulns_emits_nothing(self) -> None:
with patch.object(sc_mod, "query_batch", return_value=[[]]):
findings, covered = sc_mod._sc4_from_osv(
[("safe-pkg", None, 1)], "PyPI", "requirements.txt", ["supply-chain"]
)
assert findings == []
assert covered == set()