Skip to content

Install gate, Phase 1: core gate — corgea pip|npm install <named targets>#111

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

Install gate, Phase 1: core gate — corgea pip|npm install <named targets>#111
juangaitanv wants to merge 3 commits into
install-gate-phase-0from
install-gate-phase-1

Conversation

@juangaitanv

@juangaitanv juangaitanv commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Overview

This PR is Phase 1 of the install-gate feature for the Corgea CLI.

Phase 1 is the first user-facing slice: corgea npm install <target> and corgea pip install <target> now gate named install targets before delegating to the real package manager.

The gate resolves the concrete package version that would install, checks publish recency, checks Corgea's vuln-api verdict, and prints refusal output that an agent can use to self-correct to a safe version. This phase is public mode only: no token is sent, and vuln-api outages warn and continue.

Stacked on #110. Base branch: install-gate-phase-0. Review this PR's diff in isolation; it contains the Phase 1 slice.

What Phase 1 Includes

  • Install-verb detection behind global package-manager flags. For example, npm --loglevel silent install x is still gated because the verb is found outside flag values.
  • Named-spec parsing for npm and PyPI targets, including exact pins, ranges, npm semver/dist-tags, npm --tag, PEP 440 specifiers, wildcards, post-releases, and pip --pre.
  • Registry resolution against npm and PyPI to find the concrete version that would install, plus publish time. Yanked PyPI releases resolve only through exact pins, matching pip.
  • Two independent blocking checks: publish recency (-t, default 2d) and vuln-api verdict.
  • Agent-correctable refusal output: per-advisory fixed in <version> lines and a safe version steer that names the highest version covering every advisory.
  • Override controls: --force bypasses all install-gate blocks; --no-fail demotes recency failures only.
  • Unsupported target handling: git, URL, path, editable, PEP 508 name @ url, and npm user/repo specs pass through with a note and are not blocked.
  • Custom registry disclosure: custom npm registry and pip index flags warn that the gate resolved against the default registry and cannot vouch for mirrored artifacts.
  • Public fail-open mode: lookup outages warn and continue because authenticated enforcement lands later.
  • Docs: skills/corgea/SKILL.md documents the wrapper behavior and limitations.

Deliberately Out Of Scope

Later phases add:

  • transitive dependency and tree resolution
  • bare installs
  • npm ci
  • full requirements-file tree handling
  • --json
  • token auth and fail-closed enforcement
  • uv, yarn, pnpm
  • retries

Exit Criteria - Met

All deterministic staging targets block with exit 1; a real agent session self-corrects to the safe version.

Verified end-to-end against the live staging worker through loopback registry and vuln-api proxies with real npm/pip resolution:

Target Result
axios@0.21.0 blocked, exit 1, safe steer to axios@0.21.2
minimist@0.0.8 blocked, exit 1, safe steer to minimist@1.2.2
node-fetch@2.6.0 blocked, exit 1, safe steer to node-fetch@2.6.7
mezzanine==6.0.0 blocked, exit 1, no fixed version known

Installing the steered axios@0.21.2 passes the gate. ./harness check passes.

Comment thread src/config.rs
Comment thread src/main.rs
Comment thread src/precheck/parse.rs
Comment thread src/verify_deps/registry.rs Outdated
juangaitanv added a commit that referenced this pull request Jun 12, 2026
…gistry

Addresses Cursor review on #111.

- npm `--tag <value>` now resolves the named dist-tag for a bare spec
  (`npm install --tag beta pkg` gates the beta release, not latest), so a
  fresh/vulnerable beta/canary no longer bypasses both blocks. Explicit
  pins/tags still win.
- pip `--pre` makes prereleases eligible: PypiVersion now parses PEP 440
  prereleases (dev<a<b<rc, all below the plain release) and the resolver
  includes them only when `--pre` is set, so the gate verdicts the
  prerelease pip would install instead of the latest stable.
- a custom registry/index flag (`--registry`, `-i`, `--index-url`,
  `--extra-index-url`) now prints a loud warning that the gate resolves
  against the default registry and can't vouch the mirrored artifact —
  full mirror resolution / allow-listing stays out of scope (documented
  limitation, separate PRD).
@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
…gistry

Addresses Cursor review on #111.

