Constraint-range advisory scan misses advisories introduced after the constraint floor #42

Closed
opened 2026-09-13 21:28:14 +00:00 by agent-pi · 1 comment
Collaborator

Problem

The constraint-range advisory scan (#39) fetches the version-level PyPI JSON response for the spec's lower bound only. Advisories that were introduced after the floor — vulnerable code shipped in a later release within the allowed range — do not affect the floor version, so they are invisible to the scan.

Concrete shape

Advisory introduced in 2.5, fixed in 2.11; latest release 2.12; dependency spec >=2.3:

  • floor 2.3.0 is clean → the scan reports nothing
  • a consumer whose resolution is capped below 2.11 (tight upper bound, vendor pin, == pin downstream) runs vulnerable code with no signal from zaojun

The "Known limitation" paragraph in docs/usage/command-reference.md documents this gap.

Impact

The scan answers "does my floor sit on vulnerable code?" but not "does my allowed range contain a vulnerable version my resolution could land on?". The missed class is narrower than the floor case (most advisories affect long-lived code present at the floor) but bites hardest for tight-capped ranges and pinned downstream builds.

Note: even with an improved probe (see follow-up), a mid-range island (introduced 2.5, fixed 2.7, newest allowed 2.8 and clean) remains missed; full closure requires OSV range data, which is a separate concern and a new external dependency.

## Problem The constraint-range advisory scan (#39) fetches the version-level PyPI JSON response for the spec's **lower bound** only. Advisories that were *introduced after* the floor — vulnerable code shipped in a later release within the allowed range — do not affect the floor version, so they are invisible to the scan. ## Concrete shape Advisory introduced in 2.5, fixed in 2.11; latest release 2.12; dependency spec `>=2.3`: - floor 2.3.0 is clean → the scan reports nothing - a consumer whose resolution is capped below 2.11 (tight upper bound, vendor pin, `==` pin downstream) runs vulnerable code with no signal from zaojun The "Known limitation" paragraph in `docs/usage/command-reference.md` documents this gap. ## Impact The scan answers "does my floor sit on vulnerable code?" but not "does my allowed range contain a vulnerable version my resolution could land on?". The missed class is narrower than the floor case (most advisories affect long-lived code present at the floor) but bites hardest for tight-capped ranges and pinned downstream builds. Note: even with an improved probe (see follow-up), a mid-range island (introduced 2.5, fixed 2.7, newest allowed 2.8 and clean) remains missed; full closure requires OSV range data, which is a separate concern and a new external dependency.
Author
Collaborator

Plan of attack (to implement after PR #41 merges, as a small stacked PR):

  • Selection: a pure helper over releases_map — versions satisfying the spec (SpecifierSet.contains), prereleases excluded unless the spec's own bound is a prerelease, take the max. Reuses _spec_includes_prereleases.
  • Probe set: floor + newest-allowed (skip the second probe when they coincide, e.g. exact == pins or single-release maps). Same fetch path, same cache namespace (package@version), same degrade-with-stderr-warning behaviour. Cost: one extra cached request per constrained package.
  • Union: advisories from both probes deduplicated by ID, then through the existing latest-feed dedupe (ID + alias). suggest_floor already generalises: an advisory with fixed_in above the newest-allowed version yields a suggested floor that clears it, which is the correct remedy for this class.
  • Docs: the "Known limitation" paragraph shrinks to the mid-range-island case; README/Release-Notes wording adjusted at implementation time.

Open decisions to settle at implementation:

  1. Prerelease handling in selection — if the newest allowed version is an rc/alpha, probe it or skip? (Lean: follow _spec_includes_prereleases — only probe prereleases the spec itself opts into.)
  2. --min-age interplay — lean: the probe ignores min-age; it selects advisories, not updates, and "your newest allowed version has an advisory" is precisely the supply-chain signal min-age users want surfaced.
  3. Wording — the render block currently says the floor "admits" the advisories; with mid-range probes the phrase becomes "versions the constraint allows" (already partially true).
Plan of attack (to implement after PR #41 merges, as a small stacked PR): - **Selection**: a pure helper over `releases_map` — versions satisfying the spec (`SpecifierSet.contains`), prereleases excluded unless the spec's own bound is a prerelease, take the max. Reuses `_spec_includes_prereleases`. - **Probe set**: floor + newest-allowed (skip the second probe when they coincide, e.g. exact `==` pins or single-release maps). Same fetch path, same cache namespace (`package@version`), same degrade-with-stderr-warning behaviour. Cost: one extra cached request per constrained package. - **Union**: advisories from both probes deduplicated by ID, then through the existing latest-feed dedupe (ID + alias). `suggest_floor` already generalises: an advisory with `fixed_in` above the newest-allowed version yields a suggested floor that clears it, which is the correct remedy for this class. - **Docs**: the "Known limitation" paragraph shrinks to the mid-range-island case; README/Release-Notes wording adjusted at implementation time. Open decisions to settle at implementation: 1. **Prerelease handling in selection** — if the newest allowed version is an rc/alpha, probe it or skip? (Lean: follow `_spec_includes_prereleases` — only probe prereleases the spec itself opts into.) 2. **`--min-age` interplay** — lean: the probe ignores `min-age`; it selects advisories, not updates, and "your newest allowed version has an advisory" is precisely the supply-chain signal `min-age` users want surfaced. 3. **Wording** — the render block currently says the floor "admits" the advisories; with mid-range probes the phrase becomes "versions the constraint allows" (already partially true).
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
marvin8/zaojun#42
No description provided.