feat(report): reject a cited CVE when the package is known but the version is clean

Refine the empty-result handling: when the provider returns no advisories for the
installed version, distinguish two cases via a new AdvisoryProvider.knows_package
(version-less query):
- provider KNOWS the package (advisories on other versions) but none affect this
  version -> CONFIDENT out-of-range -> reject (e.g. minimist 1.2.6 cited for
  CVE-2021-44906, which was fixed in 1.2.6 — OSV knows minimist, 0 affect 1.2.6).
- provider doesn't know the package at all -> genuine coverage gap (private /
  vendored / provider lag) -> still fail-open, emit.

Without this the most common dependency false positive — a real CVE cited against
a version that's already patched past its range — slipped through as a 'coverage
gap'. Surfaced by a live scan: a weak model, coerced into over-claiming patched
minimist CVEs, had them emitted; now they're correctly rejected while the genuinely
in-range lodash CVEs still emit.

Also refactor OsvProvider to a shared _query helper (used by both affecting and
knows_package). 18 tests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Sean Turner 2026-08-03 09:42:00 +01:00
parent 8af0d81418
commit d963bbd9ef
No known key found for this signature in database
2 changed files with 76 additions and 27 deletions

View file

@ -79,6 +79,13 @@ class AdvisoryProvider(Protocol):
def affecting(self, pkg: str, ecosystem: str, version: str) -> set[str] | None: ...
def knows_package(self, pkg: str, ecosystem: str) -> bool | None:
"""Does the provider have ANY advisory for this package (any version)?
Lets the caller tell a CONFIDENT out-of-range (provider knows the package
but nothing affects the installed version) from a COVERAGE GAP (provider
doesn't know the package at all → fail open). None = can't determine."""
...
class OsvProvider:
"""AdvisoryProvider backed by an OSV-schema /v1/query endpoint. Default
@ -89,28 +96,38 @@ class OsvProvider:
def __init__(self, url: str) -> None:
self.url = url
def affecting(self, pkg: str, ecosystem: str, version: str) -> set[str] | None:
def _query(self, payload: dict[str, Any]) -> list[dict] | None:
try:
ca = os.environ.get("REQUESTS_CA_BUNDLE")
resp = requests.post(
self.url, timeout=20,
json={"package": {"name": pkg, "ecosystem": ecosystem}, "version": version},
verify=ca if ca else True,
)
resp = requests.post(self.url, timeout=20, json=payload,
verify=ca if ca else True)
if resp.status_code != 200:
logger.info("dep-verify: OSV %s for %s@%s; can't answer",
resp.status_code, pkg, version)
logger.info("dep-verify: OSV %s for %s; can't answer",
resp.status_code, payload.get("package"))
return None
vulns = resp.json().get("vulns", []) or []
return resp.json().get("vulns", []) or []
except Exception: # noqa: BLE001 — advisory; a hiccup must never suppress a finding
logger.info("dep-verify: OSV query failed for %s@%s; can't answer",
pkg, version, exc_info=True)
logger.info("dep-verify: OSV query failed for %s; can't answer",
payload.get("package"), exc_info=True)
return None
def affecting(self, pkg: str, ecosystem: str, version: str) -> set[str] | None:
vulns = self._query(
{"package": {"name": pkg, "ecosystem": ecosystem}, "version": version})
if vulns is None:
return None
affecting: set[str] = set()
for v in vulns:
affecting |= _ids_for(v)
return affecting
def knows_package(self, pkg: str, ecosystem: str) -> bool | None:
# version-less query returns every advisory for the package (any version).
vulns = self._query({"package": {"name": pkg, "ecosystem": ecosystem}})
if vulns is None:
return None
return len(vulns) > 0
def _resolve_provider(dep_settings: Any) -> AdvisoryProvider | None:
"""Build the AdvisoryProvider from DepVerifySettings. None => disabled/unknown
@ -161,14 +178,25 @@ def verify_dependency( # noqa: PLR0911 — the returns are deliberate fail-open
ident, pkg, version)
return None # confirmed: installed version IS in the CVE's range → emit (real)
if not affecting:
# definitive "no advisories affect this version" is still a coverage-gap
# risk (private/vendored pkg, provider lag) → fail open, emit.
logger.info("dep-verify: provider lists NO advisories for %s@%s; emitting "
"(coverage gap, not a confident FP)", pkg, version)
return None
# Nothing affects the installed version. Disambiguate:
# - provider KNOWS the package (has advisories for OTHER versions) but
# none hit this version → CONFIDENT out-of-range → reject.
# - provider doesn't know the package at all (private/vendored, lag) →
# genuine coverage gap → fail open, emit.
knows = None
try:
knows = provider.knows_package(pkg, eco)
except Exception: # noqa: BLE001 — advisory
knows = None
if not knows: # False (unknown pkg) or None (can't tell) → fail open
logger.info("dep-verify: provider has no advisories for %s at all; "
"emitting (coverage gap, not a confident FP)", pkg)
return None
# else: package is known, this version is clean → fall through to reject.
# Provider knows advisories for this version but NOT the cited one → the
# installed version is out of the cited CVE's range → false positive.
# Provider knows advisories for this version but NOT the cited one (or knows
# the package and NOTHING affects this version) → the installed version is out
# of the cited CVE's range → false positive.
return {
"success": False,
"error": (
@ -176,8 +204,8 @@ def verify_dependency( # noqa: PLR0911 — the returns are deliberate fail-open
f"not affect {pkg}@{version} per the advisory provider (installed "
f"version is outside the advisory's vulnerable range — likely already "
f"patched or the CVE is mis-attributed to this package). Advisories "
f"affecting {pkg}@{version}: {sorted(affecting)}. If you believe the "
f"version IS vulnerable, cite the exact affected range."
f"affecting {pkg}@{version}: {sorted(affecting) or 'none'}. If you "
f"believe the version IS vulnerable, cite the exact affected range."
),
"verify_rejected": True,
"dep_version_out_of_range": True,

View file

@ -25,16 +25,21 @@ def _cand(**kw):
class _FakeProvider:
"""Returns a fixed affecting-set (or None for can't-answer)."""
def __init__(self, affecting):
"""Returns a fixed affecting-set (None for can't-answer). `knows` is what
knows_package returns (True=package known, False=unknown, None=can't tell)."""
def __init__(self, affecting, knows=None):
self._a = affecting
self._knows = knows
def affecting(self, _pkg, _ecosystem, _version):
return self._a
def knows_package(self, _pkg, _ecosystem):
return self._knows
def _prov(affecting):
return _FakeProvider(affecting)
def _prov(affecting, knows=None):
return _FakeProvider(affecting, knows)
# --- verdict logic (explicit provider, no network) ---
@ -53,9 +58,25 @@ def test_out_of_range_rejects():
assert "CVE-2021-23337" in out["error"]
def test_empty_set_fails_open():
# definitive "no advisories affect this version" -> coverage-gap risk -> emit.
assert dv.verify_dependency(_cand(), _prov(set())) is None
def test_empty_set_unknown_package_fails_open():
# empty affecting + provider doesn't know the package (knows=False) ->
# genuine coverage gap (private/vendored) -> emit.
assert dv.verify_dependency(_cand(), _prov(set(), knows=False)) is None
def test_empty_set_cant_tell_fails_open():
# empty affecting + knows_package can't answer (None) -> fail open, emit.
assert dv.verify_dependency(_cand(), _prov(set(), knows=None)) is None
def test_empty_set_known_package_rejects():
# empty affecting BUT provider KNOWS the package (advisories on other versions)
# -> confident out-of-range -> REJECT. The minimist@1.2.6 case a live scan of a
# weak model exposed: OSV knows minimist but 0 advisories affect 1.2.6.
out = dv.verify_dependency(_cand(), _prov(set(), knows=True))
assert out is not None
assert out["verify_rejected"] is True
assert out["dep_version_out_of_range"] is True
def test_provider_cant_answer_fails_open():