test(hosted): production e2e for scan --mode hosted across every ecosystem and package manager - #141
Conversation
…osystem and package manager
Adds `crates/socket-patch-cli/tests/e2e_hosted_production.rs`: the first suite
that exercises hosted mode against the REAL production service with no mocking
anywhere. Every other hosted-mode capstone (`e2e_redirect_*_build.rs`) serves the
patched artifact from a local wiremock, which proves the rewrite grammar but
cannot notice production drifting away from the CLI.
Each leg installs a pinned vulnerable dependency from its real upstream registry,
asserts the bytes are pristine, runs `scan --mode hosted`, asserts the lockfile
now pins patch.socket.dev, then WIPES the install tree and reinstalls from the
rewritten lock alone — so the package manager itself fetches from Socket and
verifies the integrity pin it was handed. That last step is the point: it is the
only place in the repo where a third-party package manager, not socket-patch,
downloads a Socket-hosted artifact and independently checks its checksum.
Coverage — 14 legs, all green:
npm package-lock, npm-shrinkwrap, pnpm, yarn classic, yarn berry, bun
PyPI requirements.txt, uv.lock
Cargo per-patch sparse registry
gem redirect asserted (install blocked by a server defect, below)
golang documented refusal (negative assertion)
deno unsupported (negative assertion)
preflight canary that the four required patches are still published
canary maven/nuget/composer, which have zero free published patches today
Runs against the unauthenticated public proxy on purpose — no API token is used
or needed, and SOCKET_API_TOKEN is scrubbed from the child environment.
CI: new `hosted-e2e` job intended as a required status check. It has no
job-level `if:`, no `needs:`, no matrix and no continue-on-error, because a
SKIPPED required check can wedge a PR at "Expected — waiting for status"; the
kill switch gates the steps, never the job. Set the repo variable
HOSTED_E2E_DISABLED=true and re-run to bypass without a commit, or use the new
workflow_dispatch `hosted_e2e` input for a one-off force/skip. The suite stays
`#[ignore]`-gated, so the existing `test` and `e2e` jobs are unaffected.
Three real issues surfaced by running against production, documented in
docs/testing/hosted-production-e2e.md:
* gem hosted mode is unusable for gems with dependencies — the Socket gem
patch-registry compact index omits runtime deps, so bundler fails closed
with APIResponseMismatchError (SERVER fix)
* pnpm 11 rejects hosted lockfiles by default with
ERR_PNPM_TARBALL_URL_MISMATCH; `--trust-lockfile` is the opt-out and the
CLI should warn about it (CLI UX fix)
* the uv.lock rewriter points the `sdist` entry at the patched wheel while
keeping the sdist's original `size` (CLI, minor)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No dependency changes detected. Learn more about Socket for GitHub. 👍 No dependency changes detected in pull request |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Retry loop never retries
- Added set +e before cargo test and set -e after capturing status to disable errexit during test execution, allowing the retry loop to function correctly.
Or push these changes by commenting:
@cursor push 5f1965cb50
Preview (5f1965cb50)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -916,9 +916,11 @@
# transient 503 does not block merges through a required check.
for attempt in 1 2 3; do
echo "::group::hosted-e2e attempt $attempt"
+ set +e
cargo test -p socket-patch-cli --test e2e_hosted_production -- \
--ignored --nocapture --test-threads=4
status=$?
+ set -e
echo "::endgroup::"
if [ "$status" -eq 0 ]; then
exit 0You can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 13e5442. Configure here.
| sleep $((attempt * 20)) | ||
| done | ||
| echo "::error title=hosted-e2e::suite failed on all 3 attempts" | ||
| exit 1 |
There was a problem hiding this comment.
Retry loop never retries
High Severity
The hosted-e2e retry loop captures status=$? after cargo test, but GitHub Actions runs steps with bash -e. A failing cargo test aborts the step immediately, so attempts 2 and 3 never run. Transient production 503s — the reason this retry exists for a required check — will still fail the job on the first try.
Reviewed by Cursor Bugbot for commit 13e5442. Configure here.
| .chain(LEGACY_ENV_VARS) | ||
| { | ||
| cmd.env_remove(var); | ||
| } |
There was a problem hiding this comment.
Incomplete API token scrub
Medium Severity
run removes SOCKET_API_TOKEN but not the peer alias SOCKET_CLI_API_TOKEN. The binary promotes that alias onto SOCKET_API_TOKEN at startup, so an ambient socket-cli token still authenticates the child and moves the suite off the free public proxy this test is meant to cover.
Reviewed by Cursor Bugbot for commit 13e5442. Configure here.
| sleep $((attempt * 20)) | ||
| done | ||
| echo "::error title=hosted-e2e::suite failed on all 3 attempts" | ||
| exit 1 |
There was a problem hiding this comment.
Orphaned workflow YAML fragment
Low Severity
if-no-files-found: warn from the setup-matrix upload-artifact step was left at the end of the hosted-e2e run: | script after exit 1. It is unreachable shell text there, and the upload step no longer carries that option.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 13e5442. Configure here.
Three CI-only failures, none reproducible locally: * yarn berry auto-enables hardened mode on a public-PR run, which implies `--immutable` and refuses the lockfile the FIXTURE install has to create (`YN0028`). That install now passes `--no-immutable`; the reinstall-from-redirected-lock leg keeps `--immutable`, since that one is the actual proof and must stay strict. * `pnpm` was not on PATH: a bare `corepack enable` leaves `pnpm --version` failing for a project with no `packageManager` field, which is exactly the pnpm fixture's shape. pnpm and bun now come from `npm install -g`, matching the `e2e` job above, while corepack stays for the two yarn flavors and pre-downloads both pinned versions. `COREPACK_ENABLE_DOWNLOAD_PROMPT=0` is set on both steps so the suite's own `has_command` probes can never hang. * zizmor `ref-version-mismatch`: the setup-go pin carried a `# v5` comment that did not match its SHA. Now uses the exact pin already present elsewhere in this file (v6.4.0, `cache: false`), as do all six actions this job references. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`corepack enable` creates the pnpm shim at the same path `npm install -g pnpm@10` writes, so the two together failed the setup step with EEXIST. pnpm now comes from `corepack prepare pnpm@10 --activate`, which also fixes the original symptom: `corepack enable` alone leaves `pnpm --version` failing for a project with no `packageManager` field — exactly the pnpm fixture's shape — because the shim has no version to resolve. `--activate` supplies that default and pre-downloads it. Only bun, which corepack does not manage, still comes from npm. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pnpm 10 imports `node:sqlite` for its store index. That module does not exist before Node 22 and is only stable in 24, so on the 20.20.2 the other jobs pin, every `pnpm install` in the suite died with ERR_UNKNOWN_BUILTIN_MODULE and STRICT turned the skip into a failure. 13 of 14 legs were already green. This job pins 24 rather than following the repo-wide 20.20.2: it drives real, current package managers against production, so it should track what users actually run. The offline suites keep their pin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>



