socket-patch supports yarn berry at cacheKey 10c0 — yarn 4 with the default
compressionLevel: 0, the one cache-zip checksum recipe it can reproduce
offline — in both modes: hosted (scan --mode hosted routes the locked
descriptors to the hosted tarball through root package.json resolutions and
re-keys the lock entry <name>@https://…/<name>-<v>.tgz, see below) and
vendored (vendor wires the root
package.json resolutions plus the lock's file: entry). yarn 2 and 3
(cacheKeys 7 / 8) are refused by both modes. The node-modules and pnpm
linkers are covered end to end; Plug'n'Play keeps packages inside
.yarn/cache zips, so vendor refuses it (vendor_yarn_berry_unsupported)
and so does apply (yarn_pnp_unsupported), while standalone vex still
attests a hosted lock's checksum: pin once the PnP loader (.pnp.cjs)
resolves the package through the hosted url. A loader written before the lock
was rewired still runs the registry copy, so vex omits the package
(package_not_found) until yarn install rewrites it (#519). Plug'n'Play is
decided by the configured linker (YARN_NODE_LINKER, else the nearest rc file
at or above the project that sets nodeLinker, else the home folder's rc file;
the rc file is .yarnrc.yml unless YARN_RC_FILENAME renames it; unset means
berry's default, pnp), not by whether a .pnp.* loader happens to exist:
vendor refuses a lock-only PnP checkout up front, and a stale .pnp.js left
by a Yarn 2 migration to node-modules or pnpm is ignored. Yarn 1 PnP
(installConfig.pnp, a classic yarn.lock) has no nodeLinker, so its
loader is always refused, whatever a berry setting says.
The hosted pin is what yarn itself writes for a root resolutions entry:
package.jsongains one descriptor-specific selector per range the lock entry carries, routed to the hosted tarball:"resolutions": {"left-pad@npm:^1.3.0": "https://patch.socket.dev/…/left-pad-1.3.0.tgz"};yarn.lockre-keys only that entry,"left-pad@https://…/left-pad-1.3.0.tgz":, with the same URL as itsresolution:and the patched10c0checksum. Itsversion:,dependencies:and every dependent's descriptors stay byte-identical, and the entry moves to where yarn sorts it.
Why this shape (#404):
- An
npm:locator — including thenpm:<v>::__archiveUrl=<url>pin releases up to 5.0 wrote — is fetched with yarn's npm fetcher, which attaches the configured registry auth (npmAuthToken,YARN_NPM_AUTH_TOKEN,npmScopes.<scope>.npmAuthToken) to every scoped package's request, and to every request undernpmAlwaysAuth: true, whatever host the URL names. The tarball fetcher sends none. - A tarball locator under the untouched
npm:key is rejected by yarn's hardened mode (YN0078: Invalid resolution), which yarn turns on by itself for CI runs on public pull requests andenableHardenedMode: trueturns on anywhere. Theresolutionspin passes it, so hosted mode assumes every berry project may run hardened.
Measured on yarn 4.12.0 (fresh checkout, cold cache, YARN_NPM_AUTH_TOKEN +
npmAlwaysAuth, hardened mode): yarn install --immutable --check-cache
passes for direct and transitive packages, leaves the lock untouched, and the
patch host receives no Authorization header; another locked version of the
same package keeps its registry entry. The real-yarn capstone
(e2e_redirect_yarn_berry_build) runs that fresh install in hardened mode with
a registry token configured and asserts the patch host received none.
Only yarn berry projects are touched this way: package.json is read beside a
berry yarn.lock only, and yarn classic, npm, pnpm, bun and vlt pins are
unchanged. rollback / remove rebuild the original lock key from the
selectors and drop them (an emptied resolutions table is removed); a lock
pinned by an older release (::__archiveUrl=) is still recognized by vex,
rollback and the mode takeovers, and the next hosted scan re-pins it.
Refusals (nothing written, the warning names the cause):
redirect_yarn_berry_resolutions_conflict—package.jsonalready has a user-authoredresolutionsentry for the package (bare, ranged or nested); hosted mode never overwrites it.redirect_yarn_berry_manifest_missing— no rootpackage.jsonobject.redirect_yarn_berry_shared_descriptor— the package is also locked through a non-npm entry (yarn's builtinpatch:compatibility entries forresolve,typescript,fsevents), which wraps the same descriptor a pin would move.redirect_yarn_berry_artifact_url_unsupported— an artifact URL yarn could not fetch as a tarball (nothttp(s), not ending in.tgz/.tar.gz, or carrying a query or fragment).
The yarn-berry-e2e job in .github/workflows/ci.yml runs
scripts/yarn-berry-vex-matrix.sh with SOCKET_PATCH_YARN_E2E_REQUIRED=1
(a toolchain or registry problem fails instead of skipping):
| OS | yarn |
|---|---|
| ubuntu-latest | 4.0.2 (bare-hex checksums), 4.1.0 (first 10c0/ spelling), 4.6.0, 4.12.0, 4.18.0 |
| macos-latest | 4.12.0 |
| windows-latest | 4.12.0 |
Pull requests, the merge queue and main pushes skip ubuntu 4.6.0 and 4.12.0
(the yarn-berry-full job, which runs nightly, on dispatch and on the v5
integration PR, and on main pushes only when CI_SCOPE is full).
Each release drives four real-yarn suites — e2e_redirect_yarn_berry_build,
e2e_vendor_yarn_berry_build, e2e_yarn4_pnpm_linker_build and
e2e_yarn4_workspaces_build — each ending in the manifest-less VEX matrix of
tests/yarn_berry_common, plus e2e_yarn_legacy_cachekey_refusal_build
against yarn 2.4.3 and 3.8.7. mode_migration_npm covers both mode
takeovers against yarn 4.12.0. Hermetic twins of every shape (no toolchain)
live in in_process_redirect, in_process_vendor, e2e_vex_lockfile and the
core unit tests.
Yarn Berry preserves a file's majority line ending and uses the platform's ending for a new file. Uniform LF and CRLF files are supported. Mixed line endings can fail Yarn's immutable-install check; Socket Patch refuses mixed lockfiles before rewriting them. The suites below cover native and forced CRLF checkouts.
What socket-patch does with those files:
hosted (yarn.lock) |
vendored (yarn.lock + root package.json) |
|
|---|---|---|
| uniformly LF or CRLF | rewritten in the file's own ending; no hosted ledger | lock entry spliced in the file's ending; package.json re-serialized in its own layout (BOM, indent, ending, trailing newline) |
| leading BOM | kept | kept, both files |
| mixed CRLF / LF, or a bare CR | refused untouched: redirect_yarn_berry_mixed_line_endings |
refused before any write: vendor_yarn_berry_mixed_line_endings |
revert (rollback, remove, takeovers) |
upstream entries reconstructed from registry metadata; mixed endings refuse as drift | byte-exact while the lock is unchanged since vendoring; a lock changed since gets the restored entry keyed by the descriptors it declares now and with its checksum in the lock's current spelling (#759); a lock mixed after vendoring gets the restored entry in the terminator of the entry it replaces |
| mode takeover into this mode | the vendored revert is staged and the hosted rewrite planned against it; a purl the berry rewriter refuses (line endings, cacheKey, compressionLevel, no berry checksum) is retracted and stays vendored, byte-identical |
the backend's project gates (both files' line endings, cacheKey, compressionLevel) run BEFORE the hosted redirect is reverted; a refused purl stays hosted, byte-identical |
Every reader — manifest-less vex, the lockfile inventory, the npm flavor
sniff, repair — splits CRLF lines like LF ones and skips a leading BOM.
The shared hosted golden fixtures stay LF: their TypeScript twin in the
depscan backend has no CRLF path yet.
SOCKET_PATCH_YARN_E2E_REQUIRED=1 SOCKET_PATCH_YARN_BERRY_VERSION=4.12.0 \
COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
cargo test -p socket-patch-cli \
--test e2e_redirect_yarn_berry_build --test e2e_vendor_yarn_berry_build \
--test e2e_yarn4_pnpm_linker_build --test e2e_yarn4_workspaces_build \
--test e2e_yarn_legacy_cachekey_refusal_build -- --nocaptureSOCKET_PATCH_YARN_BERRY_EOL=crlf runs the same flows on CRLF files on macOS
or Linux: right after each fixture's first yarn install, the files yarn
wrote are respelled CRLF — what yarn itself writes on Windows — and yarn keeps
them CRLF on every later write. Each fixture file prints one
BERRY-EOL|<yarn>|<flow>|<file>|yarn=<ending>|flow=<ending> line: the ending
yarn wrote (lf on macOS and Linux, crlf on Windows) and the one the flow
ran on. The mode_migration_npm berry legs honor the variable too (run them
with CI unset: they do not pin yarn's CI defaults).