Skip to content

Commit 2cadffb

Browse files
mikolalysenkoclaude
andcommitted
fix(redirect): fail closed on non-npm: berry descriptors + yarn layering regression suite
rewrite_yarn_berry matched lock blocks by descriptor NAME only, so a hosted redirect of a package that yarn berry builtin-patches (fsevents, resolve, typescript) spliced an `npm:` resolution + checksum under the `patch:`-protocol key — a corrupted key/resolution pairing in exactly the 2026-07 strapi incident's error family, emitted with no warning. Now non-`npm:` descriptor ranges (patch:/workspace:/portal:/link:) are skipped byte-identically with a redirect_yarn_berry_unsupported_protocol warning, mirroring the vendor backend's fail-closed gate (vendor/yarn_berry_lock.rs). New vendor::yarn_layering_tests (in-crate #[cfg(test)] module, since the core-dedups cleanup privatized the vendor wiring internals it plumbs; inventory_npm_lock widened to pub(crate) for the same reason) pins the incident's flows on a strapi-shaped yarn v1 fixture (multi-version ansi-regex + fsevents + resolve) and a yarn-4 berry fixture with real builtin patch: entries — 9 tests, each RED-verified: - vendored wiring is byte-surgical: untouched sibling/builtin-target blocks byte-identical, zero `patch:` strings introduced, file still parseable by the crate's own inventory - integrity chain: #sha1 fragment, sha512 SRI, and ledger sha256/size all recomputed from the on-disk vendored tarball - hosted redirect layered over vendored wiring records the vendored block as `original` (reversible), is byte-surgical, and re-runs are no-ops - vendor revert after a hosted overlay: drift-skipped and lossy (deletes the blob dir, lockfile stays hosted) — pinned as current behavior - berry builtin patch: entries survive vendored wiring AND hosted redirect byte-identically; redirecting the builtin-patched package itself now skips its patch: entry with the new warning Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1af43a3 commit 2cadffb

4 files changed

Lines changed: 1055 additions & 1 deletion

File tree

crates/socket-patch-core/src/patch/redirect/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,26 @@ fn rewrite_yarn_berry(
987987
if !version_re.is_match(block) {
988988
continue;
989989
}
990+
// Descriptor ranges carry a protocol; only an `npm:` range names
991+
// a registry tarball this rewriter can own. A `patch:` range
992+
// (yarn's OWN builtin compat patches — the 2026-07 strapi
993+
// incident family), `workspace:`, `portal:`, or `link:` block
994+
// must survive byte-identically: splicing an npm resolution
995+
// under such a key corrupts the key/resolution protocol pairing.
996+
// Mirrors the vendor backend's fail-closed gate
997+
// (vendor/yarn_berry_lock.rs).
998+
if !parsed.iter().all(|p| p.unwrap().1.starts_with("npm:")) {
999+
result.warnings.push(RewriteWarning {
1000+
code: "redirect_yarn_berry_unsupported_protocol".into(),
1001+
detail: format!(
1002+
"lock entry `{raw_key}` resolves {fname}@{} through a protocol \
1003+
the hosted redirect cannot own (workspace:/patch:/portal:/link:); \
1004+
leaving it byte-identical",
1005+
dep.version
1006+
),
1007+
});
1008+
continue;
1009+
}
9901010
// Rewrite the resolution wholesale from name+version — handles a
9911011
// pre-existing `::__archiveUrl=` (custom-registry lock) for free.
9921012
let resolution = format!(

crates/socket-patch-core/src/patch/vendor/lock_inventory.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ impl LockfileEntry {
9898
/// Inventory the project's npm-family lockfile. Routes by
9999
/// [`detect_npm_lock_flavor`] (PnP markers, bun.lockb, unsupported lock
100100
/// versions, and a missing lockfile all yield `None`).
101-
async fn inventory_npm_lock(project_root: &Path) -> Option<(NpmLockFlavor, Vec<LockfileEntry>)> {
101+
pub(crate) async fn inventory_npm_lock(
102+
project_root: &Path,
103+
) -> Option<(NpmLockFlavor, Vec<LockfileEntry>)> {
102104
// Rush monorepos have no root package.json/lock pair; their single
103105
// pnpm source-of-truth lives under common/config/rush/. The flavor
104106
// probe (root-relative) can't see it, so fall back explicitly when the

crates/socket-patch-core/src/patch/vendor/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ mod toml_surgery;
7575
pub(crate) mod verify;
7676
pub(crate) mod yarn_berry_lock;
7777
mod yarn_classic_lock;
78+
#[cfg(test)]
79+
mod yarn_layering_tests;
7880

7981
pub use path::{ecosystem_dir_for_purl, parse_vendor_path};
8082
pub use state::{load_state, lookup_entry, save_state, VendorEntry, VendorState, VENDOR_STATE_REL};

0 commit comments

Comments
 (0)