Skip to content

Gate bare npm installs; honest ungated note for yarn/pnpm/uv#102

Merged
juangaitanv merged 1 commit into
install-vuln-gatefrom
ivg/u2-bare-npm-gate
Jun 11, 2026
Merged

Gate bare npm installs; honest ungated note for yarn/pnpm/uv#102
juangaitanv merged 1 commit into
install-vuln-gatefrom
ivg/u2-bare-npm-gate

Conversation

@juangaitanv

Copy link
Copy Markdown
Contributor

What

Unit 2 of the install-vuln-gate dogfood frictions.

  • Bare npm install is now gated. tree::covers_input is also true for npm with zero targets when ./package.json exists, so the existing tree pass resolves the lockfile set (0 named, N transitive) and verdicts everything. Vulnerable/unverifiable blocks fail-closed (exit 1), --force escapes, recency stays named-only, and a resolution failure degrades to the NamedOnly warning + exec — all unchanged semantics (should_block_install untouched).
  • Honest note for yarn/pnpm/uv. Install-shaped commands with zero targets print one stderr line: note: bare '<pm> <sub>' is not gated (no safe dry-run) — dependencies install unchecked, then exec as before.
  • Report header fix (code-review finding): Pre-checking npm install `` no longer renders a trailing space when the gated arg list is empty (first path where that happens).
  • SKILL.md offline-inputs sentence (lines ~120-122) rewritten to match reality: git/URL/path specs noted, bare npm gated with token, bare yarn/pnpm/uv noted as ungated.

Tests

  • New tests/cli_bare_install.rs (12 tests): vulnerable lockfile blocks / clean proceeds / --force / --json tree object / resolution-failure fallback / no-package.json passthrough / tokenless passthrough / yarn+pnpm+uv (add, pip install) notes / note absent on named installs.
  • ./harness check green (clippy -D warnings, fmt, 264 tests, deps-skill drift).

Live staging smoke

Against cve-worker-staging.corgea.workers.dev with a poisoned package.json (axios@0.21.0):

Pre-checking `npm install` (threshold 2d)
  0 ok, 0 recent, 1 vulnerable, 0 unverifiable, 0 skipped, 0 errors
  tree: 2 packages resolved, 2 transitive checked
  ✗ axios@0.21.0 (transitive)  known vulnerable:
      CVE-2021-3749 (high) — fixed in 0.21.2
      ...
      → safe version: axios@0.21.2
Refusing to run install. Pass --force to proceed despite findings.
exit=1

corgea yarn|pnpm install and corgea 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 bare npm install --omit=dev can block on a dev-only lockfile vulnerability that would not actually install. Same property as the existing named-target npm tree pass; --force escapes.

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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for the blocker inline: bare npm gating can be bypassed or can mutate a real target lockfile when npm install-root flags are present.

Open in Web View Automation 

Sent by Cursor Automation: pr-flow

Comment thread src/precheck/tree.rs
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())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@juangaitanv juangaitanv merged commit 837e437 into install-vuln-gate Jun 11, 2026
17 checks passed
@juangaitanv juangaitanv deleted the ivg/u2-bare-npm-gate branch June 11, 2026 07:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant