Skip to content

Install gate, Phase 2: gate the full would-install set (tree pass)#112

Open
juangaitanv wants to merge 3 commits into
install-gate-phase-1from
install-gate-phase-2
Open

Install gate, Phase 2: gate the full would-install set (tree pass)#112
juangaitanv wants to merge 3 commits into
install-gate-phase-1from
install-gate-phase-2

Conversation

@juangaitanv

Copy link
Copy Markdown
Contributor

Phase 2 of the install-gate restart — SHIP

Stacked on #111 (Phase 1). Base: install-gate-phase-1 — retargets up the stack automatically as parents merge.

Extends the gate from named targets to the full would-install set.

Scope (per the PRD)

  • pip tree via pip install --dry-run --report - (--only-binary :all: so resolution never builds sdists / runs package code).
  • npm tree via an isolated npm install --package-lock-only --ignore-scripts in a throwaway temp dir — never touches the project lockfile.
  • Bare npm install gated from the nearest-ancestor package.json (found the way npm finds it, incl. from a subdirectory).
  • npm ci (and aliases) gated from the lockfile directly, with an unparsable-lockfile refusal (--force escape).
  • Transitive findings labeled by provenance: (transitive), (from requirements), (already in package.json) — with a fix with: corgea npm install <pkg>@<fix> hint — and (locked).
  • Honest named-only fallback: a failed dry-run or an npm root-redirect flag (--prefix, -g) degrades loudly; pip -r requirements.txt entries are still parsed and verified in that fallback.
  • Bare installs that block blame the existing tree explicitly when no finding was added by the command.
  • Bounded verdict pool fixed at 8 (no --concurrency flag — a deliberate dead end from the spike), progress line above 8 jobs, repeated-outage error collapsing above 3.

Out of scope (later phases): uv/yarn/pnpm, --json, auth.

Exit criteria — met

A vulnerable transitive dep blocks the install. A vulnerable lockfile blocks npm ci. Fallback warnings fire when and only when the tree pass didn't run.

All three covered by hermetic e2e tests (tests/cli_tree.rs, cli_bare_install.rs, cli_npm_ci.rs, cli_provenance.rs) and confirmed against real npm/pip resolution on the test-cli fixtures (monorepo ancestor-walk, -r requirements.txt trees). ./harness check green.

Also de-flakes the config test: vuln_api_url resolution is now tested as a pure function instead of mutating process env (which raced concurrent getenv under the parallel harness).

🤖 Generated with Claude Code

Comment thread src/precheck/tree.rs
Comment thread src/precheck/tree.rs
Comment thread src/precheck/mod.rs
Comment thread src/precheck/tree.rs Outdated
Comment thread src/precheck/tree.rs Outdated
Comment thread src/precheck/tree.rs Outdated
@juangaitanv juangaitanv force-pushed the install-gate-phase-1 branch from 68dbba9 to 5a99db0 Compare June 12, 2026 14:51
juangaitanv added a commit that referenced this pull request Jun 12, 2026
…hrinkwrap, lockfile v1, workspace stanzas

Addresses Cursor review on #112.

- SECURITY: the pip resolver's `--only-binary :all:` guard is now appended
  AFTER the user's args. pip format-control is last-wins, so a user
  `--no-binary :all:` could previously re-enable sdist builds and execute
  package code during the report step. e2e test pins the arg order.
- a bare `npm install --prefix <other>` (and `npm ci --prefix/-C`) now
  fails closed unless --force: it installs a redirected project's tree the
  gate can't resolve from the CWD, so it would otherwise install wholly
  unchecked. (Named installs still verify their targets + degrade the tree
  pass to the named-only warning.)