What
The first test suite that exercises hosted mode (
scan --mode hosted) against the real Socket production service, with no mocking anywhere, using patches that are actually published on production today.Every other hosted-mode capstone in this repo (
e2e_redirect_*_build.rs) serves the patched artifact from a local wiremock. That proves the CLI's rewrite grammar, but it cannot notice production drifting away from the CLI. This suite can.Each leg:
socket-patch scan --mode hosted --json --yesagainstpatches-api.socket.dev;patch.socket.dev+ the expected patch UUID;Step 5 is the point. It is the only place in the repo where a third-party package manager — not socket-patch — downloads a Socket-hosted artifact and independently verifies its checksum.
The suite runs against the unauthenticated public proxy on purpose: no API token is used or needed, and
SOCKET_API_TOKENis scrubbed from the child environment. That is the surface every user without a token gets.Coverage — 14 legs, all green
Maven, NuGet and Composer all implement hosted mode, but production has no free-tier patches for them, so there is nothing real to redirect to. Rather than skipping silently,
canary_unpublished_ecosystemsprobes production every run and reports the moment that changes.Required production patches
The suite is pinned to these.
preflight_required_patches_are_publishedverifies all four every run, so a withdrawn patch produces one clear failure naming the PURL instead of N confusing ones that look like CLI regressions.pkg:npm/minimist@1.2.280630680-4da6-45f9-bba8-b888e0ffd58cpkg:pypi/urllib3@1.26.18de58c8b8…,26242e35…,e828efa5…pkg:cargo/traitobject@0.1.1cf2e6f58-d9fa-4096-9151-c34afa717f89pkg:gem/activestorage@7.0.2.22535d43d-67ce-4944-be27-c19e113997fbdocs/testing/hosted-production-e2e.mddocuments how these were chosen and how to re-pick one if withdrawn. urllib3 carries three patches (one per advisory) and the suite accepts any of them, so a server-side reorder does not go red.Cargo is the thin spot:
traitobjectis the only crate with a free published patch today (4 versions of it).🔴 Three real issues this found
None are test bugs. Full evidence in
docs/testing/hosted-production-e2e.md.1. gem hosted mode is unusable for gems with dependencies — server fix
Socket's gem patch-registry serves a compact index that declares no runtime dependencies, while the
.gemit serves declares six. Bundler fails closed:The compact-index generator must emit the gemspec's runtime dependencies. The test asserts the redirect (correct) and tolerates the install failure, failing loudly if it fails for any other reason.
SOCKET_PATCH_HOSTED_E2E_GEM_STRICT=1promotes it to a hard failure — flip that on as the regression guard once the server is fixed.2. pnpm 11 rejects hosted lockfiles by default — CLI UX fix
pnpm 11 added a lockfile supply-chain policy comparing each entry's tarball URL against the registry's published metadata. Hosted mode deliberately rewrites that URL:
pnpm install --trust-lockfileis pnpm's documented opt-out and works (verified — the patched artifact installs cleanly). Neither--trust-policy-excludenor--no-verify-store-integrityhelps.scan --mode hostedshould emit aredirect_pnpm_*warning naming the flag, the way it already warns forredirect_gem_no_checksums_sectionandredirect_rush_repo_state_stale.3.
uv.locksdistentry rewritten to a wheel URL — CLI, minorThe rewriter points
sdistat the patched wheel and keeps the original sdist'ssize, so URL, hash and size disagree. uv tolerates it because it prefers the wheel; it would bite on--no-binary.CI: the
hosted-e2ejobGated off by default — the suite is
#[ignore]-gated, so the existingtestande2ejobs are untouched. It runs only in the new dedicatedhosted-e2ejob, which is intended to be a required status check.Deliberately no job-level
if:, noneeds:, no matrix, nocontinue-on-error— a skipped required check is ambiguous to branch protection and can wedge a PR at "Expected — waiting for status". The job always runs and always reaches success or failure; the kill switch gates the steps.SOCKET_PATCH_HOSTED_E2E_STRICT=1in CI turns the suite's local "toolchain missing" soft-skips into hard failures, so a required check can never report green on an unexercised leg. The job retries the suite 3× with backoff, because the public proxy intermittently 503s — the documented reason the older live-API suites were pulled from the PR matrix.🔌 Escape hatch
Set a repository variable (Settings → Secrets and variables → Actions → Variables):
then hit Re-run failed jobs on any blocked PR.
varsis read at job-run time, so no commit and no push are needed — the job goes green with a loud::warning::and a BYPASSED banner in the job summary, and every open PR clears on its next re-run. Delete the variable to re-arm.Any value other than exactly
trueleaves the suite armed — a typo must not silently disable production coverage. Verified across all five gate states (default / disabled / typo'd / dispatch-force / dispatch-skip).For a single run: Actions → CI → Run workflow →
hosted_e2e = force | skip.The job runs as soon as this lands, but making it required is a repo setting, not something this PR can do:
Do that after the first green run on
main.Verification
cargo test … --test e2e_hosted_production -- --ignored→ 14 passed, 0 failedSOCKET_PATCH_HOSTED_E2E_STRICT=1→ 14 passed, 0 failed--ignored) → 0 passed, 14 ignored — existing jobs unaffectedcargo clippy --workspace --all-features -- -D warnings→ cleancargo fmt --check→ clean for the new fileci.ymlparses;hosted-e2ehas noif:/needs:/continue-on-error🤖 Generated with Claude Code
Note
Medium Risk
Introduces a branch-protection-oriented CI job that depends on live production and external registries; merge friction is mitigated by kill-switch and retries, but operational coupling to Socket production is new.
Overview
Adds the first unmocked hosted-mode capstone:
e2e_hosted_production.rsrunsscan --mode hostedagainst realpatches-api.socket.dev/patch.socket.devand real upstream registries. Each leg installs a pinned package, proves pristine bytes, runs the CLI redirect, then reinstalls from the rewritten lock so the package manager fetches and verifies Socket-hosted artifacts—coverage wiremock redirect tests cannot provide.Legs span npm-family managers (lock, shrinkwrap, pnpm, yarn classic/berry, bun), PyPI (
requirements.txt,uv.lock), Cargo sparse registry, gem redirect (install tolerated for a known server index bug), plus negative/canary probes for Go, Deno, and maven/nuget/composer with no free production patches. Tests are#[ignore]-gated withSOCKET_PATCH_HOSTED_E2E_STRICTfor CI.docs/testing/hosted-production-e2e.mddocuments patch pins, gaps, and issues the suite found (gem compact-index deps, pnpm 11 tarball policy,uv.locksdist rewrite).CI adds a dedicated
hosted-e2ejob (intended required check): installs toolchains, runs the suite with retries for transient 503s, and supports bypass viaHOSTED_E2E_DISABLEDor workflow_dispatchhosted_e2eforce/skip without skipping the job itself.Reviewed by Cursor Bugbot for commit 13e5442. Configure here.