Label install-gate tree findings by provenance (requested / pre-existing / transitive)#103
Conversation
Tree-pass findings were all labeled (transitive), which misled when the flagged package came from the user's own requirements file or was already a direct dep of the project. - TreePackage carries pip's per-item "requested" report flag (npm: false). - New TreeOrigin on TreeOutcome: Requested / PreExisting / Transitive. Requested = pip-requested leftovers (-r files); PreExisting = npm leftovers named in the project package.json's direct deps (all four dep groups), read from cwd; Transitive otherwise. - Text labels: (from requirements), (already in package.json), (transitive). JSON tree entries gain an "origin" field. - PreExisting findings with an advertised fix get a hint: fix with: corgea npm install <name>@<fix> (advertised fix). advertised_fix() takes the max parseable fixed_version across matches, ignoring fixless matches — deliberately weaker than safe_version's certification and independent of it. should_block_install semantics unchanged. New hermetic e2e coverage in tests/cli_provenance.rs (fake PM + registry stub + vuln-api stub).
| fn advertised_fix(matches: &[crate::vuln_api::VulnMatch]) -> Option<String> { | ||
| let mut fixes: Vec<&str> = matches | ||
| .iter() | ||
| .filter_map(|m| m.fixed_version.as_deref()) |
There was a problem hiding this comment.
advertised_fix() intentionally drops any advisory that does not provide fixed_version, but the caller renders the result as fix with: corgea npm install .... Evidence: the existing safe_version() path returns None when any match has no fix, while the new test at src/precheck/mod.rs explicitly asserts advertised_fix(&[fixed, none]) == Some(...). Impact: a package with multiple advisories can get a concrete "fix with" command even though one advisory is still known to have no fixed version, so users may apply the suggested update believing the finding is remediated when it is not. Fix: use safe_version(matches) for command-style remediation, or change this output to non-command informational text that clearly says only some advisories advertise that version and the gate may still fail.
| let Ok(manifest) = serde_json::from_str::<serde_json::Value>(json) else { | ||
| return Default::default(); | ||
| }; | ||
| let groups = [ |
There was a problem hiding this comment.
This collapses dependencies, devDependencies, optionalDependencies, and peerDependencies into a name-only set, but src/precheck/mod.rs later prints one generic corgea npm install <pkg>@<fix> command for every PreExisting finding. Impact: for a vulnerable dev/optional/peer dependency, the suggested command can rewrite the dependency into the wrong manifest section or omit the required npm flag (--save-dev, --save-optional, peer handling), which is unsafe remediation guidance for production users. Fix: preserve the dependency group in the provenance data and tailor/omit the fix hint unless the command can preserve the original dependency type; add tests for dev/optional/peer dependencies, not just dependencies.


Unit 3: provenance labels for tree findings
Fixes the misleading blanket
(transitive)label on tree-pass findings in thecorgea <pm> installvulnerability gate.What changed
TreePackagenow carries pip's per-item"requested"flag from the--reportJSON (previously discarded; alwaysfalsefor npm — its lockfile has no equivalent).TreeOutcomegains aTreeOrigin:Requested(pip-requested leftovers, i.e.-rrequirements files),PreExisting(npm leftovers named in the projectpackage.json's direct deps — all four dep groups, read from cwd),Transitive(everything else).(from requirements),(already in package.json),(transitive).--jsontree entries gain an"origin"field (requested/pre-existing/transitive).fix with: corgea npm install <name>@<fix> (advertised fix).advertised_fix()is the max parseablefixed_versionacross matches, ignoring fixless matches — deliberately weaker thansafe_version's all-fixes certification and independent of it (degrades gracefully if the verified-steer line is absent).should_block_installsemantics untouched.Tests
requestedparsing (incl. missing-field default), manifest direct-dep extraction (all groups + degrade-to-empty), origin assignment precedence,advertised_fix(ignores fixless, max-by-semver).tests/cli_provenance.rs(fake PM script + inline registry stub + vuln-api stub): pip-rlabel, npm pre-existing label + fix hint, no hint without a fix, JSON origins for both ecosystems../harness checkgreen (263 tests, clippy -D warnings, fmt).Live staging smoke (cve-worker-staging)
corgea npm install mkdirp@0.5.1→✗ minimist@0.0.8 (transitive), blocked.minimist: 0.0.8inpackage.json, installingmkdirp@1.0.4→✗ minimist@0.0.8 (already in package.json)+fix with: corgea npm install minimist@1.2.2 (advertised fix);--jsonshows"origin": "pre-existing".corgea pip install -r reqs.txt(mezzanine==6.0.0) →✗ Mezzanine@6.0.0 (from requirements), blocked (no fixed version advertised → no hint, as intended).Notes for the coherence merge
advertised_fixintentionally duplicates part ofsafe_version's max-by-lenient-semver scaffolding rather than depending on the parallel verified-steer unit; worth unifying once both land.PreExistingeven if that particular instance is a nested copy at another version — name+version is all the leftover carries. The label stays truthful and the hint (bump the manifest dep) is the right remediation either way.