Repository navigation
fix(fix-target): never recommend a yanked or deprecated release (FX-H5) - #976
Merged
Merged
Conversation
runyourempire
added a commit
that referenced
this pull request
Oct 17, 2026
…review of #976 B1: npm `deprecated` is often a whole-major "please upgrade" notice, not a withdrawn release (glob deprecates 160 of 168 versions; tar every 6.x). Excluding them all sent glob 10.4.5 (GHSA-5j98-mcp5-4vw2) to 12.0.0 and tar 6.1.11 to 7.5.8. A deprecated npm release is now excluded only when a later, non-deprecated, stable release exists on its caret line (same major; same minor for 0.x); a package whose `latest` is deprecated excludes nothing. lodash 4.18.0 and brace-expansion 3.0.3 stay excluded. New vectors ft-065 (glob 10.4.5 -> 10.5.0) and ft-066 (tar 6.1.11 -> 6.2.1) from live data, 2026-10-17. S2: morning audit (G4) accepts a version the stored registry facts list, so a registry-only target such as lodash 4.18.1 is not a version fault. S3: each registry is read only while its source (npm_registry, crates_io, pypi) is enabled; NETWORK.md says so. S4: only crates.io requests are paced (1.1 s apart). S5: 90 s request budget for large documents (next ~26 MB, boto3 ~3.3 MB). Nits: no default client on a builder error; OSV live feed text annotates a withdrawn "Fixed in" release with the one to use; the security verdict reads registry facts once per verdict. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hjX3pEMmC7CHfG41PiPj7
runyourempire
force-pushed
the
fix/fx-h5-withdrawn-fix-targets
branch
from
October 17, 2026 03:23
722db8e to
6befaa4
Compare
runyourempire
enabled auto-merge
October 17, 2026 14:32
lodash 4.17.21 was told "update to >= 4.18.0"; npm marks 4.18.0
deprecated ("Bad release"), 4.18.1 is the release to take. A yanked
crate target would make `cargo update --precise` fail outright.
- fourda-depengine: new `registry::RegistryReleases {published, excluded}`
input. The walk skips excluded releases exactly as it skips
pre-releases, can step to any published release, and reports the
withdrawn releases it passed over (`WalkOutcome.withheld`). A window
whose every exit is withdrawn has no way out: no target, honestly
named. `installable_fix` / `installable_line_fix` apply the same rule to
line fixes and listed fixes. `match_inventory_with` takes a
`RegistryIndex`; `MatchedDependency.withheld` carries the names. JSON
API ops accept an optional `registry`. Crate stays pure (wasm: 0 imports).
- app: `osv::release_facts` reads crates.io `yanked`, PyPI `yanked` (all
files) and npm `deprecated` (abbreviated packument; not when `latest` is
deprecated too) for packages with a confirmed advisory, after each OSV
sync, cached in kv_store (24 h fresh, 30 d max). Go `retract` is not
covered. Offline / never fetched: advisory-data-only, as before.
- Upgrade Plan: when every fixing release is withdrawn the row reads
"No installable fix" and names the withdrawn release, never "No fix
published". The scorer's security verdict fix goes through the same rule.
- Vectors: ft-007 now 4.18.1 (excluded 4.18.0); new ft-062 (h2 0.3.17
yanked -> 0.3.18), ft-063 (brace-expansion 3.0.3 deprecated -> 3.0.4),
ft-064 (all clearing releases withdrawn -> no target, withheld named).
Optional `published`/`excluded`/`withheld` fields documented in the
vectors README. NETWORK.md lists the new registry reads.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018hjX3pEMmC7CHfG41PiPj7
…review of #976 B1: npm `deprecated` is often a whole-major "please upgrade" notice, not a withdrawn release (glob deprecates 160 of 168 versions; tar every 6.x). Excluding them all sent glob 10.4.5 (GHSA-5j98-mcp5-4vw2) to 12.0.0 and tar 6.1.11 to 7.5.8. A deprecated npm release is now excluded only when a later, non-deprecated, stable release exists on its caret line (same major; same minor for 0.x); a package whose `latest` is deprecated excludes nothing. lodash 4.18.0 and brace-expansion 3.0.3 stay excluded. New vectors ft-065 (glob 10.4.5 -> 10.5.0) and ft-066 (tar 6.1.11 -> 6.2.1) from live data, 2026-10-17. S2: morning audit (G4) accepts a version the stored registry facts list, so a registry-only target such as lodash 4.18.1 is not a version fault. S3: each registry is read only while its source (npm_registry, crates_io, pypi) is enabled; NETWORK.md says so. S4: only crates.io requests are paced (1.1 s apart). S5: 90 s request budget for large documents (next ~26 MB, boto3 ~3.3 MB). Nits: no default client on a builder error; OSV live feed text annotates a withdrawn "Fixed in" release with the one to use; the security verdict reads registry facts once per verdict. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hjX3pEMmC7CHfG41PiPj7
…68 (#972 holds ft-065/066) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hjX3pEMmC7CHfG41PiPj7
runyourempire
force-pushed
the
fix/fx-h5-withdrawn-fix-targets
branch
from
October 17, 2026 14:46
66fa88f to
78cf9b2
Compare
runyourempire
added a commit
that referenced
this pull request
Oct 17, 2026
…ry (audit H1) A vulnerable transitive copy whose parent pins it said "upgrade express (check that it admits path-to-regexp >= 0.1.13)" and stopped. The registry already knew the answer. The plan, Preemption and the Brief now name it, by the rules of @4da/mcp-server fix/planner-advice-625 (1840dab): - the smallest parent release above the installed one whose own requirement resolves to a clean copy at or above the target, clear of the parent's own advisories (osv/parent_release.rs, pure); - one release per dependency: a parent with its own vulnerable row has that row raised to the highest release the transitive rows need from it (no major crossed, no own advisory affects it), and a lower row is lifted to it when it still admits a clean copy; - the refresh after the install when the new requirement still admits the installed copy (npm 10.9.2 keeps it); - a parent that cannot be tied to a declared dependency gets its release but no command; a command goes through osv::direct_upgrade (declared operator kept); a major move is flagged; - a parent reached through a declared dependency names that dependency's release that brings a fixed parent in (adjacent lever). Data: the npm abbreviated document #976 already reads now also keeps, for the parents that pin a vulnerable copy (and their declared lever), each release's requirement on that package plus the latest tag. Same request, same cap, cache, source toggle; NETWORK.md says so. npm only. NodeGoat (express 4.16.4): the express, path-to-regexp and qs rows all name express 4.22.3 (qs forces it over 4.22.0). New shared vector kind parent_release (pr-001..pr-006), pinned to live registry and OSV data of 2026-10-18, run against the real functions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hjX3pEMmC7CHfG41PiPj7
runyourempire
added a commit
that referenced
this pull request
Oct 17, 2026
…y; the Brief and the plan name the same one - SF1: with_direct_fix gets the installed version only from a version-confirmed copy. Without one, the version, is_direct and the target are the alert's fallbacks (the raw highest OSV fix, which skips the withdrawn-release rule of #976), and the Upgrade Plan names no command there either. - SF2: the verbatim-quote version check covers npm, Cargo and Go commands and the PyPI manifest edit. - SF3: one seeded project, built by both builders: the Brief's direct row and the Upgrade Plan's direct row print the same command; with no confirmed copy the Brief names none. - A changed target still changes the act-now signature; no installed version or an unread ecosystem names nothing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH
runyourempire
added a commit
that referenced
this pull request
Oct 18, 2026
…ry (audit H1) A vulnerable transitive copy whose parent pins it said "upgrade express (check that it admits path-to-regexp >= 0.1.13)" and stopped. The registry already knew the answer. The plan, Preemption and the Brief now name it, by the rules of @4da/mcp-server fix/planner-advice-625 (1840dab): - the smallest parent release above the installed one whose own requirement resolves to a clean copy at or above the target, clear of the parent's own advisories (osv/parent_release.rs, pure); - one release per dependency: a parent with its own vulnerable row has that row raised to the highest release the transitive rows need from it (no major crossed, no own advisory affects it), and a lower row is lifted to it when it still admits a clean copy; - the refresh after the install when the new requirement still admits the installed copy (npm 10.9.2 keeps it); - a parent that cannot be tied to a declared dependency gets its release but no command; a command goes through osv::direct_upgrade (declared operator kept); a major move is flagged; - a parent reached through a declared dependency names that dependency's release that brings a fixed parent in (adjacent lever). Data: the npm abbreviated document #976 already reads now also keeps, for the parents that pin a vulnerable copy (and their declared lever), each release's requirement on that package plus the latest tag. Same request, same cap, cache, source toggle; NETWORK.md says so. npm only. NodeGoat (express 4.16.4): the express, path-to-regexp and qs rows all name express 4.22.3 (qs forces it over 4.22.0). New shared vector kind parent_release (pr-001..pr-006), pinned to live registry and OSV data of 2026-10-18, run against the real functions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hjX3pEMmC7CHfG41PiPj7
runyourempire
added a commit
that referenced
this pull request
Oct 18, 2026
…aming a command toward it A newly published malicious (OSV MAL- / GitHub malware) or advisory-affected minor inside a declared range was announced in the stack-change stream with a ready-to-run `npm update`, the command that installs it; the Brief's upgrade facts had the same blind spot (release_grade never consults advisories). osv/release_advisories.rs checks the release itself with the predicate behind every "this version is clear" (check_version_affected over the package's stored advisories, conservative on unreadable windows), never a registry- withdrawn release (#976), and an all-clear only from a mirror that is ready (osv::readiness) and synced after the release was published; otherwise the status is unknown. Stack-change stream: affected -> no command, title/explanation name the advisories (malware: "do not install", urgency High, says a refresh would pull it in when the range admits it), osv citations; when every affecting advisory already hits the installed copy the item is left to the advisory lane; withdrawn / unknown -> no command, said so. Brief: affected or withdrawn releases are not presented as upgrades. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hjX3pEMmC7CHfG41PiPj7
runyourempire
added a commit
that referenced
this pull request
Oct 18, 2026
…ry (audit H1) A vulnerable transitive copy whose parent pins it said "upgrade express (check that it admits path-to-regexp >= 0.1.13)" and stopped. The registry already knew the answer. The plan, Preemption and the Brief now name it, by the rules of @4da/mcp-server fix/planner-advice-625 (1840dab): - the smallest parent release above the installed one whose own requirement resolves to a clean copy at or above the target, clear of the parent's own advisories (osv/parent_release.rs, pure); - one release per dependency: a parent with its own vulnerable row has that row raised to the highest release the transitive rows need from it (no major crossed, no own advisory affects it), and a lower row is lifted to it when it still admits a clean copy; - the refresh after the install when the new requirement still admits the installed copy (npm 10.9.2 keeps it); - a parent that cannot be tied to a declared dependency gets its release but no command; a command goes through osv::direct_upgrade (declared operator kept); a major move is flagged; - a parent reached through a declared dependency names that dependency's release that brings a fixed parent in (adjacent lever). Data: the npm abbreviated document #976 already reads now also keeps, for the parents that pin a vulnerable copy (and their declared lever), each release's requirement on that package plus the latest tag. Same request, same cap, cache, source toggle; NETWORK.md says so. npm only. NodeGoat (express 4.16.4): the express, path-to-regexp and qs rows all name express 4.22.3 (qs forces it over 4.22.0). New shared vector kind parent_release (pr-001..pr-006), pinned to live registry and OSV data of 2026-10-18, run against the real functions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hjX3pEMmC7CHfG41PiPj7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem (FX-H5)
Fix targets came only from OSV bounds (
fourda-depengine/src/fix_target.rs). Nothing checked whether the registry had withdrawn a release:npm view lodash@4.18.0 deprecatedreturns "Bad release. Please use lodash@4.17.21 instead." 4.18.1 is the latest release, it is clean, and it is not deprecated.cargo update -p ... --precise <target>).Fix
Pure crate (
crates/fourda-depengine)registry::RegistryReleases { published, excluded }.publishedjoins the advisory-named releases, so the walk can name a release no advisory mentions.WalkOutcome.withheldnames the withdrawn releases. The engine never returns an excluded version.installable_fix/installable_line_fixapply the same rule to line fixes and to an advisory's listed fix (the unconfirmed-finding fallback).match_inventory_withtakes aRegistryIndex.MatchedDependency.withheldis serde default and skipped when empty.fix_target,match_inventory) accepts an optionalregistry.App
New
osv::release_facts. After each OSV sync, for packages with a version-confirmed advisory only, it reads:/api/v1/crates/{crate}yanked/pypi/{pkg}/json/{pkg}, abbreviated packumentdeprecated, but only when a later, non-deprecated, stable release exists on the same caret line (same major; same minor for 0.x). A package whoselatestis deprecated excludes nothing.Why the npm rule is narrow (review B1): npm
deprecatedis often a whole-major "please upgrade" notice, not a withdrawn release. glob deprecates 160 of 168 versions and tar every 6.x. Excluding all of them sent glob 10.4.5 to 12.0.0 and tar 6.1.11 to 7.5.8, major jumps that break the per-line contract.Each registry is read only while its source toggle (
npm_registry,crates_io,pypi) is enabled (S3).Only crates.io requests are paced, 1.1 s apart (S4).
Requests get a 90 s budget, because
next's packument is about 26 MB and boto3's PyPI JSON about 3.3 MB (S5).PyPI still uses the JSON API rather than PEP 691 Simple JSON. PEP 691 files carry no version field, so I would have to parse wheel and sdist filenames; that is a follow-up if
releasesis ever removed.The morning audit (G4
version_is_true) accepts versions listed in the stored registry facts (S2).The OSV live feed text annotates a withdrawn "Fixed in" release:
(withdrawn by the registry; use X)(nit 7).Go
retractis not covered (it needs the newest version's go.mod) and is documented as such.Facts are cached in
kv_store: fresh for 24 h, used for at most 30 d, at most 60 packages per sync.If the facts are unavailable (offline, never fetched), behaviour is exactly as before: targets come from the advisory data. A stale
publishedlist can only make a target higher, never vulnerable.No new hosts. NETWORK.md §2c and the §4 derived-data table list the new paths, including the npm
/{package}path for the desktop app.Upgrade Plan wording: when every fixing release is withdrawn, the row reads "No installable fix for X" and its note names the withdrawn release. It never says "No fix published" in that case, and never prints the withdrawn version as a target.
The scorer's security-verdict
fixed_versiongoes through the same rule.Before / after
Built wasm engine, pinned OSV records, live registry facts (2026-10-16):
Review B1, live npm data on 2026-10-17, same engine. "first revision" means every deprecated release excluded; "now" means the caret-line rule:
End-to-end through the DB:
upgrade_plan_tests::a_deprecated_fix_release_is_never_the_plan_target: the title reads>= 4.18.1, and no item or work-order step contains4.18.0.every_fixing_release_withdrawn_reads_no_installable_fix: the title reads "No installable fix for h2".Vector diff (
src-tauri/tests/conformance/vectors/fix_target.jsonl)"target":"4.18.0"becomes"target":"4.18.1". Adds"published":["4.17.23","4.18.0","4.18.1"]and"excluded":["4.18.0"]. The rule and source text are extended.0.3.18, clears true.3.0.4, clears true.null,withheld:["0.3.17"].10.5.0, clears true.6.2.1, clears true.published/excluded/withheldfields are documented in the vectors README. Lines without them behave exactly as before./v1/query(2026-10-16 for ft-062/063, 2026-10-17 for ft-067/068).excludedis the app's npm rule applied to the live packument on the stated date.TypeScript engine: the exact changed and new lines went to the peer session 4da-b0 before commit, and their final ids after the renumbering. They will mirror the npm rule exactly. The TS side lands with their MCP 6.2.5 integration PR. Until then the MCP runner will disagree on ft-007, ft-062..064 and ft-067/068.
Verification
Crate CI steps (
crates/fourda-depengine):cargo fmt --check: okcargo clippy --locked -- -D warnings: okcargo clippy --locked --target wasm32-unknown-unknown -- -D warnings: okcargo test --locked: 36 lib + 2 integration tests passscripts/check-wasm.cjs: 0 imports, all checks ok, wasm == native on the corpus case. A new check covers a withdrawn fix.check-lock-drift: okTargeted
src-tauritests after rebasing ontoaaba1438e(CARGO_TARGET_DIR=C:\cargo-targets\fxh5). Thefix_path,fix_targetandmatchingcounts are from the first round; nothing those filters cover changed in the review round.osv::vectorsrelease_factsosv_livemorningfix_pathfix_targetmatchingupgrade_planevidence::preemptionbrief_factssecurity_verdictcargo clippy --lib -D warnings: clean.--all-targetsreports only lints in files this PR does not touch, or on lines it does not touch.cargo test --libwas not run here (machine load); the merge-queue CI runs it.Notes for reviewers
fix_path.rs,parent_hint.rs,scanner.rs,brief_facts_copies.rs) are untouched.morning_audit.rs(where t2 has a follow-up) gains one clause inversion_is_true; 4da-b0 was told the exact hunk.preemption.rs,identity.rsand three test files only gainwithheld: Vec::new(),inMatchedDependencytest literals.🤖 Generated with Claude Code
https://claude.ai/code/session_018hjX3pEMmC7CHfG41PiPj7