Constraint-range advisory scanning, dependency refresh, and Release-Notes reset recipe #41

Manually merged
marvin8 merged 8 commits from feat/issue-37-39-stage2-and-vuln-range into main 2026-09-14 01:31:47 +00:00
Collaborator

Three pieces of work in one PR, as requested:

Constraint-range advisory scanning (#39)

The package-level PyPI endpoint only lists advisories against the newest release, so a constraint like ~=2.3 looked fully up to date while still admitting known-vulnerable versions (exactly what hid the httpx2 advisories from a library-mode run against longwei 2.1.2 — six advisories, invisible). zaojun now fetches the version-level PyPI JSON response for each spec's lower bound (>=, ==, ~=) and reports advisories affecting it:

  • 🚨 inline indicator (always shown, even with --short) + Advisories in declared range block with a suggested floor (highest per-advisory minimum fixed_in)
  • Exit code 1; respects vuln-ignore; never silenced by --library or --compat-ok
  • Cached under a package@lowerbound key with the same 24 h TTL; failing fetches degrade to no advisories with a stderr warning; JSON gains range_vulnerabilities + suggested_floor
  • TDD: 22 new tests; docs (README, docs/usage/command-reference.md) and Release-Notes updated
  • Verified against the live longwei 2.1.2 case: all six httpx2 advisories now surface with suggested floor 2.12.0

Known limitation (documented): advisories introduced after the floor aren't caught by a lower-bound-only fetch — a full-range OSV query would be the follow-up.

Dependency refresh + security posture (#37)

Most of #37 landed via merged PR #38 (Codeberg references clean; stale ROADMAP trackers deliberately removed in 952d57b, so tracker completion = closing the issue with stage 1 = PR #36 and stage 2 = PR #38 recorded as merged). What was genuinely stale after ~4 weeks:

  • Deps: zaojun --format json (min-age 7) → cyclopts ~=4.25.0, complexipy ~=8.0.0, mkdocstrings-python ~=2.0.8, prek ~=0.5.2, ruff ~=0.16.6, ty ~=0.0.78, uv ~=0.12.10; pre-commit revs aligned; uv.lock + pylock.toml regenerated
  • Security: pysentry 0.5.0 — 95 packages scanned, 0 vulnerabilities. gitleaks runs on this PR's CI.

Release-Notes reset recipe (#40)

just fediverse-post no longer owns the reset logic: a new just reset-release-notes recipe does the template substitution, and fediverse-post delegates to it. just release untouched.

Checks

ruff check/format, ty, complexipy, interrogate, deptry clean; 312 tests pass (95% coverage, min 90); taibai thread --dry-run valid.

Three pieces of work in one PR, as requested: ## Constraint-range advisory scanning (#39) The package-level PyPI endpoint only lists advisories against the newest release, so a constraint like `~=2.3` looked fully up to date while still admitting known-vulnerable versions (exactly what hid the httpx2 advisories from a library-mode run against longwei 2.1.2 — six advisories, invisible). zaojun now fetches the version-level PyPI JSON response for each spec's lower bound (`>=`, `==`, `~=`) and reports advisories affecting it: - 🚨 inline indicator (always shown, even with `--short`) + `Advisories in declared range` block with a suggested floor (highest per-advisory minimum `fixed_in`) - Exit code 1; respects `vuln-ignore`; never silenced by `--library` or `--compat-ok` - Cached under a `package@lowerbound` key with the same 24 h TTL; failing fetches degrade to no advisories with a stderr warning; JSON gains `range_vulnerabilities` + `suggested_floor` - TDD: 22 new tests; docs (README, `docs/usage/command-reference.md`) and Release-Notes updated - Verified against the live longwei 2.1.2 case: all six httpx2 advisories now surface with suggested floor 2.12.0 Known limitation (documented): advisories introduced after the floor aren't caught by a lower-bound-only fetch — a full-range OSV query would be the follow-up. ## Dependency refresh + security posture (#37) Most of #37 landed via merged PR #38 (Codeberg references clean; stale ROADMAP trackers deliberately removed in 952d57b, so tracker completion = closing the issue with stage 1 = PR #36 and stage 2 = PR #38 recorded as merged). What was genuinely stale after ~4 weeks: - **Deps**: `zaojun --format json` (min-age 7) → cyclopts ~=4.25.0, complexipy ~=8.0.0, mkdocstrings-python ~=2.0.8, prek ~=0.5.2, ruff ~=0.16.6, ty ~=0.0.78, uv ~=0.12.10; pre-commit revs aligned; `uv.lock` + `pylock.toml` regenerated - **Security**: pysentry 0.5.0 — 95 packages scanned, 0 vulnerabilities. gitleaks runs on this PR's CI. ## Release-Notes reset recipe (#40) `just fediverse-post` no longer owns the reset logic: a new `just reset-release-notes` recipe does the template substitution, and `fediverse-post` delegates to it. `just release` untouched. ## Checks ruff check/format, ty, complexipy, interrogate, deptry clean; 312 tests pass (95% coverage, min 90); `taibai thread --dry-run` valid.
Split the version-substituted template reset out of fediverse-post into
a dedicated reset-release-notes recipe so it can run without posting.
fediverse-post now delegates the reset. Refs #40
zaojun --format json (min-age 7): cyclopts ~=4.25.0, complexipy ~=8.0.0,
mkdocstrings-python ~=2.0.8, prek ~=0.5.2, ruff ~=0.16.6, ty ~=0.0.78,
uv ~=0.12.10 (aged_version floors for too_new, latest otherwise).
Pre-commit revs aligned: ruff v0.16.6, ty v0.0.78, uv 0.12.10,
complexipy v8.0.0. pylock.toml regenerated. Refs #37
flag advisories within the declared constraint range
All checks were successful
/ gitleaks (pull_request) Successful in 22s
/ checks (pull_request) Successful in 2m13s
/ publish (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ pr-review (pull_request) Successful in 2m20s
1dfc71e5ef
The package-level PyPI endpoint only lists advisories against the newest
release, so a constraint like ~=2.3 looked fully up to date while still
admitting known-vulnerable versions. zaojun now fetches the version-level
response for each spec's lower bound (>=, ==, ~=) and reports advisories
affecting it: 🚨 inline indicator, an 'Advisories in declared range' block,
a suggested floor (highest per-advisory minimum fixed_in), and exit code 1.
Range advisories respect vuln-ignore and are never silenced by --library
or --compat-ok. Cached under a package@lowerbound key with the same TTL.
Failing range fetches degrade silently. Refs #39
forgejo-actions left a comment

WuMing

Found 9 issue(s). See inline comments below.

## WuMing Found **9** issue(s). See inline comments below.
README.md Outdated
@ -15,6 +15,8 @@
`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**: 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.

docs [MEDIUM]

This new item breaks the convention used by the two entries below it, which are both labelled with a version (**New in v1.6.0**, **New in v1.5.0**). A bare **New** leaves the reader unable to tell which release introduced the feature and will need re-editing on the next release. Label it with the release it ships in (e.g. **New in v1.7.5**, matching Release-Notes.md). While you are here, consider mentioning the same behaviour the other entries state for parity: that range advisories set exit code 1 and are never silenced by --library/--compat-ok (currently only vuln-ignore is mentioned).

**docs** [MEDIUM] This new item breaks the convention used by the two entries below it, which are both labelled with a version (`**New in v1.6.0**`, `**New in v1.5.0**`). A bare `**New**` leaves the reader unable to tell which release introduced the feature and will need re-editing on the next release. Label it with the release it ships in (e.g. `**New in v1.7.5**`, matching `Release-Notes.md`). While you are here, consider mentioning the same behaviour the other entries state for parity: that range advisories set exit code 1 and are never silenced by `--library`/`--compat-ok` (currently only `vuln-ignore` is mentioned). <!-- wuming:sha256:d74dcba907f4e7f67ec073ec085197cc8aa50ddec0623bcce55737e7a5ce4f1c -->
Release-Notes.md Outdated
@ -8,3 +10,4 @@
---
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 six known-vulnerable versions. zaojun now fetches the advisory list for each constraint's lower bound as well. Anything your range still allows 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. Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.

docs [LOW]

"admitting six known-vulnerable versions" misstates the verified case: the longwei 2.1.2 run surfaced six advisories (see the README wording, "six advisories"), not six distinct vulnerable versions. Reword to something like "while still admitting versions with six known advisories" so the release notes agree with the README and the PR description.

**docs** [LOW] "admitting six known-vulnerable versions" misstates the verified case: the longwei 2.1.2 run surfaced six *advisories* (see the README wording, "six advisories"), not six distinct vulnerable versions. Reword to something like "while still admitting versions with six known advisories" so the release notes agree with the README and the PR description. <!-- wuming:sha256:eb9cfed0c2629a75b88667bebf5f44b01d73c329248a20ec5fffd1a967611dff -->
@ -160,0 +165,4 @@
package-level PyPI endpoint only lists advisories against the newest release, so a
constraint like `~=2.3` can look fully up to date while still admitting known-vulnerable
releases. zaojun therefore also fetches the version-level PyPI JSON response for each
spec's lower bound (`>=`, `==`, `~=`) and reports advisories that affect it:

docs [LOW]

The operator list (>=, ==, ~=) reads as exhaustive but is never stated as such, so a user with a >2.3 (or other) constraint cannot tell whether their range is scanned. For >2.3 the bound version is not itself allowed by the constraint, so scanning it would produce false positives; whatever the actual behaviour (skip vs. scan), state it explicitly here, e.g. "constraints whose operator excludes the bound version (>) are not scanned".

**docs** [LOW] The operator list (`>=`, `==`, `~=`) reads as exhaustive but is never stated as such, so a user with a `>2.3` (or other) constraint cannot tell whether their range is scanned. For `>2.3` the bound version is not itself allowed by the constraint, so scanning it would produce false positives; whatever the actual behaviour (skip vs. scan), state it explicitly here, e.g. "constraints whose operator excludes the bound version (`>`) are not scanned". <!-- wuming:sha256:eade93c7d44bcfb0a7b11ea1a6e8b12bbbf99c44109a8ce5a2d4817806b89528 -->
@ -160,0 +179,4 @@
```
The 🚨 inline indicator is always shown (even in `--short` mode); the block follows the
same suppression rules as `Vulnerabilities`. Advisories already reported against the

docs [LOW]

"the block follows the same suppression rules as Vulnerabilities" is ambiguous, because the preceding sentence has just said the inline indicator is always shown even in --short, while Vulnerabilities suppression rules are not spelled out in this section. A reader cannot tell whether the Advisories in declared range detail block is hidden in --short/--library or not, which conflicts with the "never silenced by --library or --compat-ok" claim three lines later. Replace with an explicit statement of exactly which flags hide the block and which do not.

**docs** [LOW] "the block follows the same suppression rules as `Vulnerabilities`" is ambiguous, because the preceding sentence has just said the inline indicator is always shown even in `--short`, while `Vulnerabilities` suppression rules are not spelled out in this section. A reader cannot tell whether the `Advisories in declared range` detail block is hidden in `--short`/`--library` or not, which conflicts with the "never silenced by `--library` or `--compat-ok`" claim three lines later. Replace with an explicit statement of exactly which flags hide the block and which do not. <!-- wuming:sha256:4d895ea260721e43a215c742dc00384783c4b6a22d7b1e692c6548889bfea4d6 -->
@ -516,6 +547,8 @@ human-readable text. The JSON format is stable and suitable for scripting and CI
`groups` is only present when `--groups` is used. `--short` has no effect in JSON mode.
`vulnerabilities` is only present on records that have open (non-ignored) advisories.
`range_vulnerabilities` and `suggested_floor` are only present on records whose

docs [LOW]

The two new JSON fields are named but never described: neither the shape of range_vulnerabilities (array of objects with which keys — advisory id, aliases, fixed_in?) nor the type of suggested_floor (string, like 2.12.0) is documented. Also, the stated presence condition covers only range_vulnerabilities; suggested_floor is additionally omitted when no in-range advisory has a listed fixed_in, as noted in the section above. Document the element shape and the exact presence conditions for each field.

**docs** [LOW] The two new JSON fields are named but never described: neither the shape of `range_vulnerabilities` (array of objects with which keys — advisory id, aliases, `fixed_in`?) nor the type of `suggested_floor` (string, like `2.12.0`) is documented. Also, the stated presence condition covers only `range_vulnerabilities`; `suggested_floor` is additionally omitted when no in-range advisory has a listed `fixed_in`, as noted in the section above. Document the element shape and the exact presence conditions for each field. <!-- wuming:sha256:057ed0a67ac39c847e90e861b40f4628c0b98d6c3a80380d74deb2e5c503d7ee -->
@ -207,0 +217,4 @@
index_url=index_url,
)
latest_ids = {vuln.id for vuln in result.vulns} | {alias for vuln in result.vulns for alias in vuln.aliases}
result.range_vulns = [vuln for vuln in range_vulns if vuln.id not in latest_ids]

code [LOW]

Deduplication checks only the range advisory's primary id against latest_ids, but latest_ids also contains aliases. If a range advisory has a different primary ID but an alias matching a latest advisory ID or alias, it will still appear in both vulns and range_vulns. Include the range advisory's aliases in the membership check to avoid duplicate advisories.

**code** [LOW] Deduplication checks only the range advisory's primary `id` against `latest_ids`, but `latest_ids` also contains aliases. If a range advisory has a different primary ID but an alias matching a latest advisory ID or alias, it will still appear in both `vulns` and `range_vulns`. Include the range advisory's aliases in the membership check to avoid duplicate advisories. <!-- wuming:sha256:a17656c400ad551bd31c21ae7f92abfb51c22d8c2c552ae1b35127bcd437ed5f -->
@ -124,0 +176,4 @@
response = await client.get(f"{index_url.rstrip('/')}/{package_name}/{lower_bound}/json")
response.raise_for_status()
payload = response.json()
except (httpx2.HTTPStatusError, httpx2.RequestError, json.JSONDecodeError):

security [MEDIUM]

The range-advisory fetch catches HTTP/protocol/JSON errors and returns an empty list without logging. Since this is a security-relevant vulnerability scan, a failed fetch silently degrades to "no advisories", potentially hiding known vulnerable versions. Log a warning/error and mark the scan as degraded. A09:

**security** [MEDIUM] The range-advisory fetch catches HTTP/protocol/JSON errors and returns an empty list without logging. Since this is a security-relevant vulnerability scan, a failed fetch silently degrades to "no advisories", potentially hiding known vulnerable versions. Log a warning/error and mark the scan as degraded. A09: <!-- wuming:sha256:d017e60b78042ff068f927ff8bb391b8106170c7cb673610aff1dcb44a318035 -->
@ -17,0 +32,4 @@
"""
minima: list[Version] = []
for advisory in advisories:
parsed = [parse_version(v) for v in advisory.fixed_in]

code [MEDIUM]

parse_version may raise packaging.version.InvalidVersion for malformed/non-PEP-440 entries in fixed_in. Since suggest_floor is called inside check_dependency's try block, that ValueError can turn a best-effort range advisory scan into a package-check error instead of simply ignoring the unfixable advisory. Catch InvalidVersion per entry and skip invalid versions.

**code** [MEDIUM] `parse_version` may raise `packaging.version.InvalidVersion` for malformed/non-PEP-440 entries in `fixed_in`. Since `suggest_floor` is called inside `check_dependency`'s try block, that ValueError can turn a best-effort range advisory scan into a package-check error instead of simply ignoring the unfixable advisory. Catch `InvalidVersion` per entry and skip invalid versions. <!-- wuming:sha256:3fea4f0714143c4219a341d23cd8d7fbb7c3abf09f3513574fd5c56472f55100 -->
@ -88,0 +127,4 @@
"""
if not result.range_vulns:
return
result.range_vulns = _filter_vulns(

code [MEDIUM]

After _filter_vulns filters out ignored range advisories, suggested_floor is not recomputed. It was previously set from the unfiltered list in check_dependency; if the ignored advisory had the highest minimum fixed version, the suggested floor is stale/higher than necessary, and if all are ignored a suggested_floor can remain in JSON while range_vulnerabilities is absent. Recompute via suggest_floor(result.range_vulns) or set None when empty.

**code** [MEDIUM] After `_filter_vulns` filters out ignored range advisories, `suggested_floor` is not recomputed. It was previously set from the unfiltered list in `check_dependency`; if the ignored advisory had the highest minimum fixed version, the suggested floor is stale/higher than necessary, and if all are ignored a `suggested_floor` can remain in JSON while `range_vulnerabilities` is absent. Recompute via `suggest_floor(result.range_vulns)` or set `None` when empty. <!-- wuming:sha256:05297cb86020708fbca810b9edca3930a9ce491e06df2772ebc8b7b033458466 -->
🐛 recompute range floor after suppression and warn on degraded scans
Some checks failed
/ gitleaks (pull_request) Successful in 22s
/ publish (pull_request) Has been cancelled
/ deploy-docs (pull_request) Has been cancelled
/ pr-review (pull_request) Has been cancelled
/ checks (pull_request) Has been cancelled
082eb7bd6a
WuMing wave 1 on PR #41: suggested_floor is now recomputed from the
surviving range advisories after vuln-ignore suppression (cleared to None
when none survive); suggest_floor skips non-PEP-440 fixed_in entries
instead of raising InvalidVersion; range-fetch dedupe also matches
aliases; a failed range fetch prints a stderr warning so a silently
degraded scan is visible. Docs: README version label + exit-code parity,
Release-Notes wording, command-reference scan conditions, explicit
suppression rules and JSON field shapes.
agent-pi force-pushed feat/issue-37-39-stage2-and-vuln-range from 082eb7bd6a
Some checks failed
/ gitleaks (pull_request) Successful in 22s
/ publish (pull_request) Has been cancelled
/ deploy-docs (pull_request) Has been cancelled
/ pr-review (pull_request) Has been cancelled
/ checks (pull_request) Has been cancelled
to 0148cdfed6
All checks were successful
/ gitleaks (pull_request) Successful in 19s
/ checks (pull_request) Successful in 2m1s
/ publish (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ pr-review (pull_request) Successful in 2m12s
2026-09-13 21:16:36 +00:00
Compare
Author
Collaborator

src/zaojun/pypi.py

security [MEDIUM] — silent degradation…

Fixed in 0148cdf — a failed range fetch now prints a warning to stderr (zaojun: range-advisory fetch for <package> <floor> failed (…) — scan degraded), so a silently-degraded scan is visible. Covered by test_http_error_warns_on_stderr.

[`src/zaojun/pypi.py`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3138) > **security** [MEDIUM] — silent degradation… ✅ Fixed in `0148cdf` — a failed range fetch now prints a warning to stderr (`zaojun: range-advisory fetch for <package> <floor> failed (…) — scan degraded`), so a silently-degraded scan is visible. Covered by `test_http_error_warns_on_stderr`.
Author
Collaborator

src/zaojun/vulns.py

code [MEDIUM] — suggested_floor not recomputed after filtering…

Fixed in 0148cdf_apply_range_vuln_filter now recomputes suggested_floor via suggest_floor(result.range_vulns) after suppression: a suppressed max-fix advisory lowers the floor, and full suppression clears it to None (so it cannot linger in JSON without range_vulnerabilities).

[`src/zaojun/vulns.py`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3131) > **code** [MEDIUM] — suggested_floor not recomputed after filtering… ✅ Fixed in `0148cdf` — `_apply_range_vuln_filter` now recomputes `suggested_floor` via `suggest_floor(result.range_vulns)` after suppression: a suppressed max-fix advisory lowers the floor, and full suppression clears it to `None` (so it cannot linger in JSON without `range_vulnerabilities`).
Author
Collaborator

src/zaojun/vulns.py

code [MEDIUM] — InvalidVersion from malformed fixed_in…

Fixed in 0148cdfsuggest_floor now skips non-PEP-440 fixed_in entries (contextlib.suppress(InvalidVersion) per entry) instead of bubbling a ValueError into a package-check error.

[`src/zaojun/vulns.py`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3130) > **code** [MEDIUM] — InvalidVersion from malformed fixed_in… ✅ Fixed in `0148cdf` — `suggest_floor` now skips non-PEP-440 `fixed_in` entries (`contextlib.suppress(InvalidVersion)` per entry) instead of bubbling a ValueError into a package-check error.
Author
Collaborator

src/zaojun/checker.py

code [LOW] — dedupe misses alias collisions…

Fixed in 0148cdf — the membership check now also drops range advisories whose aliases intersect the latest-advisory ID/alias set.

[`src/zaojun/checker.py`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3132) > **code** [LOW] — dedupe misses alias collisions… ✅ Fixed in `0148cdf` — the membership check now also drops range advisories whose aliases intersect the latest-advisory ID/alias set.
Author
Collaborator

README.md

docs [MEDIUM] — bare New breaks version-label convention…

Fixed in 0148cdf — labelled New in v1.8.0 (features ship in minor bumps under this repo's semver history — v1.5.0, v1.6.0; if the release lands as a different number the label is a one-word fix) and added the exit-code-1 / never-silenced parity sentence.

[`README.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3133) > **docs** [MEDIUM] — bare **New** breaks version-label convention… ✅ Fixed in `0148cdf` — labelled **New in v1.8.0** (features ship in minor bumps under this repo's semver history — v1.5.0, v1.6.0; if the release lands as a different number the label is a one-word fix) and added the exit-code-1 / never-silenced parity sentence.
Author
Collaborator

Release-Notes.md

docs [LOW] — "six known-vulnerable versions" misstates the case…

Fixed in 0148cdf — reworded to "still admitting versions with six known advisories", matching the README/PR wording.

[`Release-Notes.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3134) > **docs** [LOW] — "six known-vulnerable versions" misstates the case… ✅ Fixed in `0148cdf` — reworded to "still admitting versions with six known advisories", matching the README/PR wording.
Author
Collaborator

docs/usage/command-reference.md

docs [LOW] — operator list reads as exhaustive…

Fixed in 0148cdf — the section now states explicitly that only constraints defining a concrete allowed floor are scanned (>=X.Y, exact ==X.Y.Z, ~=X.Y), and that >2.3, ==2.3.*, <2.0 are not scanned (bound either not allowed by the constraint or not a concrete release). That matches the implementation: _spec_lower_bound recognises >=, ==, ~= only and skips wildcard/exclusive bounds.

[`docs/usage/command-reference.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3135) > **docs** [LOW] — operator list reads as exhaustive… ✅ Fixed in `0148cdf` — the section now states explicitly that only constraints defining a concrete allowed floor are scanned (`>=X.Y`, exact `==X.Y.Z`, `~=X.Y`), and that `>2.3`, `==2.3.*`, `<2.0` are not scanned (bound either not allowed by the constraint or not a concrete release). That matches the implementation: `_spec_lower_bound` recognises `>=`, `==`, `~=` only and skips wildcard/exclusive bounds.
Author
Collaborator

docs/usage/command-reference.md

docs [LOW] — ambiguous suppression wording…

Fixed in 0148cdf — replaced with explicit rules: the Advisories in declared range block is hidden by --short (like Vulnerabilities); the 🚨 inline indicator is always shown, even in --short; neither is affected by --library or --compat-ok.

[`docs/usage/command-reference.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3136) > **docs** [LOW] — ambiguous suppression wording… ✅ Fixed in `0148cdf` — replaced with explicit rules: the `Advisories in declared range` block is hidden by `--short` (like `Vulnerabilities`); the 🚨 inline indicator is always shown, even in `--short`; neither is affected by `--library` or `--compat-ok`.
Author
Collaborator

docs/usage/command-reference.md

docs [LOW] — JSON fields named but undocumented…

Fixed in 0148cdf — the JSON section now documents range_vulnerabilities as an array of advisory objects with the same keys as vulnerabilities (id, aliases, fixed_in, details, link), suggested_floor as a version string, and the exact presence conditions for each — including recomputation after vuln-ignore suppression.

[`docs/usage/command-reference.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3137) > **docs** [LOW] — JSON fields named but undocumented… ✅ Fixed in `0148cdf` — the JSON section now documents `range_vulnerabilities` as an array of advisory objects with the same keys as `vulnerabilities` (`id`, `aliases`, `fixed_in`, `details`, `link`), `suggested_floor` as a version string, and the exact presence conditions for each — including recomputation after `vuln-ignore` suppression.
forgejo-actions left a comment

WuMing

Found 3 issue(s). See inline comments below.

## WuMing Found **3** issue(s). See inline comments below.
@ -15,6 +15,8 @@
`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`.

docs [HIGH]

Version mismatch: this entry claims "New in v1.8.0", but the corresponding Release-Notes entry is published under the heading zaojun 1.7.5 (Release-Notes.md line 5). The README changelog also now jumps straight from v1.8.0 to v1.6.0, so no v1.7.x entry exists. Decide which version actually ships this feature and make the README heading and the Release-Notes version heading agree (and insert the missing v1.7.x entry if one was released).

**docs** [HIGH] Version mismatch: this entry claims "New in v1.8.0", but the corresponding Release-Notes entry is published under the heading `zaojun 1.7.5` (Release-Notes.md line 5). The README changelog also now jumps straight from v1.8.0 to v1.6.0, so no v1.7.x entry exists. Decide which version actually ships this feature and make the README heading and the Release-Notes version heading agree (and insert the missing v1.7.x entry if one was released). <!-- wuming:sha256:88712c859dea5adba542e813afc8d22299813e9fb015e854d178b4f41044eb6b -->
marvin8 marked this conversation as resolved
@ -160,0 +168,4 @@
lower bound and reports advisories that affect it. Only constraints that define a
concrete allowed floor are scanned — `>=X.Y`, exact `==X.Y.Z`, and `~=X.Y` — because the
bound version must itself be allowed by the constraint to be a meaningful floor.
Constraints without one (e.g. `>2.3`, `==2.3.*`, `<2.0`) are not scanned.

docs [LOW]

The stated rationale for excluding constraints is inconsistent with the example. The text says only constraints whose bound version "must itself be allowed by the constraint" are scanned, yet ==2.3.* is listed as excluded even though 2.3.x (and therefore a concrete floor such as 2.3.0) is allowed by that constraint. Either fix the rationale or state the real reason (e.g. wildcard == specifiers are not resolvable to a single floor version).

**docs** [LOW] The stated rationale for excluding constraints is inconsistent with the example. The text says only constraints whose bound version "must itself be allowed by the constraint" are scanned, yet `==2.3.*` is listed as excluded even though 2.3.x (and therefore a concrete floor such as 2.3.0) is allowed by that constraint. Either fix the rationale or state the real reason (e.g. wildcard `==` specifiers are not resolvable to a single floor version). <!-- wuming:sha256:dfe30b322ff2b8bf938d48f9113ec9d7a2dbe5bda466f4d947527b2dffc9e725 -->
@ -160,0 +189,4 @@
suggested floor is the highest per-advisory minimum `fixed_in` version; advisories
without any listed fix cannot be cleared by a floor raise.
A failed range fetch degrades to no advisories for that package and prints a warning to

docs [HIGH]

Self-contradictory and contradicts the PR description. The sentence says a failed range fetch "degrades to no advisories" and prints a warning to stderr, then calls the result a "silently-degraded scan" — a scan that prints a warning is not silent. The PR description states the opposite behaviour: "failing fetches degrade silently". Confirm the actual implementation and document one behaviour: either the failure is silent (per the PR description) or it emits a stderr warning (per this sentence), not both.

**docs** [HIGH] Self-contradictory and contradicts the PR description. The sentence says a failed range fetch "degrades to no advisories" and prints a warning to stderr, then calls the result a "silently-degraded scan" — a scan that prints a warning is not silent. The PR description states the opposite behaviour: "failing fetches degrade silently". Confirm the actual implementation and document one behaviour: either the failure is silent (per the PR description) or it emits a stderr warning (per this sentence), not both. <!-- wuming:sha256:54f8443c227fc3189217dc061d4275be5203af2e454cbf3fb34660269e68e764 -->
📝 fix range-scan exclusion rationale and degradation wording
All checks were successful
/ gitleaks (pull_request) Successful in 12s
/ checks (pull_request) Successful in 2m5s
/ publish (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ pr-review (pull_request) Successful in 2m11s
e4f755311a
WuMing wave 2 on PR #41: state the concrete reason each non-scanned
constraint is excluded (>2.3 excludes the bound itself; ==2.3.* and <2.0
do not resolve to a single concrete version), and replace the
contradictory 'silently-degraded scan is visible' phrasing — the
degradation prints a stderr warning and is never silent.
Author
Collaborator

docs/usage/command-reference.md

docs [HIGH] — 'silently-degraded scan' self-contradiction…

Fixed in commit e4f7553 — reworded to: "A failed range fetch produces no advisories for that package and prints a warning to stderr — the degradation is never silent." Also patched the PR description, which still said "degrade silently" from before the wave-1 fix.

[`docs/usage/command-reference.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3154) > **docs** [HIGH] — 'silently-degraded scan' self-contradiction… ✅ Fixed in commit `e4f7553` — reworded to: "A failed range fetch produces no advisories for that package and prints a warning to stderr — the degradation is never silent." Also patched the PR description, which still said "degrade silently" from before the wave-1 fix.
Author
Collaborator

docs/usage/command-reference.md

docs [LOW] — exclusion rationale inconsistent for ==2.3.*…

Fixed in commit e4f7553 — the text now states the scan requires a single concrete allowed version and gives each exclusion its real reason: >2.3 excludes the bound itself (scanning it would produce false positives); ==2.3.* and <2.0 do not resolve to a single concrete version to query.

[`docs/usage/command-reference.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3155) > **docs** [LOW] — exclusion rationale inconsistent for ==2.3.*… ✅ Fixed in commit `e4f7553` — the text now states the scan requires a single concrete allowed version and gives each exclusion its real reason: `>2.3` excludes the bound itself (scanning it would produce false positives); `==2.3.*` and `<2.0` do not resolve to a single concrete version to query.
Author
Collaborator

README.md

docs [HIGH] — Version mismatch: this entry claims "New in v1.8.0", but the corresponding Release-Notes entry is published under zaojun 1.7.5

🔴 No change — this re-raises the substance of the wave-1 finding (already answered above: new sha256, same substance — duplicate), but the three factual claims deserve direct answers:

  1. The zaojun 1.7.5 heading in Release-Notes.md is the reset-template state, not a published mapping: bump-my-version rewrites zaojun {current_version} in that file when the release is cut. After just release minor it reads zaojun 1.8.0 and the README heading and Release-Notes agree.
  2. The v1.8.0 → v1.6.0 gap in the New-in stack predates this PR: v1.7.x were patch releases with no feature paragraphs (none exist anywhere in the repo, including the version history in docs/usage/command-reference.md), so there is no missing v1.7.x entry to insert.
  3. Which version ships is Marvin8's release decision made at just release time; v1.8.0 is the semver-consistent label for a feature and the assumption was disclosed in the wave-1 reply — a one-word fix if the release lands differently.
[`README.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3153) > **docs** [HIGH] — Version mismatch: this entry claims "New in v1.8.0", but the corresponding Release-Notes entry is published under `zaojun 1.7.5`… 🔴 No change — this re-raises the substance of the wave-1 finding ([already answered ✅ above](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3148): new sha256, same substance — duplicate), but the three factual claims deserve direct answers: 1. The `zaojun 1.7.5` heading in `Release-Notes.md` is the **reset-template state**, not a published mapping: `bump-my-version` rewrites `zaojun {current_version}` in that file when the release is cut. After `just release minor` it reads `zaojun 1.8.0` and the README heading and Release-Notes agree. 2. The v1.8.0 → v1.6.0 gap in the New-in stack **predates this PR**: v1.7.x were patch releases with no feature paragraphs (none exist anywhere in the repo, including the version history in `docs/usage/command-reference.md`), so there is no missing v1.7.x entry to insert. 3. Which version ships is Marvin8's release decision made at `just release` time; `v1.8.0` is the semver-consistent label for a feature and the assumption was disclosed in the wave-1 reply — a one-word fix if the release lands differently.
forgejo-actions left a comment

WuMing

Found 2 issue(s). See inline comments below.

## WuMing Found **2** issue(s). See inline comments below.
@ -4,10 +4,16 @@ visibility: public
zaojun 1.7.5

docs [MEDIUM]

Version mismatch: README.md line 18 announces this feature as "New in v1.8.0", but the new Release-Notes entry is added under the zaojun 1.7.5 heading. If the release notes are generated by just reset-release-notes template substitution, either the heading must be updated to the version being shipped (1.8.0) or the README version must be corrected, so the two documents agree.

**docs** [MEDIUM] Version mismatch: README.md line 18 announces this feature as "New in v1.8.0", but the new Release-Notes entry is added under the `zaojun 1.7.5` heading. If the release notes are generated by `just reset-release-notes` template substitution, either the heading must be updated to the version being shipped (1.8.0) or the README version must be corrected, so the two documents agree. <!-- wuming:sha256:1caa933cd8da3c33acc0ed92cfef8b0eb561912a0b297d38cde5bcb0fc9247b5 -->
marvin8 marked this conversation as resolved
@ -160,0 +166,4 @@
constraint like `~=2.3` can look fully up to date while still admitting known-vulnerable
releases. zaojun therefore also fetches the version-level PyPI JSON response for the
lower bound and reports advisories that affect it. Only constraints that resolve to a
single concrete allowed version are scanned — `>=X.Y`, exact `==X.Y.Z`, and `~=X.Y`.

docs [MEDIUM]

Inaccurate description of which constraints are scanned. >=2.3 and ~=2.3 each allow many versions, so they do not "resolve to a single concrete allowed version". The actual criterion (as the following sentence about >2.3, ==2.3.* and <2.0 implies) is that the constraint has a single concrete inclusive lower bound. Reword to something like: "Only constraints with a single concrete lower bound are scanned — >=X.Y, exact ==X.Y.Z, and ~=X.Y." and align the exclusion sentence with that criterion.

**docs** [MEDIUM] Inaccurate description of which constraints are scanned. `>=2.3` and `~=2.3` each allow *many* versions, so they do not "resolve to a single concrete allowed version". The actual criterion (as the following sentence about `>2.3`, `==2.3.*` and `<2.0` implies) is that the constraint has a single concrete *inclusive lower bound*. Reword to something like: "Only constraints with a single concrete lower bound are scanned — `>=X.Y`, exact `==X.Y.Z`, and `~=X.Y`." and align the exclusion sentence with that criterion. <!-- wuming:sha256:7195888a01da6ce208b585167467d7016ca50d4c734ab1f8a190e589ef7670a9 -->
📝 restate range-scan criterion as inclusive lower bound
All checks were successful
/ gitleaks (pull_request) Successful in 20s
/ pr-review (pull_request) Successful in 2m15s
/ checks (pull_request) Successful in 2m27s
/ publish (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
3438968056
WuMing wave 3 on PR #41: >=X.Y allows many versions, so the criterion is
a single concrete inclusive lower bound, not a single allowed version.
Aligns the exclusion sentence with that wording.
Author
Collaborator

docs/usage/command-reference.md

docs [MEDIUM] — 'resolve to a single concrete allowed version' is inaccurate for >=2.3…

Fixed in commit 3438968 — reworded to "Only constraints with a single concrete inclusive lower bound are scanned", with the exclusion sentence aligned to that criterion.

[`docs/usage/command-reference.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3163) > **docs** [MEDIUM] — 'resolve to a single concrete allowed version' is inaccurate for >=2.3… ✅ Fixed in commit `3438968` — reworded to "Only constraints with a single concrete *inclusive lower bound* are scanned", with the exclusion sentence aligned to that criterion.
Author
Collaborator

Release-Notes.md

docs [MEDIUM] — Version mismatch: README announces "New in v1.8.0" but the Release-Notes entry sits under the zaojun 1.7.5 heading…

🔴 Duplicate — third re-raise of the same substance (wave-1 reply, wave-2 🔴 reply). The one decisive fact: the zaojun 1.7.5 heading is not generated by the reset template substitution[tool.bumpversion] rewrites the zaojun {current_version} line in Release-Notes.md when the release is cut. Manually setting it to 1.8.0 now would (a) claim an unreleased version and (b) break bump-my-version's search match at release time. After just release minor the heading reads zaojun 1.8.0 and both documents agree. No further action.

[`Release-Notes.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3162) > **docs** [MEDIUM] — Version mismatch: README announces "New in v1.8.0" but the Release-Notes entry sits under the `zaojun 1.7.5` heading… 🔴 Duplicate — third re-raise of the same substance ([wave-1 ✅ reply](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3148), [wave-2 🔴 reply](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3161)). The one decisive fact: the `zaojun 1.7.5` heading is **not generated by the reset template substitution** — `[tool.bumpversion]` rewrites the `zaojun {current_version}` line in `Release-Notes.md` when the release is cut. Manually setting it to 1.8.0 now would (a) claim an unreleased version and (b) break bump-my-version's search match at release time. After `just release minor` the heading reads `zaojun 1.8.0` and both documents agree. No further action.
forgejo-actions left a comment

WuMing

Found 4 issue(s). See inline comments below.

## WuMing Found **4** issue(s). See inline comments below.
Release-Notes.md Outdated
@ -4,10 +4,16 @@ visibility: public
zaojun 1.7.5
- Constraint-range advisory scanning: zaojun now also checks the oldest version each constraint allows — 🚨 warns when your declared range still admits known-vulnerable releases, and suggests a floor that clears them

docs [HIGH]

Version mismatch: this new bullet is inserted directly under the existing heading zaojun 1.7.5 (line 5), so the constraint-range feature is announced as part of 1.7.5, while README line 18 announces the same feature as "New in v1.8.0". Users and the fediverse post will be attributed to the wrong release. Update the heading on line 5 (and any other version placeholders in this file) to the actual release version, or correct the README if 1.7.5 is correct, so both files agree.

**docs** [HIGH] Version mismatch: this new bullet is inserted directly under the existing heading `zaojun 1.7.5` (line 5), so the constraint-range feature is announced as part of 1.7.5, while README line 18 announces the same feature as "New in v1.8.0". Users and the fediverse post will be attributed to the wrong release. Update the heading on line 5 (and any other version placeholders in this file) to the actual release version, or correct the README if 1.7.5 is correct, so both files agree. <!-- wuming:sha256:ddd9c59e080af566d6dd5b893418e4c863fe92c8cef443a728b4dfeb39622c49 -->
@ -160,0 +193,4 @@
A failed range fetch produces no advisories for that package and prints a warning to
stderr — the degradation is never silent.
Known limitation (documented): advisories introduced *after* the floor (affecting only

docs [LOW]

The parenthetical "(documented)" is internal meta-commentary about the docs themselves and reads oddly in user-facing documentation — it also duplicates the "Known limitation" framing that already appeared in the PR description. Drop the parenthetical and keep only the limitation text, e.g. "Known limitation: advisories introduced after the floor …".

**docs** [LOW] The parenthetical "(documented)" is internal meta-commentary about the docs themselves and reads oddly in user-facing documentation — it also duplicates the "Known limitation" framing that already appeared in the PR description. Drop the parenthetical and keep only the limitation text, e.g. "Known limitation: advisories introduced *after* the floor …". <!-- wuming:sha256:aaee11ecc7b238bbea199ef154ce64f932172504409e7f270582a6ec5e1cc88f -->
@ -124,0 +174,4 @@
return _rebuild_advisories(cached.get("vulns") or [])
try:
response = await client.get(f"{index_url.rstrip('/')}/{package_name}/{lower_bound}/json")

security [MEDIUM]

A10: The user-controllable index_url is concatenated directly into the request URL and passed to the HTTP client with no allow-list or scheme validation. In library/CI usage an attacker-influenced index_url could target internal endpoints or cloud metadata services. Restrict index_url to an allow-list of trusted HTTPS PyPI mirrors and parse/validate it before use.

**security** [MEDIUM] A10: The user-controllable index_url is concatenated directly into the request URL and passed to the HTTP client with no allow-list or scheme validation. In library/CI usage an attacker-influenced index_url could target internal endpoints or cloud metadata services. Restrict index_url to an allow-list of trusted HTTPS PyPI mirrors and parse/validate it before use. <!-- wuming:sha256:8825f8d57e88ab2c5f9d1959706eef02189a35b1e8f91daa6b0a5bb22a23a241 -->
marvin8 marked this conversation as resolved
@ -124,0 +179,4 @@
payload = response.json()
except (httpx2.HTTPStatusError, httpx2.RequestError, json.JSONDecodeError) as exc:
print(
f"zaojun: range-advisory fetch for {package_name} {lower_bound} failed ({exc!r}) — scan degraded",

security [MEDIUM]

A09: The exception is logged with {exc!r}, which can expose the full request URL in stderr. If index_url contains embedded credentials (userinfo or query tokens), those secrets can leak into logs. Sanitise/redact the URL and exception before logging, and avoid interpolating raw dynamic values into log messages.

**security** [MEDIUM] A09: The exception is logged with {exc!r}, which can expose the full request URL in stderr. If index_url contains embedded credentials (userinfo or query tokens), those secrets can leak into logs. Sanitise/redact the URL and exception before logging, and avoid interpolating raw dynamic values into log messages. <!-- wuming:sha256:8502b006c24156aed8fc803fc00f2c63f7cd83f99169a4167a241e49ef738141 -->
🚨 redact exception detail from range-fetch warning
All checks were successful
/ gitleaks (pull_request) Successful in 21s
/ checks (pull_request) Successful in 2m16s
/ publish (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ pr-review (pull_request) Successful in 2m20s
f6f684f33c
WuMing wave 4 on PR #41: the stderr warning interpolated {exc!r}, which
embeds the full request URL — leaking credentials if index_url carries
userinfo or query tokens. Log the exception type name only. Also drop the
'(documented)' meta-parenthetical from the known-limitation text.
Author
Collaborator

docs/usage/command-reference.md

docs [LOW] — '(documented)' is internal meta-commentary…

Fixed in commit f6f684f — dropped the parenthetical; the limitation text stands alone.

[`docs/usage/command-reference.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3173) > **docs** [LOW] — '(documented)' is internal meta-commentary… ✅ Fixed in commit `f6f684f` — dropped the parenthetical; the limitation text stands alone.
Author
Collaborator

src/zaojun/pypi.py

security [MEDIUM] — {exc!r} can expose the full request URL (and embedded credentials) in stderr…

Fixed in commit f6f684f — the warning now logs only the exception type name (HTTPStatusError / RequestError / JSONDecodeError), never the exception repr, so no URL or credentials can reach stderr. Test asserts https:// is absent from the warning.

[`src/zaojun/pypi.py`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3175) > **security** [MEDIUM] — {exc!r} can expose the full request URL (and embedded credentials) in stderr… ✅ Fixed in commit `f6f684f` — the warning now logs only the exception type name (`HTTPStatusError` / `RequestError` / `JSONDecodeError`), never the exception repr, so no URL or credentials can reach stderr. Test asserts `https://` is absent from the warning.
Author
Collaborator

src/zaojun/pypi.py

security [MEDIUM] A10: user-controllable index_url concatenated into the request URL with no allow-list or scheme validation…

🔴 Declined — by design, not by omission. --index-url is a documented core zaojun feature (Artifactory, devpi, any PyPI JSON-compatible index); the value comes from the operator's own CLI args or [tool.zaojun] config. zaojun is a developer CLI, not a network service: an "attacker-influenced index_url" already implies local config compromise, at which point the attacker controls the entire check anyway. An HTTPS allow-list of "trusted mirrors" would break every private-index user and contradict the feature contract introduced in v1.5.0. The range probe reuses exactly the trust model of the pre-existing package-level fetch one line above ({index_url}/{package}/json) — no new exposure is introduced by this PR. Documenting "prefer HTTPS for token-bearing indexes" is a reasonable docs follow-up; a code-level gate is not.

[`src/zaojun/pypi.py`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3174) > **security** [MEDIUM] A10: user-controllable index_url concatenated into the request URL with no allow-list or scheme validation… 🔴 Declined — by design, not by omission. `--index-url` is a documented core zaojun feature (Artifactory, devpi, any PyPI JSON-compatible index); the value comes from the operator's own CLI args or `[tool.zaojun]` config. zaojun is a developer CLI, not a network service: an "attacker-influenced index_url" already implies local config compromise, at which point the attacker controls the entire check anyway. An HTTPS allow-list of "trusted mirrors" would break every private-index user and contradict the feature contract introduced in v1.5.0. The range probe reuses exactly the trust model of the pre-existing package-level fetch one line above (`{index_url}/{package}/json`) — no new exposure is introduced by this PR. Documenting "prefer HTTPS for token-bearing indexes" is a reasonable docs follow-up; a code-level gate is not.
Author
Collaborator

Release-Notes.md

docs [HIGH] — Version mismatch: bullet under zaojun 1.7.5 vs README "New in v1.8.0"…

🔴 Duplicate — fourth re-raise of the same substance (wave-1, wave-2, wave-3). The answer is unchanged: the zaojun 1.7.5 heading is the current-version line that [tool.bumpversion] rewrites at release (search = "zaojun {current_version}" in pyproject.toml); setting it to 1.8.0 by hand would claim an unreleased version and break the release automation. After just release minor, heading and README agree. Escalated to Marvin8 for a ruling on this loop.

[`Release-Notes.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3172) > **docs** [HIGH] — Version mismatch: bullet under `zaojun 1.7.5` vs README "New in v1.8.0"… 🔴 Duplicate — fourth re-raise of the same substance ([wave-1](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3148), [wave-2](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3161), [wave-3](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3171)). The answer is unchanged: the `zaojun 1.7.5` heading is the current-version line that `[tool.bumpversion]` rewrites at release (`search = "zaojun {current_version}"` in `pyproject.toml`); setting it to 1.8.0 by hand would claim an unreleased version and break the release automation. After `just release minor`, heading and README agree. Escalated to Marvin8 for a ruling on this loop.
forgejo-actions left a comment

WuMing

Found 1 issue(s). See inline comments below.

## WuMing Found **1** issue(s). See inline comments below.
@ -8,3 +10,4 @@
---
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 your range still allows 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. Range warnings respect `vuln-ignore`, always set exit code 1, and are never silenced by `--library` or `--compat-ok`.

docs [LOW]

"Anything your range still allows shows up as a 🚨 warning" overstates the feature and contradicts the limitation documented in docs/usage/command-reference.md lines 196-198 (and the PR description): only advisories affecting the constraint's lower bound are fetched, so advisories introduced after the floor are missed. Soften this to something like "anything your lower bound is known to be affected by shows up" so the published note does not promise full-range coverage.

**docs** [LOW] "Anything your range still allows shows up as a 🚨 warning" overstates the feature and contradicts the limitation documented in docs/usage/command-reference.md lines 196-198 (and the PR description): only advisories affecting the constraint's lower bound are fetched, so advisories introduced after the floor are missed. Soften this to something like "anything your lower bound is known to be affected by shows up" so the published note does not promise full-range coverage. <!-- wuming:sha256:ad6f69d9e1bcb9a0d7e0937ab178a9dc4364134a0ecc5523a94a58a7a2899398 -->
Author
Collaborator

Release-Notes.md

docs [LOW] — "Anything your range still allows shows up as a 🚨 warning" overstates the feature…

Fixed in commit 3cbac0e — reworded to "Anything the floor is known to be affected by shows up as a 🚨 warning…", with the after-the-floor limitation now stated explicitly in the same paragraph. Also tightened the intro bullet ("warns when that floor is known to be affected by vulnerabilities") under the same no-overpromise rationale — flagged here since it goes one sentence beyond the quoted one.

[`Release-Notes.md`](https://forge.marvin8.zone/marvin8/zaojun/pulls/41#issuecomment-3183) > **docs** [LOW] — "Anything your range still allows shows up as a 🚨 warning" overstates the feature… ✅ Fixed in commit `3cbac0e` — reworded to "Anything the floor is known to be affected by shows up as a 🚨 warning…", with the after-the-floor limitation now stated explicitly in the same paragraph. Also tightened the intro bullet ("warns when that floor is known to be affected by vulnerabilities") under the same no-overpromise rationale — flagged here since it goes one sentence beyond the quoted one.
📝 scope the release-notes range-scan claim to the floor
All checks were successful
/ gitleaks (pull_request) Successful in 21s
/ pr-review (pull_request) Successful in 2m11s
/ checks (pull_request) Successful in 2m47s
/ publish (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
3cbac0e096
WuMing wave 5 on PR #41: 'anything your range still allows shows up'
overpromised full-range coverage; the scan fetches the floor's advisories
only. Reworded the prose and tightened the intro bullet to match, with
the after-the-floor limitation stated explicitly.
marvin8 approved these changes 2026-09-14 01:28:51 +00:00
marvin8 manually merged commit 3a30a2609b into main 2026-09-14 01:31:47 +00:00
Sign in to join this conversation.
No description provided.