Probe the newest allowed release in the constraint-range advisory scan #43
No reviewers
Labels
No labels
bug
contribution welcome
duplicate
enhancement
good first issue
help wanted
invalid
question
upstream
No milestone
No assignees
3 participants
Notifications
Due date
No due date set.
Reference
marvin8/zaojun!43
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/issue-42-probe-newest-allowed"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements #42: the range-advisory scan previously probed only the spec's lower bound, so advisories introduced after the floor (shipped broken in a later release, fixed later still) were invisible. zaojun now probes two versions per constrained dependency — the lowest release the spec actually allows and the newest release it allows.
How
oldest_allowed_version()andnewest_allowed_version()over the existingreleases_map: versions satisfying the spec, prereleases excluded unless the spec's own bound is a prerelease, unparseable keys skipped.~=0.0probes the lowest published0.0.xinstead of a literal0.0that was never released. When no release history is available, the probe falls back to the spec's inclusive lower bound.==pins and single-release maps degrade to one probe; probes coincident under parsed-version comparison are fetched once.suggest_floorgeneralises — an advisory fixed above the newest-allowed version yields a floor that clears it.--min-age(it selects advisories, not updates) — decision pinned on the issue.Scope note
The known limitation shrinks but doesn't vanish: a vulnerable mid-range island (introduced 2.5, fixed 2.7, newest-allowed 2.8 and clean) still needs full-range OSV data. Docs state this explicitly.
Verification
--groups:ty~=0.0no longer produces a 404 warning and the mkdocs-material/pytest alias pairs each report one advisory.Closes #42
WuMing
Found 1 issue(s). See inline comments below.
@ -157,0 +202,4 @@cache=mock_cache,releases_map={"2.3.0": None},)assert [advisory.id for advisory in result] == ["PYSEC-2026-3849"]tests [MEDIUM]
This test is named/documented as verifying that coincident floor and newest-allowed releases cause only a single fetch, but the only assertion checks the resulting advisory IDs. Because the responses for both probes would be identical (same URL) and the collection is deduplicated by advisory ID, this test still passes if the parsed-version dedupe in
get_range_vulnerabilitiesis removed and the endpoint is queried twice — it cannot fail for the behavior it claims to pin. Add an explicit assertion on the number of requests (e.g. count matching requests via the mock, or register the responder as single-use/assert-all-called so a second hit to.../2.3/jsonerrors).tests/test_get_range_vulnerabilities.py✅ Fixed in commit
c1fcff6— the test now assertsroute.call_count == 1andhttpx2_mock.calls.call_count == 1(pytest-httpx2 is a respx router wrapper, so request history is available). Verified the strengthened test actually bites: with the dedupe temporarily regressed to string comparison, the second probe hits.../2.3.0/jsonand the test fails on the call count; restored, it passes.WuMing
Found 3 issue(s). See inline comments below.
@ -16,3 +16,3 @@`zaojun` is a command-line (CLI) tool to check versions of your dependencies as defined in `pyproject.toml` against the latest versions published on [PyPI](https://pypi.org). Full documentation is available at [zaojun.marvin8.zone](https://zaojun.marvin8.zone/latest/).**New in v1.8.0**: Constraint-range advisory scanning — zaojun checks the oldest version your constraint allows, not just the newest release. When that floor has known security advisories, the dependency line gains a 🚨 inline indicator and an `Advisories in declared range` block lists them with a suggested floor that clears every fixable advisory. One extra cached HTTP request per constrained package; `vuln-ignore` rules apply. Range advisories set exit code 1 and are never silenced by `--library` or `--compat-ok`.**New in v1.8.0**: Constraint-range advisory scanning — zaojun checks the oldest and newest versions your constraint allows, not just the latest release. When either has known security advisories, the dependency line gains a 🚨 inline indicator and an `Advisories in declared range` block lists them with a suggested floor that clears every fixable advisory. Up to two extra cached HTTP requests per constrained package; `vuln-ignore` rules apply. Range advisories set exit code 1 and are never silenced by `--library` or `--compat-ok`.docs [LOW]
The blanket claim that zaojun "checks the oldest and newest versions your constraint allows" overstates coverage for constraint shapes that have no inclusive lower bound. docs/usage/command-reference.md (lines 167-172) states that
>2.3,==2.3.*and<2.0have no lower bound to probe and only their newest allowed release is scanned (a single-release map or an==X.Y.Zpin also degrades to one probe). Consider wording such as "checks the oldest version (when the constraint has an inclusive lower bound) and the newest version it allows" so the summary does not contradict the command reference.@ -5,3 +5,3 @@zaojun 1.7.5- Constraint-range advisory scanning: zaojun now also checks the oldest version each constraint allows — 🚨 warns when that floor is known to be affected by vulnerabilities, and suggests a floor that clears them- Constraint-range advisory scanning: zaojun now also checks the oldest and newest versions each constraint allows — 🚨 warns when either is known to be affected by vulnerabilities, and suggests a floor that clears themdocs [MEDIUM]
This rewritten bullet still sits under the existing "zaojun 1.7.5" heading (line 5), but it now describes the brand-new two-probe behaviour, while README.md line 18 still bills constraint-range advisory scanning as "New in v1.8.0". As written, the historical 1.7.5 note claims behaviour that did not exist in that release, and the two documents disagree about which version introduced the newest-allowed probe. Fix by adding a new release-notes entry for the current release (leaving the 1.7.5 text describing only the floor probe), or by correcting the version labels so both files agree.
@ -173,0 +169,4 @@`~=X.Y`), and — when the release history is available — the newest release theconstraint allows, which is what a fresh resolution would install. Exclusive-bound andwildcard shapes (`>2.3`, `==2.3.*`, `<2.0`) have no inclusive lower bound to probe, buttheir newest allowed release is still scanned.docs [LOW]
Probe selection deliberately ignores
--min-age(that option filters update candidates, not advisory probes), but this section never says so. Since the paragraph documents fetch failures and other scan mechanics, users running--min-agemay wrongly expect the "newest release the constraint allows" probe to respect the age filter. Add a sentence here noting that probe selection is independent of--min-age.README.md✅ Fixed in commit
31824ed— README now reads "checks the oldest version (when the constraint has an inclusive lower bound) and the newest version it allows", matching the command reference.docs/usage/command-reference.md✅ Fixed in commit
31824ed— the section now states probe selection is independent of--min-age(it filters update candidates, not advisory probes) and why: a too-new release carrying advisories is the signal--min-ageusers want surfaced.Release-Notes.md🔴 Duplicate — fifth re-raise of the version-label substance (wave-1 on #41, wave-2, wave-3, wave-4). The Release-Notes file is an unreleased accumulator: the
zaojun 1.7.5heading is the current-version line[tool.bumpversion]rewrites tozaojun 1.8.0when the release is cut, and only then is the thread posted. 1.7.5's actual notes were posted and the file reset months ago — no published 1.7.5 note claims two-probe behaviour. Hand-setting the heading now would break bump-my-version's search match. No further action; escalated to Marvin8.WuMing
Found 4 issue(s). See inline comments below.
@ -16,3 +16,3 @@`zaojun` is a command-line (CLI) tool to check versions of your dependencies as defined in `pyproject.toml` against the latest versions published on [PyPI](https://pypi.org). Full documentation is available at [zaojun.marvin8.zone](https://zaojun.marvin8.zone/latest/).**New in v1.8.0**: Constraint-range advisory scanning — zaojun checks the oldest version your constraint allows, not just the newest release. When that floor has known security advisories, the dependency line gains a 🚨 inline indicator and an `Advisories in declared range` block lists them with a suggested floor that clears every fixable advisory. One extra cached HTTP request per constrained package; `vuln-ignore` rules apply. Range advisories set exit code 1 and are never silenced by `--library` or `--compat-ok`.**New in v1.8.0**: Constraint-range advisory scanning — zaojun checks the oldest version (when the constraint has an inclusive lower bound) and the newest version it allows, not just the latest release. When either has known security advisories, the dependency line gains a 🚨 inline indicator and an `Advisories in declared range` block lists them with a suggested floor that clears every fixable advisory. Up to two extra cached HTTP requests per constrained package; `vuln-ignore` rules apply. Range advisories set exit code 1 and are never silenced by `--library` or `--compat-ok`.docs [MEDIUM]
Version attribution conflicts with the release notes: this line presents constraint-range advisory scanning as
New in v1.8.0, but the same feature is announced in Release-Notes.md under thezaojun 1.7.5heading ("zaojun now also checks the oldest and newest versions each constraint allows"). Both files were edited in this PR, so one of them must be wrong. If the feature shipped in 1.7.5, change this header toNew in v1.7.5; if it is landing in 1.8.0, move the edited Release-Notes bullet under a newzaojun 1.8.0heading instead of editing the 1.7.5 entry.@ -11,3 +11,3 @@---zaojun's vulnerability feed only looked at the newest release on PyPI, so a constraint like `~=2.3` could look fully up to date while still admitting versions with six known advisories. zaojun now fetches the advisory list for each constraint's lower bound as well. Anything the floor is known to be affected by shows up as a 🚨 warning inline plus an "Advisories in declared range" section, with a suggested floor that clears every fixable advisory — the exact gap that hid the httpx2 advisories from library-mode runs. (Advisories introduced after the floor are out of scope for this scan.) Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.zaojun's vulnerability feed only looked at the newest release on PyPI, so a constraint like `~=2.3` could look fully up to date while still admitting versions with six known advisories. zaojun now fetches the advisory list for each constraint's lower bound and its newest allowed release as well. Anything either probe is known to be affected by shows up as a 🚨 warning inline plus an "Advisories in declared range" section, with a suggested floor that clears every fixable advisory — the exact gap that hid the httpx2 advisories from library-mode runs. (Vulnerable ranges that open and close entirely below the newest allowed release remain out of scope.) Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.docs [LOW]
This parenthetical overstates the remaining gap and contradicts the (more precise) wording now used in docs/usage/command-reference.md. A vulnerable range that "opens below the newest allowed release" but starts at or below the constraint's floor (e.g. affected [2.0, 2.7) with floor 2.3 and newest allowed 2.8) is still caught — the floor probe sits inside the affected range. Only ranges that fall strictly between the floor and the newest allowed release escape both probes. Reword to the docs' formulation, e.g. "(Vulnerable ranges that open and close entirely between the lower bound and the newest allowed release remain out of scope.)"
@ -173,0 +168,4 @@JSON endpoint: the constraint's *inclusive lower bound* (`>=X.Y`, exact `==X.Y.Z`,`~=X.Y`), and — when the release history is available — the newest release theconstraint allows, which is what a fresh resolution would install. Exclusive-bound andwildcard shapes (`>2.3`, `==2.3.*`, `<2.0`) have no inclusive lower bound to probe, butdocs [LOW]
The stated rationale is inaccurate for
==2.3.*. A wildcard specifier such as==2.3.*does have an implicit inclusive lower bound (2.3.0, the lowest version matching the wildcard); it is skipped because the implementation only probes specs with a single concrete lower bound, not because no lower bound exists. The previous wording ("provide no single concrete version to query") was correct — restore that rationale, e.g. "Exclusive-bound and wildcard shapes (>2.3,==2.3.*,<2.0) have no single concrete lower-bound version to query, but their newest allowed release is still scanned."@ -215,6 +215,7 @@ async def check_dependency( # noqa: PLR0913, PLR0917client=client,cache=cache,index_url=index_url,releases_map=pypi_data.releases_map,tests [LOW]
This is the wiring that actually enables the new dual-probe behaviour for the checker, yet no test in this diff exercises the checker path: the new/updated tests target zaojun.pypi.get_range_vulnerabilities and zaojun.version.newest_allowed_version directly, so a regression that drops or mis-threads this argument (e.g. reverting to the old call signature) would still pass the whole suite. Add a checker-level test with a mocked package-level payload (releases_map populated) asserting that get_range_vulnerabilities is invoked with the releases map / that the upper-bound advisory surfaces in result.range_vulns.
WuMing
Found 2 issue(s). See inline comments below.
@ -19,3 +19,3 @@"link": "https://osv.dev/vulnerability/PYSEC-2026-3849",}_VERSION_RESPONSE = {_ADVISORY_FLOOR = {tests [LOW]
_ADVISORY_FLOORis a byte-for-byte copy of the existing_ADVISORYconstant (same id, aliases, details,fixed_in, link) — the diff merely renames the reference while keeping both definitions. Two identical fixtures can silently drift apart and make it hard to see which advisory the newest-probe dedupe is meant to overlap with. Reference the single_ADVISORYconstant in both_VERSION_RESPONSE_FLOORand_VERSION_RESPONSE_NEWESTinstead (the union-dedupe test still exercises the overlap).@ -157,0 +209,4 @@async def test_upper_bound_only_spec_probes_newest_allowed(httpx2_mock, mock_cache):"""Without a floor, the newest allowed version is still probed."""httpx2_mock.route(url="https://pypi.org/pypi/mypackage/1.9.0/json").respond(json=_VERSION_RESPONSE_NEWEST)tests [LOW]
The payload served for the 1.9.0 probe is
_VERSION_RESPONSE_NEWEST, whose body declares"info": {"version": "2.12.0"}and whose advisories carryfixed_in: ["2.11.0"]— a version far above the release being probed. Reusing a fixture whose declared version contradicts the requested version makes the intent of this test unclear and means the test would keep passing even if the probe target were wrong in a way that only shows up when the payload's version field is inspected. Define a small response fixture (or helper) whoseinfo.versionmatches the probed release.README.md🔴 Duplicate — sixth re-raise of the version-label substance (full history linked in the wave-2 reply). The Release-Notes file is an unreleased accumulator: the
zaojun 1.7.5heading is the current-version line[tool.bumpversion]rewrites tozaojun 1.8.0at release time, before the thread is posted. No published 1.7.5 note claims this behaviour. No further action; with Marvin8.docs/usage/command-reference.md✅ Fixed in commit
e5d2356— restored the precise rationale: wildcard and exclusive-bound shapes "have no single concrete lower-bound version to query, but their newest allowed release is still scanned."Release-Notes.md✅ Fixed in commit
e5d2356— reworded to "ranges that open and close entirely between the lower bound and the newest allowed release", matching the command reference's precision.src/zaojun/checker.py✅ Fixed in commit
1d13f9b—test_checker_threads_releases_map_into_range_probeasserts the map reachesget_range_vulnerabilitiesand the resulting advisories surface on the result. Verified it fails when the argument is removed.tests/test_get_range_vulnerabilities.py✅ Fixed in commit
a94b037— the test now uses a dedicated_VERSION_RESPONSE_1_9_0fixture whoseinfo.versionmatches the probed release (assertion tightened to the single advisory it carries).tests/test_get_range_vulnerabilities.py✅ Fixed in commit
a94b037—_ADVISORY_FLOORremoved;_VERSION_RESPONSE_FLOORand_VERSION_RESPONSE_NEWESTboth reference_ADVISORY, so the union-dedupe overlap stays defined in one place.New commits pushed, approval review dismissed automatically according to repository settings
WuMing
Found 4 issue(s). See inline comments below.
@ -5,3 +5,3 @@zaojun 1.7.5- Constraint-range advisory scanning: zaojun now also checks the oldest version each constraint allows — 🚨 warns when that floor is known to be affected by vulnerabilities, and suggests a floor that clears them- Constraint-range advisory scanning: zaojun now also checks the oldest and newest versions each constraint allows — 🚨 warns when either is known to be affected by vulnerabilities, and suggests a floor that clears themdocs [MEDIUM]
This bullet sits under the unchanged
zaojun 1.7.5heading (line 5) but is being rewritten to describe the new dual-probe behaviour (oldest and newest allowed version), while README.md line 18 advertises constraint-range advisory scanning as "New in v1.8.0". Both cannot be true. If the newest-allowed probe ships in 1.8.0, this change belongs in a new 1.8.0 entry and the 1.7.5 entry must be left as released; if 1.7.5 has not shipped yet, README's "New in v1.8.0" is wrong instead. Reconcile the version attribution: either add a new release-notes section for the release that contains this change or fix the README's version number.@ -11,3 +11,3 @@---zaojun's vulnerability feed only looked at the newest release on PyPI, so a constraint like `~=2.3` could look fully up to date while still admitting versions with six known advisories. zaojun now fetches the advisory list for each constraint's lower bound as well. Anything the floor is known to be affected by shows up as a 🚨 warning inline plus an "Advisories in declared range" section, with a suggested floor that clears every fixable advisory — the exact gap that hid the httpx2 advisories from library-mode runs. (Advisories introduced after the floor are out of scope for this scan.) Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.zaojun's vulnerability feed only looked at the newest release on PyPI, so a constraint like `~=2.3` could look fully up to date while still admitting versions with six known advisories. zaojun now fetches the advisory list for each constraint's lower bound and its newest allowed release as well. Anything either probe is known to be affected by shows up as a 🚨 warning inline plus an "Advisories in declared range" section, with a suggested floor that clears every fixable advisory — the exact gap that hid the httpx2 advisories from library-mode runs. (Vulnerable ranges that open and close entirely between the lower bound and the newest allowed release remain out of scope.) Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.docs [LOW]
"for each constraint's lower bound" overstates the scan. As the updated command reference now states, constraints without a concrete inclusive lower bound (e.g.
<2.0,>2.3,==2.3.*) have no lower-bound probe at all and are probed only at their newest allowed release. Reword to something like "for each constraint's lower bound where one exists, and its newest allowed release" so the release note does not promise a floor query that never happens.@ -173,0 +168,4 @@JSON endpoint: the constraint's *inclusive lower bound* (`>=X.Y`, exact `==X.Y.Z`,`~=X.Y`), and — when the release history is available — the newest release theconstraint allows, which is what a fresh resolution would install. Exclusive-bound andwildcard shapes (`>2.3`, `==2.3.*`, `<2.0`) have no single concrete lower-bound versiondocs [LOW]
The rewrite loses precision about why these shapes are skipped.
>2.3does have a single concrete bound (2.3) — the previous text correctly explained that the probe is skipped because the bound itself is excluded and querying it would produce false positives. Grouping<2.0(an upper-bound-only shape with no lower bound whatsoever) under the same "Exclusive-bound" label at line 170 further blurs two different cases. Consider splitting the exclusive-lower-bound case from the no-lower-bound/wildcard cases and keeping the false-positive rationale for>2.3.@ -193,9 +193,13 @@ without any listed fix cannot be cleared by a floor raise.A failed range fetch produces no advisories for that package and prints a warning todocs [LOW]
With the new dual-probe model there are up to two version-level fetches per package, so the singular "a failed range fetch produces no advisories for that package" is now ambiguous: does a failure of only the newest-allowed fetch discard the floor-probe results too, or are the probes independent? Clarify the failure semantics here (e.g. "if a probe fails, its advisories are omitted and a warning is printed") so the degradation description matches the two-request behaviour described at lines 167–172.
WuMing
Found 3 issue(s). See inline comments below.
@ -16,3 +16,3 @@`zaojun` is a command-line (CLI) tool to check versions of your dependencies as defined in `pyproject.toml` against the latest versions published on [PyPI](https://pypi.org). Full documentation is available at [zaojun.marvin8.zone](https://zaojun.marvin8.zone/latest/).**New in v1.8.0**: Constraint-range advisory scanning — zaojun checks the oldest version your constraint allows, not just the newest release. When that floor has known security advisories, the dependency line gains a 🚨 inline indicator and an `Advisories in declared range` block lists them with a suggested floor that clears every fixable advisory. One extra cached HTTP request per constrained package; `vuln-ignore` rules apply. Range advisories set exit code 1 and are never silenced by `--library` or `--compat-ok`.**New in v1.8.0**: Constraint-range advisory scanning — zaojun checks the lowest and newest releases your constraint actually allows, not just the latest release. When either has known security advisories, the dependency line gains a 🚨 inline indicator and an `Advisories in declared range` block lists them with a suggested floor that clears every fixable advisory. Up to two extra cached HTTP requests per constrained package; `vuln-ignore` rules apply. Range advisories set exit code 1 and are never silenced by `--library` or `--compat-ok`.docs [LOW]
Version attribution conflicts with Release-Notes.md: this README highlight says the constraint-range advisory scanning is "New in v1.8.0", but the changelog entry that describes the very same feature is headed "zaojun 1.7.5". Users reading both will see two different releases for one feature. Verify which release actually ships it and align the two files (either update the README to the shipping version or move/relabel the changelog entry).
@ -188,2 +191,2 @@Advisories already reported against the latest release are not repeated (matching by IDor alias). Range advisories set exit code 1 and apply the same `vuln-ignore` rules. TheAdvisories already reported against the latest release, or reported by both thelowest-allowed and newest-allowed probes, are not repeated (matching by ID or alias). Range advisories set exit code 1 and apply the same `vuln-ignore` rules. Thedocs [LOW]
Formatting regression: this line is roughly 160 characters because the text that used to start a new line ("Range advisories set exit code 1 and apply the same
vuln-ignorerules. The") was merged into the previous sentence. Every other paragraph in this file wraps at ~80–90 columns, so this line breaks the file's convention and is hard to diff/review. Re-wrap the sentence as its own continuation line(s) without changing the wording.@ -199,0 +200,4 @@not advisory probes. A too-new release carrying advisories is precisely the signal`--min-age` users want surfaced, so the newest-allowed probe ignores the age window.Known limitation: a vulnerable *mid-range island* — introduced and fixed entirelydocs [MEDIUM]
The precondition given for the known blind spot ("introduced and fixed entirely below the newest allowed release") is inaccurate/incomplete and contradicts Release-Notes.md. An island introduced below the newest allowed release but covering the lowest allowed release is NOT missed — the lowest-allowed probe lands inside it and reports the advisory. Only an island lying strictly between the two probes is invisible. Release-Notes.md line 13 words this correctly ("entirely between the lowest and newest allowed probes"); make this sentence match, e.g. "introduced and fixed entirely between the lowest and newest allowed releases".
README.md✅ Updated README to v1.7.5 to match Release-Notes.md and the current pyproject.toml version.
docs/usage/command-reference.md✅ Re-wrapped the long line so the paragraph follows the file's wrapping convention.
docs/usage/command-reference.md✅ Updated the wording to 'introduced and fixed entirely between the lowest and newest allowed releases', matching Release-Notes.md.
WuMing
Found 3 issue(s). See inline comments below.
@ -16,3 +16,3 @@`zaojun` is a command-line (CLI) tool to check versions of your dependencies as defined in `pyproject.toml` against the latest versions published on [PyPI](https://pypi.org). Full documentation is available at [zaojun.marvin8.zone](https://zaojun.marvin8.zone/latest/).**New in v1.8.0**: Constraint-range advisory scanning — zaojun checks the oldest version your constraint allows, not just the newest release. When that floor has known security advisories, the dependency line gains a 🚨 inline indicator and an `Advisories in declared range` block lists them with a suggested floor that clears every fixable advisory. One extra cached HTTP request per constrained package; `vuln-ignore` rules apply. Range advisories set exit code 1 and are never silenced by `--library` or `--compat-ok`.**New in v1.7.5**: Constraint-range advisory scanning — zaojun checks the lowest and newest releases your constraint actually allows, not just the latest release. When either has known security advisories, the dependency line gains a 🚨 inline indicator and an `Advisories in declared range` block lists them with a suggested floor that clears every fixable advisory. Up to two extra cached HTTP requests per constrained package; `vuln-ignore` rules apply. Range advisories set exit code 1 and are never silenced by `--library` or `--compat-ok`.docs [MEDIUM]
The "New in" release label is changed from v1.8.0 to v1.7.5 in the same PR that rewrites the already-existing
zaojun 1.7.5entry in Release-Notes.md to describe the new dual-probe behaviour. If 1.7.5 is a shipped release, this retroactively reattributes a new feature to an older version and rewrites published release notes; if it is not shipped, the README/release-notes pair should be reconciled by introducing a new version heading for the dual-probe change rather than editing the previous feature's entry. Pick one version and state it consistently: either keep the README label at the next release (v1.8.0) and add a new Release-Notes entry, or state explicitly that v1.7.5 is unreleased and carries both probe revisions.@ -173,0 +170,4 @@For example, `~=0.0` is probed at the lowest published `0.0.x` rather than a literal`0.0` that was never released. When no release history is available, the probe fallsback to the constraint's inclusive lower bound (`>=X.Y`, exact `==X.Y.Z`, `~=X.Y`).Exclusive-bound and wildcard shapes (`>2.3`, `==2.3.*`, `<2.0`) have no single concretedocs [LOW]
This sentence reads as self-contradictory: it first says exclusive-bound and wildcard shapes "have no single concrete lower-bound version", then says they "are still scanned at their lowest and newest allowed releases" (which is itself a concrete version). Suggest rephrasing to make the point that these shapes have no literal bound to fall back on when no release history exists, so they are covered only via release-history probing — e.g. "Exclusive-bound and wildcard shapes (
>2.3,==2.3.*,<2.0) have no inclusive lower bound to fall back on, but when release history is available they are still scanned at their lowest and newest allowed releases."@ -190,4 +194,4 @@suggested floor is the highest per-advisory minimum `fixed_in` version; advisorieswithout any listed fix cannot be cleared by a floor raise.A failed range fetch produces no advisories for that package and prints a warning todocs [LOW]
This paragraph was not updated for the change from one probe to two. It still says a failed range fetch yields "no advisories for that package", but with separate lowest-allowed and newest-allowed probes a failure of only one request should not necessarily discard the advisories found by the other. Clarify the failure semantics (e.g. "if either probe's fetch fails, that probe contributes no advisories and a warning is printed to stderr") so readers know whether a single failed request suppresses the package's range advisories.
docs/usage/command-reference.md✅ Updated the failure wording to clarify that a failed fetch affects only that probe, and the other probe still runs.
docs/usage/command-reference.md✅ Rephrased to say these shapes have no inclusive lower bound to fall back on, but are covered via release-history probing.
README.md✅ Moved the dual-probe refinement to a new v1.8.0 Release-Notes entry and restored the README label to v1.8.0. v1.7.5 is already shipped with the base range scan.
WuMing
Found 4 issue(s). See inline comments below.
@ -11,3 +11,3 @@---zaojun's vulnerability feed only looked at the newest release on PyPI, so a constraint like `~=2.3` could look fully up to date while still admitting versions with six known advisories. zaojun now fetches the advisory list for each constraint's lower bound as well. Anything the floor is known to be affected by shows up as a 🚨 warning inline plus an "Advisories in declared range" section, with a suggested floor that clears every fixable advisory — the exact gap that hid the httpx2 advisories from library-mode runs. (Advisories introduced after the floor are out of scope for this scan.) Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.zaojun's constraint-range advisory scan previously probed a spec's lower bound and its newest allowed release. It now snaps the lower-bound probe to the lowest release the constraint actually allows, taken from the release history — so a spec like `~=0.0` probes the lowest published `0.0.x` rather than a literal `0.0` that was never released. Advisories are also now deduplicated by ID and alias across both probes, so the same vulnerability published under a GHSA and a PYSEC id is reported once. The existing mid-range-island caveat still applies: a vulnerable range that opens and closes entirely between the lowest and newest allowed probes remains out of scope. Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.docs [HIGH]
The opening sentence contradicts this file's own bullet on line 7 ("zaojun now probes the lowest and newest releases each constraint actually allows") and the README ("New in v1.8.0 ... checks the lowest and newest releases your constraint actually allows"): it asserts the scan "previously probed a spec's lower bound and its newest allowed release", which would mean the newest-allowed probe already existed and the only change is snapping the lower probe. That also drops the release's headline change from the notes. Fix: state that the scan previously probed only the lower bound and now probes both the lowest and newest allowed releases (both snapped to real releases from the release history), then keep the alias-dedupe and mid-range-island sentences.
@ -173,0 +170,4 @@For example, `~=0.0` is probed at the lowest published `0.0.x` rather than a literal`0.0` that was never released. When no release history is available, the probe fallsback to the constraint's inclusive lower bound (`>=X.Y`, exact `==X.Y.Z`, `~=X.Y`).Exclusive-bound and wildcard shapes (`>2.3`, `==2.3.*`, `<2.0`) have nodocs [LOW]
Grouping
<2.0under "Exclusive-bound and wildcard shapes" is misleading:<2.0is an upper-bound-only spec with no lower bound at all, whereas>2.3does have a lower bound that is merely exclusive, and==2.3.*conventionally implies a lower bound of 2.3.0 under PEP 440 prefix matching, so saying it has "no inclusive lower bound" is debatable. Reword to separate the cases, e.g. "Specs with no usable lower bound (<2.0,==2.3.*) or with only an exclusive lower bound (>2.3) fall back to release-history selection instead."@ -400,6 +409,13 @@ Library mode is designed for Python packages that are consumed by other packages`--min-age` works normally in library mode and applies to incompatible updates.**Dev dependencies**: the constraint-hygiene rules above apply to whicheverdocs [LOW]
This dev-dependency paragraph is unrelated to the change described in the PR (probing the newest allowed release) and is not mentioned in the PR summary; it is also inserted between the
--min-agenote and the**Examples**:block under what appears to be the library-mode discussion, with no heading of its own, so readers may not see which section it belongs to. Consider moving it into a dedicated**Dev dependencies**section (or a separate PR) and double-check the example wording (uv~=0.11.15also caps the range at<0.12, not just raises the floor).@ -175,3 +169,3 @@try:response = await client.get(f"{index_url.rstrip('/')}/{package_name}/{lower_bound}/json")response = await client.get(f"{index_url.rstrip('/')}/{package_name}/{version}/json")security [MEDIUM]
A10 SSRF:
index_urlis accepted from the caller and used directly to build the HTTP request with no allow-list validation of scheme/host. If an untrusted user can influence this value, the client can be pointed at internal services or cloud metadata endpoints. Additionally,package_nameandversionare interpolated into the URL path without percent-encoding. Remediate by validatingindex_urlagainst an allow-list of permitted PyPI-compatible endpoints and constructing the URL with encoded path segments.Release-Notes.md✅ Reworded the bullet to focus on the lower-bound snap and alias dedupe, and updated the prose to state that the scan previously probed a spec's lower bound and newest allowed release, with the lower-bound probe now snapping to the lowest allowed release.
docs/usage/command-reference.md✅ Reworded to separate specs with no usable lower bound (
<2.0,==2.3.*) from those with only an exclusive lower bound (>2.3), and clarified they use release-history selection when available.docs/usage/command-reference.md✅ Moved the dev-deps note into a dedicated
#### Dev dependenciessubsection and clarified that raising the floor with~=does not change the compatible-range width.src/zaojun/pypi.py✅ Percent-encoded
package_nameandversionwhen building PyPI URLs to prevent path injection. An allow-list forindex_urlwould break private-index support because--index-urlis operator-controlled; if you want a stricter allow-list, that needs a design decision and possibly new config. Awaiting your ruling on that part.WuMing
Found 3 issue(s). See inline comments below.
@ -6,2 +5,3 @@zaojun 1.8.0- Constraint-range advisory scanning: zaojun now also checks the oldest version each constraint allows — 🚨 warns when that floor is known to be affected by vulnerabilities, and suggests a floor that clears them- Constraint-range advisory scan refined: the lower-bound probe now snaps to the lowest allowed release, and advisories are deduplicated by ID and alias across probesdocs [MEDIUM]
The 1.8.0 summary bullet describes only the lower-bound snap and the alias/ID dedupe; it never mentions that the scan now also probes the newest allowed release, which is the primary user-visible change of this release (and the likely reason users upgrade). Add it, e.g. "...scan refined: it now probes both the lowest and the newest release each constraint allows, snaps the lower-bound probe to the lowest allowed release, and deduplicates advisories by ID and alias across probes".
@ -11,3 +11,3 @@---zaojun's vulnerability feed only looked at the newest release on PyPI, so a constraint like `~=2.3` could look fully up to date while still admitting versions with six known advisories. zaojun now fetches the advisory list for each constraint's lower bound as well. Anything the floor is known to be affected by shows up as a 🚨 warning inline plus an "Advisories in declared range" section, with a suggested floor that clears every fixable advisory — the exact gap that hid the httpx2 advisories from library-mode runs. (Advisories introduced after the floor are out of scope for this scan.) Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.zaojun's constraint-range advisory scan previously probed a spec's lower bound and its newest allowed release. The lower-bound probe now snaps to the lowest release the constraint actually allows, taken from the release history — so a spec like `~=0.0` probes the lowest published `0.0.x` rather than a literal `0.0` that was never released. Advisories are also now deduplicated by ID and alias across both probes, so the same vulnerability published under a GHSA and a PYSEC id is reported once. The existing mid-range-island caveat still applies: a vulnerable range that opens and closes entirely between the lowest and newest allowed probes remains out of scope. Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.docs [HIGH]
Incorrect description of the previous behaviour. This change adds the newest-allowed probe: per the PR, the scan "previously probed only the spec's lower bound", and probing the lowest/newest allowed releases is the new behaviour. The note instead claims the previous scan already probed "a spec's lower bound and its newest allowed release", which would leave readers (and the changelog) unaware that probing the newest allowed release is the headline change of this release. Reword the opening, e.g. "zaojun's constraint-range advisory scan previously probed only a spec's lower bound; it now probes both the lowest and the newest release each constraint allows."
@ -403,0 +416,4 @@when `--groups` is used). Dev dependencies do not ship to consumers, so raisingtheir floor to exclude known-vulnerable releases is a normal hygiene bump, not alibrary-mode violation. For example, moving from `uv~=0.11` to`uv~=0.11.15` raises the minimum without changing the compatible range widthdocs [HIGH]
Incorrect PEP 440 semantics.
~=0.11is a compatible-release clause meaning>=0.11, ==0.*(upper bound<1.0), whereas~=0.11.15means>=0.11.15, ==0.11.*(upper bound<0.12), so moving between them does change the range width — the example contradicts its own point that the width is unchanged. Use a like-for-like pair, e.g. moving fromuv~=0.11.0touv~=0.11.15(both>=x, <0.12), or fromuv>=0.11touv>=0.11.15(top unchanged). Also note the parenthetical(<0.12)on the next line only applies to the new constraint, not to the old one.