- npm `--tag <value>` now resolves the named dist-tag for a bare spec
  (`npm install --tag beta pkg` gates the beta release, not latest), so a
  fresh/vulnerable beta/canary no longer bypasses both blocks. Explicit
  pins/tags still win.
- pip `--pre` makes prereleases eligible: PypiVersion now parses PEP 440
  prereleases (dev<a<b<rc, all below the plain release) and the resolver
  includes them only when `--pre` is set, so the gate verdicts the
  prerelease pip would install instead of the latest stable.
- a custom registry/index flag (`--registry`, `-i`, `--index-url`,
  `--extra-index-url`) now prints a loud warning that the gate resolves
  against the default registry and can't vouch the mirrored artifact —
  full mirror resolution / allow-listing stays out of scope (documented
  limitation, separate PRD).
@juangaitanv juangaitanv force-pushed the install-gate-phase-1 branch from 5a99db0 to ddd215b Compare June 12, 2026 16:42
Harvested from the install-vuln-gate spike (dfac68e), trimmed to
named-target paths: no tree resolution, no uv/yarn/pnpm, no --json, no
token auth — public fail-open mode only.

- corgea npm|pip wrap their package manager: install verbs (found
  behind global flags) gate named targets; everything else passes
  through with the manager's own exit code
- two independent blocks: publish recency (-t, default 2d) and the
  vuln-api verdict on each resolved version
- refusal output built for agent self-correction: per-advisory
  "fixed in <version>" lines and a "→ safe version:" steer naming the
  highest fix covering every advisory
- --force overrides everything; --no-fail demotes recency only
- git/URL/path/editable specs are noted, never blocked; -r files and
  bare installs noted, not gated
- public mode fails open: vuln-api outages warn and continue
- pip→pip3 binary fallback; pip3/pip-add "did you mean" guidance
- SKILL.md: install-wrapper section with limitations + staging targets

Verified end-to-end: all four deterministic staging targets
(axios@0.21.0, minimist@0.0.8, node-fetch@2.6.0, mezzanine==6.0.0)
block with exit 1 and steer to the fixed version; installing the
steered version passes.
…gistry

Addresses Cursor review on #111.

- npm `--tag <value>` now resolves the named dist-tag for a bare spec
  (`npm install --tag beta pkg` gates the beta release, not latest), so a
  fresh/vulnerable beta/canary no longer bypasses both blocks. Explicit
  pins/tags still win.
- pip `--pre` makes prereleases eligible: PypiVersion now parses PEP 440
  prereleases (dev<a<b<rc, all below the plain release) and the resolver
  includes them only when `--pre` is set, so the gate verdicts the
  prerelease pip would install instead of the latest stable.
- a custom registry/index flag (`--registry`, `-i`, `--index-url`,
  `--extra-index-url`) now prints a loud warning that the gate resolves
  against the default registry and can't vouch the mirrored artifact —
  full mirror resolution / allow-listing stays out of scope (documented
  limitation, separate PRD).
…tag, no resolution guessing

- pypi resolution adopts the registry's canonical spelling (info.name,
  guarded to PEP 503-equivalent values so a hostile mirror can't redirect
  the verdict to another package's identity). The vuln-api keys advisories
  by lowercase(canonical), so checking a user-typed variant (Flask_Cors)
  would miss the flask-cors row and fail open.
- npm --tag is last-wins like npm's own config parser; gating the first
  of two --tag flags verdicts the wrong dist-tag.
- pick_latest_stable no longer guesses by upload time when nothing parses
  as PEP 440 (could pick a prerelease without --pre); a visible
  resolution error replaces the silent wrong pick.
- Resolution-error output now states the target is ungated.
Comment thread tests/cli_verdict.rs
key("pypi", "oldpkg", "1.0.0"),
vulnerable_body("pypi", "oldpkg", "1.0.0", "MAL-2024-0001", Some("2.0.0")),
);
let mut h = pip_harness(checks, HashMap::new(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

should we add npm_harness and mirror the same test coverage we have for pip here

Comment thread skills/corgea/SKILL.md
| `--force` | | Proceed despite all findings (vulnerable, recent). Findings still print. |

Overrides for testing: `CORGEA_PYPI_REGISTRY`, `CORGEA_NPM_REGISTRY`,
`CORGEA_VULN_API_URL`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

should we tell the agents what the overrides are expected to be?

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.

2 participants