Skip to content

Install gate, Phase 0: vuln-api contract + test harness#110

Open
juangaitanv wants to merge 5 commits into
mainfrom
install-gate-phase-0
Open

Install gate, Phase 0: vuln-api contract + test harness#110
juangaitanv wants to merge 5 commits into
mainfrom
install-gate-phase-0

Conversation

@juangaitanv

Copy link
Copy Markdown
Contributor

Phase 0 of the install-gate restart

First of a stacked series implementing PRD.md (Gate Package Installs). The previous attempt (branch install-vuln-gate, 60+ commits, ~10.7k lines) built the right thing but was unreviewable as one PR. This restart lands one phase per PR, each with explicit exit criteria. Later phases stack on this branch as it merges.

This PR is foundation only — no user-facing command ships here.

What's in scope (per the PRD)

The vuln-api client and its versioned contract, the in-process test stub, and the shared integration-test scaffold:

  • src/vuln_api/ — blocking client for GET /v1/packages/{eco}/{name}/versions/{ver}/check. Independent of the shared CLI HTTP client (the host is user-configurable via CORGEA_VULN_API_URL, so it must never replay Corgea cookies/redirects). Status mapping, a confused-deputy identity guard (the response's (eco, name, version) must match the request), and request-time PEP 503 name normalization so an alternate spelling can't miss and read as clean. Comparisons are case-insensitive — staging spells pypi "PyPI".
  • src/vuln_api_stub/ — minimal in-process TCP stub, gated out of release builds behind a test-stub cargo feature (enabled for every test build via a self dev-dependency). No standalone binary.
  • tests/common/ — the GateHarness scaffold later phases build on (isolated corgea, private PATH of fake package managers, registry + vuln-api stubs).
  • tests/fixtures/vuln_api/ — the four committed contract bodies (clean / vulnerable / malware / unknown), matching the authoritative server serialization.
  • tests/vuln_api_contract.rs — contract tests against the stub (hermetic, always on) and the staging worker (#[ignore], network).

Deliberately out of scope (later phases): any user-facing command, auth/token handling, retries.

Exit criteria — met

Contract tests green against both the stub and staging. Another phase can write an integration test in under 20 lines of setup.

  • Hermetic contract tests + 118 in-crate unit tests pass via cargo test.
  • Staging contract tests pass against the live worker:
    cargo test --test vuln_api_contract -- --ignored
    # 5 passed (axios@0.21.0, minimist@0.0.8, node-fetch@2.6.0, mezzanine==6.0.0, unknown)
    
  • ./harness check (strict clippy, fmt, tests, suppression report) passes.

Also included

A one-line prerequisite fix (0e5beb4): tests/cli_deps.rs's git helper now scrubs inherited GIT_* env. Running the suite from the pre-commit hook in a worktree leaked GIT_DIR into the tests' subprocesses, pointing their git init at the developer's repo (locked mid-commit) instead of the temp dir. Required for the harness to pass cleanly under the commit hook.

Review notes

  • The staging worker (https://cve-worker-staging.corgea.workers.dev) is the current default endpoint (DEFAULT_VULN_API_URL); the production-worker handoff and seed-data ownership are open questions in the PRD, not blockers for this phase.
  • Stub key normalization mirrors the client's, so a fixture keyed under an alternate pypi spelling still serves a request for the canonical name.

🤖 Generated with Claude Code

Running the suite from a git hook (e.g. pre-commit in a worktree) leaks
GIT_DIR into the tests' subprocesses, pointing their git init at the
developer's repo — locked mid-commit — instead of the temp dir.
The vuln-api client and its versioned contract (clean / vulnerable /
malware / unknown verdicts, remediation data), harvested from the
install-vuln-gate spike (dfac68e) and trimmed to phase scope: public
unauthenticated lookups only, no retries, no user-facing command.

- src/vuln_api: blocking client for /v1/packages/.../check with status
  mapping, identity guard, and PEP 503 request-time normalization
- src/vuln_api_stub: in-process TCP stub, gated out of release builds
  via the test-stub feature + self dev-dependency
- tests/common: shared GateHarness scaffold for later phases
- tests/vuln_api_contract.rs: contract tests against the stub (hermetic)
  and the staging worker (#[ignore], deterministic targets documented in
  tests/fixtures/vuln_api/README.md)
Comment thread src/vuln_api/mod.rs Outdated
Comment thread tests/common/mod.rs
Comment thread tests/vuln_api_contract.rs
…, staging CI

Addresses Cursor review on #110.

- vuln_api identity guard now applies the ecosystem's canonical-name rule
  to the response package_name before comparing, not just
  eq_ignore_ascii_case. A response echoing the stored spelling
  (`flask_cors` for a `flask-cors` request — PEP 503-equivalent) no longer
  trips the guard and fails the gate closed for valid pypi packages with
  `_`/`.` in their names. New unit test covers it.
- tests/harness_smoke.rs exercises the GateHarness scaffold directly (fake
  package manager on PATH, registry stub, vuln-api stub) so the wiring
  can't silently regress before a later phase drives it end-to-end.
- .github/workflows/staging-contract.yml runs the #[ignore]d staging
  contract tests on a daily schedule (non-blocking) so endpoint/schema/seed
  drift is caught out-of-band instead of shipping undetected.
cargo test runs the module's tests on parallel threads. Five of them bind
ephemeral ports, and between port_is_available_reflects_current_port_usage's
drop(listener) and its re-check, a concurrent :0 bind could be handed the
just-freed port, flipping the second assert to false.

Add a module-level PORT_TEST_LOCK that all five port-binding tests acquire.
The async test scopes the guard to the synchronous reserve so no lock is held
across .await (keeps clippy::await_holding_lock clean).
…riant docs, harness opt-out

- pypi wire names now use the server's rule (lowercase + trim,
  worker.js normalizePackageName), NOT PEP 503: collapsing
  zope.interface to zope-interface missed the stored advisory row and
  read vulnerable dotted/underscored packages as clean. PEP 503 remains
  the identity-comparison rule (Ecosystem::request_name vs
  normalize_name), and the stub's key() now mirrors the server so the
  divergence can't be masked in tests again.
- Module/auth docs no longer claim lookups are permanently
  unauthenticated: production /check requires a Corgea token (staging
  runs VULN_API_REQUIRE_AUTH=false); token wiring lands with
  authenticated mode. Test renamed public_check_sends_no_auth_headers.
- GateHarness::without_vuln_api() opt-out for no-endpoint tests.
- utils/api.rs get_source() delegates to the cached vuln_api::source().
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