diff --git a/crates/socket-patch-cli/tests/e2e_hosted_production.rs b/crates/socket-patch-cli/tests/e2e_hosted_production.rs index 29002aa7..312cdcbd 100644 --- a/crates/socket-patch-cli/tests/e2e_hosted_production.rs +++ b/crates/socket-patch-cli/tests/e2e_hosted_production.rs @@ -35,7 +35,6 @@ //! |-----------|------|------------|----------| //! | npm | `pkg:npm/minimist@1.2.2` | `80630680-4da6-45f9-bba8-b888e0ffd58c` | GHSA-xvch-5gv4-984h (CVE-2021-44906) | //! | PyPI | `pkg:pypi/urllib3@1.26.18` | *any of three* (see [`PYPI_UUIDS`]) | GHSA-gm62-xv2j-4w53 &co | -//! | Cargo | `pkg:cargo/traitobject@0.1.1` | `cf2e6f58-d9fa-4096-9151-c34afa717f89` | GHSA-pp8r-vv2j-9j5v | //! | gem | `pkg:gem/activestorage@6.0.3` | *any of* [`GEM_UUIDS`] (five today) | GHSA-m42x-37p3-fv5w (CVE-2020-8162), GHSA-w749-p3v6-hccq (CVE-2022-21831), GHSA-9xrj-h377-fr87 (CVE-2026-33195), GHSA-r4mg-4433-c7g3 (CVE-2025-24293), GHSA-xr9x-r78c-5hrm (CVE-2026-66066) | //! //! `docs/testing/hosted-production-e2e.md` explains how these were chosen and @@ -43,11 +42,14 @@ //! //! # Ecosystems with no coverage, and why //! -//! * **maven / nuget / composer** — hosted mode is implemented and documented -//! for all three, but production currently publishes **zero** free-tier -//! patches for them, so there is nothing real to redirect to. Rather than -//! silently skipping, [`canary_unpublished_ecosystems`] probes production -//! every run and tells us the moment that changes. +//! * **cargo / maven / nuget / composer** — hosted mode is implemented and +//! documented for all four, but production currently publishes **zero** +//! free-tier patches for them, so there is nothing real to redirect to. +//! Rather than silently skipping, [`canary_unpublished_ecosystems`] probes +//! production every run and tells us the moment that changes. cargo carried +//! a full sparse-registry install proof until 2026-09-01: production's free +//! cargo tier emptied on 2026-08-28, so the leg was demoted to the canary +//! (`docs/testing/hosted-production-e2e.md` says how to re-promote it). //! * **golang** — hosted mode is supported for free-tier references carrying //! a `goproxy` override (`docs/design/golang-hosted.md`), but production //! publishes no golang hosted modules yet. Covered as a shape guard that @@ -58,11 +60,11 @@ //! //! Toolchains (each leg soft-skips if its own toolchain is absent, unless //! `SOCKET_PATCH_HOSTED_E2E_STRICT=1`): `npm`, `pnpm`, `yarn` (classic), -//! `corepack` (berry), `bun`, `uv`, `cargo`, `ruby` + `bundle`, `go`. +//! `corepack` (berry), `bun`, `uv`, `ruby` + `bundle`, `go`. //! //! Network egress to: `patches-api.socket.dev`, `patch.socket.dev`, //! `registry.npmjs.org`, `pypi.org`, `files.pythonhosted.org`, -//! `static.crates.io`, `index.crates.io`, `rubygems.org`. +//! `rubygems.org`. //! //! No API token is used or needed — the suite deliberately runs against the //! **free public proxy**, which is the surface every unauthenticated user @@ -118,16 +120,6 @@ const PYPI_UUIDS: &[&str] = &[ "e828efa5-5c6d-43f3-9909-03f5ac232b98", ]; -const CARGO_PURL: &str = "pkg:cargo/traitobject@0.1.1"; -const CARGO_NAME: &str = "traitobject"; -const CARGO_VERSION: &str = "0.1.1"; -const CARGO_UUID: &str = "cf2e6f58-d9fa-4096-9151-c34afa717f89"; -/// The traitobject patch annotates `src/lib.rs` with its advisory ID (the -/// crate is unmaintained; the patch documents that and fixes deprecations). -/// Cargo crates are not rewritten with the `// Socket Community Patch` header -/// that npm/PyPI artifacts carry, so this is the marker to look for. -const CARGO_MARKER: &str = "GHSA-pp8r-vv2j-9j5v"; - /// The gem pin is deliberately UNQUALIFIED. Production publishes the purl as /// `pkg:gem/activestorage@6.0.3?platform=ruby`, but nothing client-side /// strips qualifiers — the SERVER normalizes both spellings to the same @@ -182,6 +174,19 @@ const PATCH_MARKER: &str = "Socket Community Patch"; /// patches to exercise it with. [`canary_unpublished_ecosystems`] watches /// these so coverage can be extended the moment one lights up. const UNPUBLISHED_ECOSYSTEMS: &[(&str, &[&str])] = &[ + // cargo joined this list on 2026-09-01: production deleted its last free + // cargo patches on 2026-08-28, retiring the pinned sparse-registry + // install proof this suite used to carry. Re-promotion procedure: + // docs/testing/hosted-production-e2e.md. + ( + "cargo", + &[ + "pkg:cargo/openssl", + "pkg:cargo/tokio", + "pkg:cargo/hyper", + "pkg:cargo/smallvec", + ], + ), ( "maven", &[ @@ -729,7 +734,6 @@ async fn preflight_required_patches_are_published() { let required: Vec<(&str, Vec<&str>)> = vec![ (NPM_PURL, vec![NPM_UUID]), (PYPI_PURL, PYPI_UUIDS.to_vec()), - (CARGO_PURL, vec![CARGO_UUID]), (GEM_PURL, GEM_UUIDS.to_vec()), ]; @@ -783,7 +787,7 @@ async fn canary_patches_name_advisories_so_merge_state_is_inferable() { let mut failures: Vec = Vec::new(); let mut coverage_seen: Vec<(String, String, usize)> = Vec::new(); - let canary_purls = vec![NPM_PURL, PYPI_PURL, CARGO_PURL, GEM_PURL]; + let canary_purls = vec![NPM_PURL, PYPI_PURL, GEM_PURL]; for purl in canary_purls { match published_patch_advisory_counts(purl).await { Err(e) => failures.push(format!("{purl}: production probe failed: {e}")), @@ -1531,111 +1535,6 @@ fn pypi_uv_lock_hosted_install_proof() { ); } -// =========================================================================== -// Cargo — per-patch sparse registry -// =========================================================================== - -#[test] -#[ignore = "live production API + real crates.io. Run with --ignored."] -fn cargo_hosted_install_proof() { - const LEG: &str = "cargo_hosted_install_proof"; - if !has_command("cargo") { - soft_skip!(LEG, "`cargo` not on PATH"); - } - let tmp = tempfile::tempdir().expect("tempdir"); - let proj = tmp.path().join("proj"); - std::fs::create_dir_all(proj.join("src")).expect("mkdir src"); - let home = tmp.path().join("cargo-home").display().to_string(); - let env = [("CARGO_HOME", home.as_str())]; - - std::fs::write( - proj.join("Cargo.toml"), - format!( - "[package]\nname = \"hosted-e2e\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n\ - [dependencies]\n{CARGO_NAME} = \"={CARGO_VERSION}\"\n" - ), - ) - .expect("write Cargo.toml"); - std::fs::write(proj.join("src").join("main.rs"), "fn main() {}\n").expect("write main.rs"); - - let fetch = tool(&proj, "cargo", &["fetch"], &env); - if !ok(&fetch) { - soft_skip!(LEG, "upstream `cargo fetch` failed:\n{}", dump(&fetch)); - } - let pristine_lock = read(&proj.join("Cargo.lock")); - assert!( - pristine_lock.contains("registry+https://github.com/rust-lang/crates.io-index"), - "{LEG}: pristine Cargo.lock does not resolve {CARGO_NAME} from \ - crates.io — fixture setup is wrong:\n{pristine_lock}" - ); - - let env_json = scan_hosted(&proj, &[]); - assert_redirected(&env_json, "Cargo.lock"); - - let lock = read(&proj.join("Cargo.lock")); - assert_hosted_pin(&lock, &[CARGO_UUID], LEG); - let config = read(&proj.join(".cargo").join("config.toml")); - assert!( - config.contains(&format!( - "sparse+https://{PATCH_HOST}/patch-registry/cargo/" - )), - "{LEG}: .cargo/config.toml declares no Socket sparse registry:\n{config}" - ); - let manifest = read(&proj.join("Cargo.toml")); - assert!( - manifest.contains(&format!("socket-patch-{CARGO_UUID}")), - "{LEG}: Cargo.toml does not route {CARGO_NAME} at the per-patch \ - registry:\n{manifest}" - ); - - // Proof: fetch again with a cold CARGO_HOME so cargo must reach the Socket - // sparse index, download the crate, and verify the checksum in the lock. - let cold = tmp.path().join("cargo-home-cold").display().to_string(); - let cold_env = [("CARGO_HOME", cold.as_str())]; - let refetch = tool(&proj, "cargo", &["fetch"], &cold_env); - assert!( - ok(&refetch), - "{LEG}: `cargo fetch` from the Socket sparse registry failed — cargo \ - could not reach the index, download the crate, or verify its \ - checksum:\n{}", - dump(&refetch) - ); - - // The extracted source must be the patched crate, not the crates.io one. - let src_root = Path::new(&cold).join("registry").join("src"); - let mut found = None; - if let Ok(hosts) = std::fs::read_dir(&src_root) { - for host in hosts.flatten() { - let candidate = host - .path() - .join(format!("{CARGO_NAME}-{CARGO_VERSION}")) - .join("src") - .join("lib.rs"); - if candidate.exists() { - found = Some(candidate); - break; - } - } - } - let lib_rs = found.unwrap_or_else(|| { - panic!("{LEG}: no extracted {CARGO_NAME}-{CARGO_VERSION}/src/lib.rs under {src_root:?}") - }); - assert!( - lib_rs - .parent() - .and_then(|p| p.parent()) - .and_then(|p| p.parent()) - .and_then(|p| p.file_name()) - .map(|n| n.to_string_lossy().contains(PATCH_HOST)) - .unwrap_or(false), - "{LEG}: {CARGO_NAME} was extracted from a non-Socket registry dir \ - ({}) — cargo served it from the crates.io cache instead of the \ - redirect", - lib_rs.display() - ); - assert_patched(&lib_rs, CARGO_MARKER, LEG); -} - // =========================================================================== // RubyGems — full hosted install proof // =========================================================================== @@ -1960,10 +1859,12 @@ fn deno_hosted_is_unsupported() { // Canary — ecosystems whose hosted support has nothing to test against // =========================================================================== -/// maven, nuget and composer all implement hosted mode, but production +/// cargo, maven, nuget and composer all implement hosted mode, but production /// publishes no free-tier patches for them, so there is no honest end-to-end -/// leg to write. This probes production every run and reports the moment that -/// changes, so coverage can be extended deliberately rather than by accident. +/// leg to write. (cargo used to have one — the pinned sparse-registry install +/// proof retired 2026-09-01 when production's free cargo tier emptied.) This +/// probes production every run and reports the moment that changes, so +/// coverage can be extended deliberately rather than by accident. /// /// It deliberately does NOT fail when patches appear: production publishing a /// new patch is not a socket-patch regression, and a required check must not @@ -1996,9 +1897,9 @@ async fn canary_unpublished_ecosystems() { if newly_published.is_empty() { println!( - "canary_unpublished_ecosystems: maven / nuget / composer still have \ - no free-tier published patches — their hosted-mode legs remain \ - untestable end-to-end against production." + "canary_unpublished_ecosystems: cargo / maven / nuget / composer \ + still have no free-tier published patches — their hosted-mode legs \ + remain untestable end-to-end against production." ); return; } diff --git a/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs b/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs index 06e0a779..d766c647 100644 --- a/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs +++ b/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs @@ -47,8 +47,8 @@ use sha2::{Digest, Sha256}; mod common; use common::{ - assert_run_ok, cargo_run, has_command, parse_json_envelope, run, run_with_env, sha256_hex, - write_blob, write_minimal_manifest, PatchEntry, + assert_run_ok, cargo_run, has_command, parse_json_envelope, run, sha256_hex, write_blob, + write_minimal_manifest, PatchEntry, }; const ORIGINAL_LIB_RS: &str = "pub fn hello() -> &'static str { \"world\" }\n"; @@ -996,212 +996,3 @@ fn apply_normalizes_package_prefix_in_cargo_checksum() { "sidecar record must still report .cargo-checksum.json:rewritten; got {cargo}" ); } - -/// Headline real-world round trip: fetch the actual `traitobject@0.0.1` -/// crate from crates.io, apply the real Socket patch -/// `b15f2b7f-d5cb-43c9-b793-80f71682188f` from the public proxy, then -/// run `cargo check` against a consumer that depends on it. -/// -/// This is the cargo "layer 2 + layer 3" combined test (per the -/// PR #80 plan): a real published crate plus the real Socket patch, -/// no synthetic fixtures. Proves the sidecar fixup composes with -/// cargo's actual on-disk verification of crates.io sources. -/// -/// Network deps: -/// - crates.io (cargo fetch traitobject@0.0.1) -/// - patches-api.socket.dev (socket-patch get, public proxy) -/// -/// The traitobject 0.0.1 patch adds a `compile_error!` to `src/lib.rs` -/// gated on `#[cfg(not(feature = "allow-unmaintained"))]`, and adds -/// that feature to the crate's Cargo.toml. The consumer MUST declare -/// the dep bare: cargo resolves features from the crates.io INDEX, -/// which knows nothing of the patch-added feature, so declaring -/// `features = ["allow-unmaintained"]` fails resolution ("does not -/// have that feature") before fetch even runs — against both the -/// unpatched AND the patched crate. (This test originally shipped -/// with the feature declared; the resulting resolution error was -/// swallowed by the fetch skip path and the test silently skipped -/// itself on every machine.) The end-to-end oracle is therefore the -/// patch's own compile_error: cargo accepting the rewritten checksums -/// and then failing compilation with the "unmaintained" message -/// proves the sidecar fixup landed AND that rustc consumed the -/// patched bytes. -#[test] -#[ignore] -fn traitobject_real_socket_patch_round_trip() { - if !has_command("cargo") { - eprintln!("SKIP: cargo not on PATH"); - return; - } - let root = tempfile::tempdir().unwrap(); - let consumer = root.path().join("consumer"); - let cargo_home = root.path().join(".cargo-home"); - std::fs::create_dir_all(consumer.join("src")).unwrap(); - - // Consumer crate that uses traitobject, declared bare (see the - // doc comment: the patch-added feature is index-invisible and can - // never be enabled on a registry dependency). - std::fs::write( - consumer.join("Cargo.toml"), - r#"[package] -name = "traitobject-consumer" -version = "0.0.1" -edition = "2021" - -[dependencies] -traitobject = "0.0.1" -"#, - ) - .unwrap(); - std::fs::write(consumer.join("src/main.rs"), "fn main() {}\n").unwrap(); - - // 1. Fetch traitobject@0.0.1 from crates.io (real network). - // Hermetic CARGO_HOME means we never touch the user's cache. - let cargo_home_str = cargo_home.to_str().unwrap(); - let fetch = Command::new("cargo") - .args(["fetch"]) - .current_dir(&consumer) - .env("CARGO_HOME", cargo_home_str) - .output() - .expect("cargo fetch"); - if !fetch.status.success() { - // Network unavailable, crates.io down, etc. — skip rather - // than fail. The ignore gate already keeps us out of the - // default test run; this is a defensive second skip path. - eprintln!( - "SKIP: cargo fetch traitobject failed (likely network):\nstdout:\n{}\nstderr:\n{}", - String::from_utf8_lossy(&fetch.stdout), - String::from_utf8_lossy(&fetch.stderr), - ); - return; - } - - // 2. Confirm the unpacked source landed under the registry path. - // Shape: `/registry/src/index.crates.io-*/traitobject-0.0.1/`. - let registry_src = cargo_home.join("registry/src"); - let mut traitobject_dir: Option = None; - for entry in std::fs::read_dir(®istry_src).unwrap() { - let entry = entry.unwrap(); - let candidate = entry.path().join("traitobject-0.0.1"); - if candidate.is_dir() { - traitobject_dir = Some(candidate); - break; - } - } - let traitobject_dir = traitobject_dir - .expect("traitobject-0.0.1 should be unpacked under cargo registry/src after cargo fetch"); - // Modern cargo no longer materializes `.cargo-checksum.json` under - // registry/src — it writes a bare `.cargo-ok` marker and verifies the - // .crate tarball at unpack time, leaving no per-file hashes for the - // sidecar to fix up (fixup correctly returns "nothing to do"). Older - // toolchains DO ship the file; snapshot conditionally so the rewrite - // assertions still fire wherever the file exists. - let checksum_path = traitobject_dir.join(".cargo-checksum.json"); - let pre_apply_checksum: Option = std::fs::read_to_string(&checksum_path) - .ok() - .map(|raw| serde_json::from_str(&raw).expect("pre-apply .cargo-checksum.json must parse")); - - // 3. Run `socket-patch get` against the public proxy. This - // downloads + applies the real patch in one shot. Route through - // the scrubbed runner so ambient SOCKET_* (DRY_RUN, MANIFEST_PATH, - // GLOBAL, ...) can't no-op or redirect the apply; the scrub also - // strips SOCKET_API_TOKEN, forcing the public proxy. - let (get_code, get_stdout, get_stderr) = run_with_env( - &consumer, - &[ - "get", - "b15f2b7f-d5cb-43c9-b793-80f71682188f", - "--cwd", - consumer.to_str().unwrap(), - ], - &[("CARGO_HOME", cargo_home_str)], - ); - if get_code != 0 { - eprintln!( - "SKIP: socket-patch get failed (likely network):\nstdout:\n{get_stdout}\nstderr:\n{get_stderr}" - ); - return; - } - - // 4. Manifest should now record the patch. - let manifest_path = consumer.join(".socket/manifest.json"); - let manifest: serde_json::Value = serde_json::from_str( - &std::fs::read_to_string(&manifest_path).expect("manifest.json must exist after get"), - ) - .unwrap(); - let patch = &manifest["patches"]["pkg:cargo/traitobject@0.0.1"]; - assert!( - patch.is_object(), - "manifest should contain the traitobject patch: {manifest}" - ); - - // 5. Where a `.cargo-checksum.json` existed, the sidecar fixup must - // have rewritten it: the patch covers src/lib.rs (and Cargo.toml, - // Cargo.lock, README.md), so those entries should have NEW SHA256 - // values while every unpatched-file entry stays put. Where cargo - // never wrote one (modern toolchains), the sidecar must not - // invent one. - match &pre_apply_checksum { - Some(pre) => { - let post_apply_checksum: serde_json::Value = serde_json::from_str( - &std::fs::read_to_string(&checksum_path) - .expect("sidecar must not delete .cargo-checksum.json"), - ) - .unwrap(); - let pre_files = pre["files"].as_object().unwrap(); - let post_files = post_apply_checksum["files"].as_object().unwrap(); - let patched_paths = ["Cargo.toml", "Cargo.lock", "README.md", "src/lib.rs"]; - for f in patched_paths { - if let (Some(pre), Some(post)) = (pre_files.get(f), post_files.get(f)) { - assert_ne!( - pre, post, - ".cargo-checksum.json entry for {f} should change after apply" - ); - assert_eq!( - post.as_str().unwrap().len(), - 64, - "post-apply hash for {f} should be 64-hex SHA256" - ); - } - } - // `package` field is preserved (the .crate tarball hash didn't - // become honestly recomputable without the original .crate). - assert_eq!( - pre["package"], post_apply_checksum["package"], - ".cargo-checksum.json `package` field must survive the rewrite unchanged" - ); - } - None => { - assert!( - !checksum_path.exists(), - "apply must not create a .cargo-checksum.json cargo never wrote" - ); - } - } - - // 6. The whole point: cargo gets PAST checksum verification and - // compiles the PATCHED source. A green build is impossible here - // by the patch's design (the compile_error can only be silenced - // by a feature no registry consumer can enable), so the honest - // oracle is two-sided: - // * no "checksum ... has changed" rejection — the sidecar - // rewrite was accepted by cargo's registry-source - // verification, and - // * the patch's own compile_error text in stderr — the bytes - // rustc consumed are the patched bytes, not the originals - // (unpatched traitobject compiles clean, so a silent - // no-op apply would turn this check green and fail below). - let check = cargo_check(&consumer, &cargo_home); - let check_stderr = String::from_utf8_lossy(&check.stderr); - assert!( - !(check_stderr.contains("checksum") && check_stderr.contains("changed")), - "cargo must accept the sidecar-rewritten checksums, not reject them:\n{check_stderr}" - ); - assert!( - !check.status.success() && check_stderr.contains("unmaintained"), - "cargo check must fail with the patch's `unmaintained` compile_error \ - (proving the patched source compiled); a success here means the \ - patch never reached the compiled bytes.\nstdout:\n{}\nstderr:\n{check_stderr}", - String::from_utf8_lossy(&check.stdout), - ); -} diff --git a/crates/socket-patch-cli/tests/e2e_vendored_production.rs b/crates/socket-patch-cli/tests/e2e_vendored_production.rs index 42472402..74f39e14 100644 --- a/crates/socket-patch-cli/tests/e2e_vendored_production.rs +++ b/crates/socket-patch-cli/tests/e2e_vendored_production.rs @@ -51,7 +51,6 @@ //! |-----------|------|------------|-----------------------------| //! | npm | `pkg:npm/minimist@1.2.2` | `80630680-4da6-45f9-bba8-b888e0ffd58c` | `Socket Community Patch` header | //! | PyPI | `pkg:pypi/urllib3@1.26.18` | *any of three* (see [`PYPI_UUIDS`]) | `Socket Community Patch` header | -//! | Cargo | `pkg:cargo/traitobject@0.1.1` | `cf2e6f58-d9fa-4096-9151-c34afa717f89` | advisory id `GHSA-pp8r-vv2j-9j5v` | //! | gem | `pkg:gem/activestorage@6.0.3` | *any of* [`GEM_PATCHES`] | `Socket Community Patch` header | //! //! # Ecosystem coverage notes (gaps, and one resolved gap) @@ -72,10 +71,13 @@ //! publishes no free golang patches, so there is nothing to vendor. //! [`golang_vendored_finds_no_free_patches`] asserts exactly that (zero //! applied) and would light up the moment a golang patch is published. -//! * **maven / nuget / composer** — vendored mode is implemented, but +//! * **cargo / maven / nuget / composer** — vendored mode is implemented, but //! production publishes **zero** free-tier patches for them. //! [`canary_unpublished_vendored_ecosystems`] probes production every run and -//! reports the moment that changes. +//! reports the moment that changes. cargo carried a full +//! `[patch.crates-io]` delivery proof until 2026-09-01: production's free +//! cargo tier emptied on 2026-08-28, so the leg was demoted to the canary +//! (`docs/testing/vendored-production-e2e.md` says how to re-promote it). //! * **deno** — vendored mode is not supported. //! [`deno_vendored_is_unsupported`] covers it as a negative assertion. //! @@ -83,12 +85,11 @@ //! //! Toolchains (each leg soft-skips if its own toolchain is absent, unless //! `SOCKET_PATCH_VENDORED_E2E_STRICT=1`): `npm`, `pnpm`, `corepack` (yarn -//! classic + berry), `bun`, `uv`, `python3` (pip), `cargo`, `ruby` + `bundle`, -//! `go`. +//! classic + berry), `bun`, `uv`, `python3` (pip), `ruby` + `bundle`, `go`. //! //! Network egress to: `patches-api.socket.dev`, `patch.socket.dev`, //! `registry.npmjs.org`, `pypi.org`, `files.pythonhosted.org`, -//! `static.crates.io`, `index.crates.io`, `rubygems.org`. +//! `rubygems.org`. //! //! No API token is used or needed — the suite deliberately runs against the //! **free public proxy** with `SOCKET_NO_CONFIG=true` so a developer's @@ -139,18 +140,6 @@ const PYPI_UUIDS: &[&str] = &[ "e828efa5-5c6d-43f3-9909-03f5ac232b98", ]; -const CARGO_PURL: &str = "pkg:cargo/traitobject@0.1.1"; -const CARGO_NAME: &str = "traitobject"; -const CARGO_VERSION: &str = "0.1.1"; -const CARGO_UUID: &str = "cf2e6f58-d9fa-4096-9151-c34afa717f89"; -/// The traitobject patch annotates `src/lib.rs` with its advisory ID. Cargo -/// crates are not rewritten with the `Socket Community Patch` header the -/// npm/PyPI artifacts carry, so this is the marker to look for. (The same -/// patch also injects a `compile_error!` unless the `allow-unmaintained` -/// feature is set — which is why the cargo delivery proof uses `cargo fetch`, -/// not `cargo build`; see [`cargo_vendored_install_proof`].) -const CARGO_MARKER: &str = "GHSA-pp8r-vv2j-9j5v"; - /// The gem pin is deliberately UNQUALIFIED. Production publishes the purl as /// `pkg:gem/activestorage@6.0.3?platform=ruby`, but nothing client-side /// strips qualifiers — the SERVER normalizes both spellings to the same @@ -207,6 +196,14 @@ const YARN_BERRY: &str = "yarn@4.6.0"; /// patches to exercise it with. [`canary_unpublished_vendored_ecosystems`] /// watches these so coverage can be extended the moment one lights up. const UNPUBLISHED_ECOSYSTEMS: &[(&str, &[&str])] = &[ + // cargo joined this list on 2026-09-01: production deleted its last free + // cargo patches on 2026-08-28, retiring the pinned `[patch.crates-io]` + // delivery proof this suite used to carry. Re-promotion procedure: + // docs/testing/vendored-production-e2e.md. + ( + "cargo", + &["pkg:cargo/openssl", "pkg:cargo/tokio", "pkg:cargo/smallvec"], + ), ( "maven", &[ @@ -738,7 +735,6 @@ async fn preflight_required_patches_are_published() { let required: Vec<(&str, Vec<&str>)> = vec![ (NPM_PURL, vec![NPM_UUID]), (PYPI_PURL, PYPI_UUIDS.to_vec()), - (CARGO_PURL, vec![CARGO_UUID]), (GEM_PURL, GEM_PATCHES.iter().map(|(u, _)| *u).collect()), ]; @@ -1599,180 +1595,6 @@ fn pypi_uv_lock_vendored_install_proof() { ); } -// =========================================================================== -// Cargo — `[patch.crates-io]` path dep -// =========================================================================== - -/// Cargo's vendored artifact is a **directory** (a `[patch.crates-io]` path -/// dep), so the delivery proof is a `cargo fetch --offline` that resolves the -/// whole dependency graph from committable files with an empty CARGO_HOME — -/// proving zero registry access — plus a byte check that the vendored -/// directory carries the patch. -/// -/// It deliberately does NOT `cargo build`: the production traitobject patch -/// injects a `compile_error!` unless the `allow-unmaintained` Cargo feature is -/// enabled (the patch's whole point is to make the unmaintained crate refuse to -/// compile silently). That is patch *content*, not a vendoring defect, and this -/// suite proves byte delivery, not CVE efficacy. -#[test] -#[ignore = "live production API + real crates.io. Run with --ignored."] -fn cargo_vendored_install_proof() { - const LEG: &str = "cargo_vendored_install_proof"; - if !has_command("cargo") { - soft_skip!(LEG, "`cargo` not on PATH"); - } - let tmp = tempfile::tempdir().expect("tempdir"); - let proj = tmp.path().join("proj"); - std::fs::create_dir_all(proj.join("src")).expect("mkdir src"); - let home = tmp.path().join("cargo-home").display().to_string(); - let env = [("CARGO_HOME", home.as_str())]; - - std::fs::write( - proj.join("Cargo.toml"), - format!( - "[package]\nname = \"vendored-e2e\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n\ - [dependencies]\n{CARGO_NAME} = \"={CARGO_VERSION}\"\n" - ), - ) - .expect("write Cargo.toml"); - std::fs::write(proj.join("src").join("main.rs"), "fn main() {}\n").expect("write main.rs"); - - let fetch = tool(&proj, "cargo", &["fetch"], &env); - if !ok(&fetch) { - soft_skip!(LEG, "upstream `cargo fetch` failed:\n{}", dump(&fetch)); - } - let pristine_lock = std::fs::read(proj.join("Cargo.lock")).unwrap(); - - // The pristine registry-extracted source must NOT carry the marker. - let registry_lib = find_registry_lib(Path::new(&home)); - if let Some(ref lib) = registry_lib { - assert_pristine(lib, CARGO_MARKER, LEG); - } - - let env_json = scan_vendored(&proj, &[]); - assert_vendor_applied(&env_json, CARGO_PURL, LEG); - assert_download_uuid(&env_json, &[CARGO_UUID], LEG); - - // Vendored directory carries the patch; the registry source stays pristine. - let vendored_lib = proj - .join(format!( - ".socket/vendor/cargo/{CARGO_UUID}/{CARGO_NAME}-{CARGO_VERSION}" - )) - .join("src/lib.rs"); - assert_patched(&vendored_lib, CARGO_MARKER, LEG); - if let Some(ref lib) = registry_lib { - assert!( - !read(lib).contains(CARGO_MARKER), - "{LEG}: vendoring mutated the pristine registry source at {} — vendor must copy, \ - never mutate", - lib.display() - ); - } - let config = read(&proj.join(".cargo/config.toml")); - assert!( - config.contains("[patch.crates-io]") - && config.contains(&format!(".socket/vendor/cargo/{CARGO_UUID}/")), - "{LEG}: .cargo/config.toml declares no [patch.crates-io] pointing at the vendored \ - crate:\n{config}" - ); - // Lock detached from the registry (keeps name+version, loses source+checksum). - let lock = read(&proj.join("Cargo.lock")); - let block = cargo_package_block(&lock, CARGO_NAME).expect("traitobject lock entry survives"); - assert!( - !block.contains("source = ") && !block.contains("checksum = "), - "{LEG}: lock entry must be detached from the registry:\n{block}" - ); - let lock_wired = std::fs::read(proj.join("Cargo.lock")).unwrap(); - - // DELIVERY PROOF: committable files only, EMPTY CARGO_HOME, `cargo fetch - // --offline --locked` — resolves the whole graph from the vendored path - // with zero registry downloads. - let fresh = tmp.path().join("fresh"); - std::fs::create_dir_all(&fresh).unwrap(); - std::fs::copy(proj.join("Cargo.toml"), fresh.join("Cargo.toml")).unwrap(); - std::fs::copy(proj.join("Cargo.lock"), fresh.join("Cargo.lock")).unwrap(); - copy_dir_recursive(&proj.join(".cargo"), &fresh.join(".cargo")); - copy_dir_recursive(&proj.join("src"), &fresh.join("src")); - copy_dir_recursive(&proj.join(".socket"), &fresh.join(".socket")); - let fresh_home = tmp.path().join("fresh-cargo-home"); - std::fs::create_dir_all(&fresh_home).unwrap(); - let fresh_home_s = fresh_home.display().to_string(); - let refetch = tool( - &fresh, - "cargo", - &["fetch", "--offline", "--locked"], - &[("CARGO_HOME", fresh_home_s.as_str())], - ); - assert!( - ok(&refetch), - "{LEG}: `cargo fetch --offline --locked` from the vendored path (empty CARGO_HOME) \ - failed — cargo could not resolve the graph from committable files alone:\n{}", - dump(&refetch) - ); - assert!( - !fresh_home.join("registry").exists(), - "{LEG}: the empty CARGO_HOME gained a registry/ — cargo hit the network instead of \ - resolving {CARGO_NAME} from the vendored path dep" - ); - // The delivered bytes ARE the vendored directory's bytes (path dep), and - // they carry the patch marker. - assert_patched( - &fresh.join(format!( - ".socket/vendor/cargo/{CARGO_UUID}/{CARGO_NAME}-{CARGO_VERSION}/src/lib.rs" - )), - CARGO_MARKER, - LEG, - ); - - // Idempotency + revert. - let env2 = scan_vendored(&proj, &[]); - assert_eq!( - env2["vendor"]["summary"]["applied"].as_u64().unwrap_or(99), - 0, - "{LEG}: re-run must vendor nothing new:\n{env2:#}" - ); - assert_eq!( - std::fs::read(proj.join("Cargo.lock")).unwrap(), - lock_wired, - "{LEG}: re-run must leave Cargo.lock byte-identical" - ); - assert_eq!(vendor_revert(&proj, LEG), 1, "{LEG}: one entry reverted"); - assert_eq!( - std::fs::read(proj.join("Cargo.lock")).unwrap(), - pristine_lock, - "{LEG}: revert must restore Cargo.lock byte-identical" - ); - assert!( - !proj.join(".socket/vendor").exists(), - "{LEG}: .socket/vendor must be gone after revert" - ); -} - -/// Find `/registry/src//traitobject-0.1.1/src/lib.rs`. -fn find_registry_lib(cargo_home: &Path) -> Option { - let src = cargo_home.join("registry").join("src"); - for host in std::fs::read_dir(&src).ok()?.flatten() { - let candidate = host - .path() - .join(format!("{CARGO_NAME}-{CARGO_VERSION}")) - .join("src") - .join("lib.rs"); - if candidate.exists() { - return Some(candidate); - } - } - None -} - -/// The full `[[package]]` block (text) for `name` in Cargo.lock. -fn cargo_package_block(lock_text: &str, name: &str) -> Option { - let needle = format!("name = \"{name}\""); - lock_text - .split("[[package]]") - .find(|block| block.lines().any(|l| l.trim() == needle)) - .map(str::to_string) -} - // =========================================================================== // RubyGems — bundler `path:` source // =========================================================================== @@ -2147,10 +1969,12 @@ fn deno_vendored_is_unsupported() { ); } -/// maven, nuget and composer all implement vendored mode, but production -/// publishes no free-tier patches for them. This probes production every run -/// and reports the moment that changes, so coverage can be extended -/// deliberately rather than by accident. It does not fail when patches appear; +/// cargo, maven, nuget and composer all implement vendored mode, but +/// production publishes no free-tier patches for them. (cargo used to have a +/// full delivery proof — retired 2026-09-01 when production's free cargo tier +/// emptied.) This probes production every run and reports the moment that +/// changes, so coverage can be extended deliberately rather than by accident. +/// It does not fail when patches appear; /// `SOCKET_PATCH_VENDORED_E2E_CANARY_STRICT=1` makes it fail, for a scheduled /// nag run. #[tokio::test(flavor = "multi_thread")] @@ -2180,8 +2004,8 @@ async fn canary_unpublished_vendored_ecosystems() { if newly_published.is_empty() { println!( - "canary_unpublished_vendored_ecosystems: maven / nuget / composer still have no \ - free-tier published patches — their vendored-mode legs remain untestable \ + "canary_unpublished_vendored_ecosystems: cargo / maven / nuget / composer still \ + have no free-tier published patches — their vendored-mode legs remain untestable \ end-to-end against production." ); return; diff --git a/docs/testing/hosted-production-e2e.md b/docs/testing/hosted-production-e2e.md index 447d428a..5d48a530 100644 --- a/docs/testing/hosted-production-e2e.md +++ b/docs/testing/hosted-production-e2e.md @@ -40,7 +40,6 @@ from the child environment. |-----------|------|------------|----------|---------| | npm | `pkg:npm/minimist@1.2.2` | `80630680-4da6-45f9-bba8-b888e0ffd58c` | GHSA-xvch-5gv4-984h / CVE-2021-44906 | all five npm-family legs | | PyPI | `pkg:pypi/urllib3@1.26.18` | `de58c8b8-796c-4b6d-8a48-539b5563db76`, `26242e35-f867-4da8-8789-f0d2ea49e0f1`, `e828efa5-5c6d-43f3-9909-03f5ac232b98` | GHSA-38jv-5279-wg99, GHSA-2xpw-w6gg-jr37, GHSA-gm62-xv2j-4w53 | requirements.txt, uv.lock | -| Cargo | `pkg:cargo/traitobject@0.1.1` | `cf2e6f58-d9fa-4096-9151-c34afa717f89` | GHSA-pp8r-vv2j-9j5v | cargo sparse-registry leg | | RubyGems | `pkg:gem/activestorage@6.0.3` | any of `15e960b5-f432-4b6c-b8aa-534a2b419323` (GHSA-m42x-37p3-fv5w / CVE-2020-8162), `6c4141c5-1535-4fd2-9db1-b5f8e4834bdb` (GHSA-w749-p3v6-hccq / CVE-2022-21831, published 2026-08-19), `eeb6bf9f-96c0-4963-a0f1-2e88f91f8b1a` (GHSA-9xrj-h377-fr87 / CVE-2026-33195, published 2026-08-20), `c1a1cd3c-b670-4e44-b4fa-1a63ecd42db6` (GHSA-r4mg-4433-c7g3 / CVE-2025-24293, published 2026-08-20) | see UUID column | bundler leg | urllib3 1.26.18 carries **three** distinct free patches, one per advisory. Which @@ -48,7 +47,7 @@ one the resolver returns is a server-side ordering detail, so the suite accepts any of the three rather than pinning one — pinning would go red on an unrelated server-side reorder. -`preflight_required_patches_are_published` checks all four every run and fails +`preflight_required_patches_are_published` checks all three every run and fails first with the offending PURL named, so a withdrawn patch produces one clear failure instead of N confusing ones that look like CLI regressions. @@ -63,9 +62,9 @@ failure instead of N confusing ones that look like CLI regressions. package manager in that ecosystem's leg. 2. Update the catalog constants at the top of `e2e_hosted_production.rs` (`*_PURL`, `*_NAME`, `*_VERSION`, `*_UUID`) **and** the table above. -3. If the new patch does not inject the `// Socket Community Patch` header - (Cargo crates do not), pick a marker unique to the patch and set the - ecosystem's `*_MARKER` constant. +3. If the new patch does not inject the `// Socket Community Patch` header, + pick a marker unique to the patch and set the ecosystem's `*_MARKER` + constant. ## Ecosystem coverage, and the honest gaps @@ -73,23 +72,35 @@ failure instead of N confusing ones that look like CLI regressions. |-----------|-------------|----------------------------|----------------| | npm | ✅ | ✅ many | ✅ npm, npm-shrinkwrap, pnpm, yarn classic, yarn berry, bun | | PyPI | ✅ (requirements.txt + uv.lock only) | ✅ many | ✅ requirements.txt, uv.lock | -| Cargo | ✅ | ✅ 1 crate | ✅ sparse registry | | RubyGems | ✅ | ✅ (this suite pins one purl/UUID: `activestorage@6.0.3`; the 2026-08-18 republish covers more versions) | ✅ full bundler install proof | +| Cargo | ✅ | ❌ **none** (tier emptied 2026-08-28) | canary only | | Maven | ✅ | ❌ **none** | canary only | | NuGet | ✅ | ❌ **none** | canary only | | Composer | ✅ | ❌ **none** | canary only | | Go | ✅ free tier [by design](../design/golang-hosted.md) (paid: ❌ [analysis](../design/golang-hosted-no-go.md)) | ❌ none published yet | shape guard (redirects only via `goproxy` override) | | Deno | ❌ not supported | — | negative assertion | -Maven, NuGet and Composer all *implement* hosted mode, but production publishes -**zero** free-tier patches for them, so there is nothing real to redirect to. -Rather than skipping silently, `canary_unpublished_ecosystems` probes production -every run and reports the moment that changes, so coverage can be extended -deliberately. It does not fail when patches appear — production publishing a -patch is not a socket-patch regression — but +Cargo, Maven, NuGet and Composer all *implement* hosted mode, but production +publishes **zero** free-tier patches for them, so there is nothing real to +redirect to. Rather than skipping silently, `canary_unpublished_ecosystems` +probes production every run and reports the moment that changes, so coverage +can be extended deliberately. It does not fail when patches appear — production +publishing a patch is not a socket-patch regression — but `SOCKET_PATCH_HOSTED_E2E_CANARY_STRICT=1` makes it fail, for use in a scheduled nag run. +**Cargo was demoted to the canary on 2026-09-01.** Until then the suite carried +a full sparse-registry install proof pinned to one crate, but production's free +cargo tier emptied on 2026-08-28 (both pinned patches were deleted server-side, +leaving zero live free patches for any cargo crate), so there is nothing honest +left to pin. This deliberately drops the cargo install-proof coverage — the +canary only watches for the tier lighting up again. To re-promote cargo: pick a +live free patch and follow +[the withdrawn-patch procedure](#if-a-required-patch-is-withdrawn); the git +history of this demotion shows every piece to restore (catalog constants, +preflight registration, the install-proof leg, and these tables) in both +production suites. + PyPI's poetry / pdm / pipenv locks are **not** rewritten by hosted mode (see the [matrix](../ecosystems.md#mode--ecosystem-matrix)); those flavors are vendored-mode only, so there is no hosted leg to write for them. @@ -194,18 +205,18 @@ runs only where it is explicitly asked for. | Variable | Effect | |----------|--------| | `SOCKET_PATCH_HOSTED_E2E_STRICT=1` | Turn every "toolchain missing" soft-skip into a hard failure. **CI sets this** — a required check must never report green on an unexercised leg. | -| `SOCKET_PATCH_HOSTED_E2E_CANARY_STRICT=1` | Fail when maven/nuget/composer gain their first free published patch. | +| `SOCKET_PATCH_HOSTED_E2E_CANARY_STRICT=1` | Fail when cargo/maven/nuget/composer gain their first free published patch. | ### Toolchains -`npm`, `corepack` (pnpm + yarn classic + yarn berry), `bun`, `uv`, `cargo`, +`npm`, `corepack` (pnpm + yarn classic + yarn berry), `bun`, `uv`, `ruby` + `bundle` (**≥ 2.6** — `bundle lock --add-checksums` emits the CHECKSUMS section the gem rewrite pins into), `go`. ### Network egress `patches-api.socket.dev`, `patch.socket.dev`, `registry.npmjs.org`, `pypi.org`, -`files.pythonhosted.org`, `static.crates.io`, `index.crates.io`, `rubygems.org`. +`files.pythonhosted.org`, `rubygems.org`. ## CI: the `hosted-e2e` job diff --git a/docs/testing/vendored-production-e2e.md b/docs/testing/vendored-production-e2e.md index cfb2f7e8..04c7f95a 100644 --- a/docs/testing/vendored-production-e2e.md +++ b/docs/testing/vendored-production-e2e.md @@ -43,13 +43,12 @@ matches how the `e2e_vendor_*_build.rs` capstones assert. Pinned to these free-tier patches; they must stay published on `patches-api.socket.dev`. `preflight_required_patches_are_published` checks all -four every run and fails first with the offending PURL named. +three every run and fails first with the offending PURL named. | Ecosystem | PURL | Patch UUID | Marker in the patched bytes | |-----------|------|------------|-----------------------------| | npm | `pkg:npm/minimist@1.2.2` | `80630680-4da6-45f9-bba8-b888e0ffd58c` | `Socket Community Patch` header | | PyPI | `pkg:pypi/urllib3@1.26.18` | one of three (server-ordered) | `Socket Community Patch` header | -| Cargo | `pkg:cargo/traitobject@0.1.1` | `cf2e6f58-d9fa-4096-9151-c34afa717f89` | advisory id `GHSA-pp8r-vv2j-9j5v` | | RubyGems | `pkg:gem/activestorage@6.0.3` | any of the `GEM_PATCHES` table (4 as of 2026-08-20 — see the hosted doc's UUID list; each patch marks a different file) | `Socket Community Patch` header | If a required patch is withdrawn, update the catalog constants at the top of @@ -67,19 +66,22 @@ hosted suite). | bun (text lockfile) | minimist@1.2.2 | `bun install --frozen-lockfile` | ✅ full | | pip (requirements.txt) | urllib3@1.26.18 | `pip install --no-index -r requirements.txt` | ✅ full | | uv (uv.lock) | urllib3@1.26.18 | `uv sync --frozen --offline` | ✅ full | -| cargo (`[patch.crates-io]`) | traitobject@0.1.1 | `cargo fetch --offline --locked` (see note) | ✅ full | | bundler | activestorage@6.0.3 | frozen `bundle install`, fresh empty `BUNDLE_PATH` | ✅ full | | go | — | — | zero-patch assertion (no free golang patches) | | deno | — | — | negative assertion (unsupported) | -| maven / nuget / composer | — | — | canary (no free production patches) | - -The cargo delivery proof uses `cargo fetch --offline`, not `cargo build`, -because the production traitobject patch injects a `compile_error!` unless the -`allow-unmaintained` Cargo feature is enabled — the patch's whole point is to -make the unmaintained crate refuse to compile. That is patch *content*, not a -vendoring defect; the leg proves the artifact resolves entirely from the -committable files with zero registry downloads, and byte-checks the vendored -directory. +| cargo / maven / nuget / composer | — | — | canary (no free production patches) | + +Cargo sat in the ✅-full rows until 2026-09-01: the leg vendored a pinned crate +as a `[patch.crates-io]` path dep and proved delivery with +`cargo fetch --offline --locked` from an empty `CARGO_HOME`. Production's free +cargo tier emptied on 2026-08-28 (the pinned patches were deleted server-side, +leaving zero live free patches for any cargo crate), so the leg was demoted to +`canary_unpublished_vendored_ecosystems` — deliberately dropping the cargo +install-proof coverage. To re-promote: pick a live free cargo patch and follow +the withdrawn-patch procedure in the +[hosted doc](./hosted-production-e2e.md#if-a-required-patch-is-withdrawn); the +git history of this demotion shows every piece to restore in both production +suites. ## Known issues this suite surfaced @@ -170,7 +172,7 @@ installs from contending on the shared cache sandbox. | Variable | Effect | |----------|--------| | `SOCKET_PATCH_VENDORED_E2E_STRICT=1` | Turn every "toolchain missing" soft-skip into a hard failure. | -| `SOCKET_PATCH_VENDORED_E2E_CANARY_STRICT=1` | Fail when maven/nuget/composer gain their first free published patch. | +| `SOCKET_PATCH_VENDORED_E2E_CANARY_STRICT=1` | Fail when cargo/maven/nuget/composer gain their first free published patch. | The suite forces `SOCKET_NO_CONFIG=true` and scrubs every ambient `SOCKET_*` var, planting hostile seeds so a dropped scrub reddens the suite instead of @@ -180,10 +182,9 @@ token is used. ### Toolchains `npm`, `pnpm`, `corepack` (yarn classic + berry), `bun`, `uv`, `python3` (pip), -`cargo`, `ruby` + `bundle`, `go`. +`ruby` + `bundle`, `go`. ### Network egress `patches-api.socket.dev`, `patch.socket.dev`, `registry.npmjs.org`, `pypi.org`, -`files.pythonhosted.org`, `static.crates.io`, `index.crates.io`, -`rubygems.org`. +`files.pythonhosted.org`, `rubygems.org`. diff --git a/npm/socket-patch/package-lock.json b/npm/socket-patch/package-lock.json index 987aca5f..0bd34b59 100644 --- a/npm/socket-patch/package-lock.json +++ b/npm/socket-patch/package-lock.json @@ -84,9 +84,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -100,9 +97,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -116,9 +110,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -132,9 +123,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -148,9 +136,6 @@ "cpu": [ "ia32" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -164,9 +149,6 @@ "cpu": [ "ia32" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -180,9 +162,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -196,9 +175,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/scripts/version-sync.sh b/scripts/version-sync.sh index 3451a9cb..48fde5f7 100755 --- a/scripts/version-sync.sh +++ b/scripts/version-sync.sh @@ -38,9 +38,17 @@ node -e " # Refresh the npm wrapper lockfile so package-lock.json stays in sync with the # bumped package.json (own version, optionalDependencies). Uses --package-lock-only # so node_modules is untouched. +# +# The npm major is pinned: the lock's byte shape depends on it (npm >= 11 adds +# `libc` arrays to platform-package entries that npm 10 omits), and +# release-lint.sh compares the regenerated lock byte-for-byte against the +# committed one on every PR. An unpinned npm makes that gate depend on +# whichever npm the runner or developer happens to have. Bumping this pin +# requires refreshing the committed lock in the same commit. +NPM_LOCK_REFRESH_VERSION="10" ( cd "$REPO_ROOT/npm/socket-patch" - npm install --package-lock-only --ignore-scripts >/dev/null + npx --yes "npm@$NPM_LOCK_REFRESH_VERSION" install --package-lock-only --ignore-scripts >/dev/null ) # Update all per-platform npm package versions