Gate bare npm installs; honest ungated note for yarn/pnpm/uv#102
Conversation
Zero-spec `corgea npm install` with a package.json and a token now runs the existing tree pass: the lockfile-resolved set (0 named, N transitive) is verdicted, a vulnerable lockfile blocks fail-closed, --force escapes, and a resolution failure degrades to the named-only warning + exec as before. Bare yarn/pnpm/uv install-shaped commands print one stderr line (`note: bare '<pm> <sub>' is not gated …`) instead of silently running unchecked. The gated report header no longer renders a trailing space when the arg list is empty. SKILL.md offline-inputs sentence rewritten to match.
| pub fn covers_input(manager: PackageManager, parsed: &super::parse::ParsedInstall) -> bool { | ||
| !parsed.targets.is_empty() | ||
| || (manager == PackageManager::Pip && !parsed.requirements_files.is_empty()) | ||
| || (manager == PackageManager::Npm && std::path::Path::new("package.json").exists()) |
There was a problem hiding this comment.
Blocking: this makes zero-target npm installs eligible based only on ./package.json, but the resolver still forwards the original npm args. npm args can change the install root, so the tree pass no longer matches the install it is gating.
Evidence: npm install --prefix app has no parsed targets because --prefix is treated as a flag/value pair, then this line gates solely on the cwd manifest. resolve_npm_tree later runs in a temp dir while forwarding --prefix app; npm resolves that relative to the temp dir, fails with ENOENT, run_tree_pass returns NamedOnly, and with zero named targets should_block_install is false, so the real install runs unchecked. With an absolute prefix, npm resolves the real target path from inside the temp resolver; I reproduced with npm 10.9.7 that the precheck command creates app/package-lock.json in the real project before any verdict is evaluated, violating the resolver comment that the user's lockfile is never touched.
Impact: a bare npm install that should be gated can bypass the vuln gate, and in the absolute-prefix case the precheck can mutate user files even if a later verdict would block the install.
Concrete fix: resolve npm install-root-changing args (--prefix at minimum, and workspace/root-selection flags if supported) before declaring a bare install covered. The resolver should copy/read the same target project into the sandbox without forwarding path-changing flags that point back to the real filesystem, or conservatively refuse/fail closed for unsupported root-changing bare installs. Add e2e coverage for bare npm install --prefix <relative> and npm install --prefix <absolute> that proves the gate checks the target dependencies and does not create or update the real target lockfile during precheck.


What
Unit 2 of the install-vuln-gate dogfood frictions.
npm installis now gated.tree::covers_inputis also true for npm with zero targets when./package.jsonexists, so the existing tree pass resolves the lockfile set (0 named, N transitive) and verdicts everything. Vulnerable/unverifiable blocks fail-closed (exit 1),--forceescapes, recency stays named-only, and a resolution failure degrades to the NamedOnly warning + exec — all unchanged semantics (should_block_installuntouched).note: bare '<pm> <sub>' is not gated (no safe dry-run) — dependencies install unchecked, then exec as before.Pre-checkingnpm install `` no longer renders a trailing space when the gated arg list is empty (first path where that happens).Tests
tests/cli_bare_install.rs(12 tests): vulnerable lockfile blocks / clean proceeds /--force/--jsontree object / resolution-failure fallback / no-package.json passthrough / tokenless passthrough / yarn+pnpm+uv (add,pip install) notes / note absent on named installs../harness checkgreen (clippy -D warnings, fmt, 264 tests, deps-skill drift).Live staging smoke
Against
cve-worker-staging.corgea.workers.devwith a poisonedpackage.json(axios@0.21.0):corgea yarn|pnpm installandcorgea uv add(bare) each printed the ungated note.Known edge (pre-existing, not introduced here)
The lockfile tree pass ignores
--omit=dev/--production, so a barenpm install --omit=devcan block on a dev-only lockfile vulnerability that would not actually install. Same property as the existing named-target npm tree pass;--forceescapes.