- npm lockfile reads now prefer npm-shrinkwrap.json (npm's precedence) over
  package-lock.json, in both the tree resolver and `npm ci`.
- parse_npm_lockfile supports lockfileVersion 1 (`dependencies` tree,
  recursing nested deps) so a v1 project's `npm ci` isn't forced to bypass
  the gate with --force.
- parse_npm_lockfile restricts the verdict set to `node_modules/` entries,
  excluding workspace SOURCE stanzas (`packages/foo`) — local packages with
  no registry identity that would otherwise be sent to the public vuln-api
  and could falsely block a monorepo install.
@juangaitanv juangaitanv force-pushed the install-gate-phase-2 branch from 936d69a to dd4b575 Compare June 12, 2026 14:51
@juangaitanv juangaitanv force-pushed the install-gate-phase-1 branch from 5a99db0 to ddd215b Compare June 12, 2026 16:42
juangaitanv added a commit that referenced this pull request Jun 12, 2026
…hrinkwrap, lockfile v1, workspace stanzas

Addresses Cursor review on #112.

- SECURITY: the pip resolver's `--only-binary :all:` guard is now appended
  AFTER the user's args. pip format-control is last-wins, so a user
  `--no-binary :all:` could previously re-enable sdist builds and execute
  package code during the report step. e2e test pins the arg order.
- a bare `npm install --prefix <other>` (and `npm ci --prefix/-C`) now
  fails closed unless --force: it installs a redirected project's tree the
  gate can't resolve from the CWD, so it would otherwise install wholly
  unchecked. (Named installs still verify their targets + degrade the tree
  pass to the named-only warning.)
- npm lockfile reads now prefer npm-shrinkwrap.json (npm's precedence) over
  package-lock.json, in both the tree resolver and `npm ci`.
- parse_npm_lockfile supports lockfileVersion 1 (`dependencies` tree,
  recursing nested deps) so a v1 project's `npm ci` isn't forced to bypass
  the gate with --force.
- parse_npm_lockfile restricts the verdict set to `node_modules/` entries,
  excluding workspace SOURCE stanzas (`packages/foo`) — local packages with
  no registry identity that would otherwise be sent to the public vuln-api
  and could falsely block a monorepo install.
@juangaitanv juangaitanv force-pushed the install-gate-phase-2 branch from dd4b575 to c86aa2d Compare June 12, 2026 16:42
Harvested from the install-vuln-gate spike (dfac68e), trimmed to pip/npm
(no uv, no --json, public mode only).

- pip tree via `pip install --dry-run --report -` (--only-binary :all:
  so resolution never builds sdists); npm tree via an isolated
  `npm install --package-lock-only --ignore-scripts` in a throwaway dir
  that never touches the project lockfile
- bare `npm install` gated from the nearest-ancestor package.json (like
  npm finds it); `npm ci` and aliases gated from the lockfile directly,
  with an unparsable-lockfile refusal (--force escape)
- transitive findings labeled by provenance: (transitive),
  (from requirements), (already in package.json) — with a
  "fix with: corgea npm install <pkg>@<fix>" hint — and (locked)
- honest named-only fallback: a failed dry-run or an npm root-redirect
  flag (--prefix, -g) degrades loudly, and pip -r requirements.txt
  entries are still parsed and verified in the fallback
- bare installs that block blame the existing tree explicitly when no
  finding was added by the command
- bounded verdict pool (fixed at 8) with a progress line above 8 jobs;
  repeated outage errors collapse into one line above 3 findings

Also de-flakes the config test: vuln_api_url resolution is tested as a
pure function instead of mutating process env, which raced concurrent
getenv calls under the parallel test harness.
…hrinkwrap, lockfile v1, workspace stanzas

Addresses Cursor review on #112.

- SECURITY: the pip resolver's `--only-binary :all:` guard is now appended
  AFTER the user's args. pip format-control is last-wins, so a user
  `--no-binary :all:` could previously re-enable sdist builds and execute
  package code during the report step. e2e test pins the arg order.
- a bare `npm install --prefix <other>` (and `npm ci --prefix/-C`) now
  fails closed unless --force: it installs a redirected project's tree the
  gate can't resolve from the CWD, so it would otherwise install wholly
  unchecked. (Named installs still verify their targets + degrade the tree
  pass to the named-only warning.)
- npm lockfile reads now prefer npm-shrinkwrap.json (npm's precedence) over
  package-lock.json, in both the tree resolver and `npm ci`.
- parse_npm_lockfile supports lockfileVersion 1 (`dependencies` tree,
  recursing nested deps) so a v1 project's `npm ci` isn't forced to bypass
  the gate with --force.
- parse_npm_lockfile restricts the verdict set to `node_modules/` entries,
  excluding workspace SOURCE stanzas (`packages/foo`) — local packages with
  no registry identity that would otherwise be sent to the public vuln-api
  and could falsely block a monorepo install.
… .npmrc rationale

- SECURITY: pip applies format-control directives found INSIDE a -r file
  AFTER command-line parsing, so a '--no-binary :all:' line in a
  requirements file overrode the tree pass's trailing '--only-binary
  :all:' guard and built sdists — executing package code — during the
  dry-run. The tree pass now pre-scans -r files (transitively, following
  nested includes) and refuses to dry-run any file carrying a
  format-control directive, degrading to the named-only fallback whose
  parser skips option lines. The scan is best-effort on unreadable files
  (same uid — pip can't read them either and fails loudly itself).
  Unit + e2e tests prove the dry-run never executes against such a file
  while the file's entries still verdict.
- collect_v1_dependencies carries an explicit depth cap (serde_json's
  recursion limit fires first today; the cap is the backstop), with a
  depth-bomb test.
- Documented why the .npmrc copy is safe (CLI flags win; package-lock=false
  degrades to named-only by design).
- Adapted the phase-1 alternate-spelling test to the tree-aware fake pip.
@juangaitanv juangaitanv force-pushed the install-gate-phase-1 branch from ddd215b to 5fb3e5d Compare June 12, 2026 18:28
@juangaitanv juangaitanv force-pushed the install-gate-phase-2 branch from c86aa2d to 33da96b Compare June 12, 2026 18:28
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