All notable changes to socket-patch are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Pre-v3.0 entries are concise summaries derived from each tag's commit history. For full per-release detail, see the GitHub releases page.
PRs never edit this file. Only the release agent writes [Unreleased], at
release time, from the PRs merged since the last tag and the code they
changed. Its ### headings set the next version's bump (Breaking/Removed →
major, Added/Changed/Deprecated → minor, anything else → patch). Releases
are cut by the release train
(docs/release-train/DESIGN.md) with
scripts/release.py: a release candidate's [Unreleased] entries become a
## [X.Y.Z-rc.N] section, the rolling release-sync PR brings each cut
section and version back to main, and promoting an rc folds its rc sections
into one ## [X.Y.Z] section. scripts/release-lint.sh refuses to release
a version without a non-empty section in this file.
- RubyGems.
- Agent mode patches gems with native (C) extensions for real. When
apply(orscan/get --mode agent) patches a gem whose patch changes native-extension sources (such asext/**/*.cor anextconf.rb), it now rebuilds the gem's extensions with RubyGems' own builder, the stepgem pristine --extensionsruns, using the Ruby that owns the gem home. Both the copy underextensions/<platform>/<abi>/and the copy under the gem'slib/are rebuilt, so Ruby stops loading the stale install-time.so/.bundle. A build that fails puts the original sources and build back (native_extension_build_failed), and a copy that can't be rebuilt in place (a precompiled platform gem, a Bundler git checkout, no matching Ruby) is refused withnative_extensions_unsupportedbefore anything is written. Sources an earlier release patched without a rebuild are rebuilt on the nextapply(native_extension_rebuiltwarning).vexandapply --checkattest such a patch only once its rebuild is recorded, and otherwise reportnative_extension_not_rebuilt.rollbackandremoverestore the original build. Callers that compile the extension themselves can opt out with--no-gem-native-rebuild(envSOCKET_NO_GEM_NATIVE_REBUILD): the sources are written without a rebuild, with anative_extension_not_rebuiltwarning, andvexdoes not attest them. Hosted copies are unaffected, since RubyGems compiles the patched gem itself (#1996, #1998). - Hosted mode replaces the gems the project already has installed.
Bundler keeps a gem it already holds whatever source the lock names, so
after
scan/get --mode hostedrepointedGemfile.lock,bundle installprinted "Bundle complete!" over the unpatched copy. The run now replaces those copies itself with the patched.gemthe lock pins, checked against that sha256 first. It writes files earlier releases never touched: an installed gem whose gem home is inside the project (its gem dir, the home's cached.gemand, for a gem with a native extension, the extension dir that RubyGems rebuilds in a staging home), and a stale archive in Bundler's cache dir (vendor/cache, a committed file the run lists with the files to commit).rollback,removeand the withdrawn-patch restore do the same with the upstream gem, downloading it from rubygems.org when no copy is on disk. Nobundleorgemcommand runs,--dry-runwrites and downloads nothing,--offlineuses only an archive already on disk, and the exit code never changes: a copy that can't be refreshed is left as it was. Gem homes outside the project (system gems,~/.gem, an rbenv or asdf home) and bundle paths that resolve outside it are left alone unless--gem-refresh-shared(envSOCKET_GEM_REFRESH_SHARED) is given, and--no-gem-install-refresh(envSOCKET_NO_GEM_INSTALL_REFRESH) turns the refresh off. Each replaced copy is aredirect_gem_install_refreshedorupstream_gem_install_refreshednote (redirect_gem_install_would_refresh/upstream_gem_install_would_refreshon a dry run). What is still stale is one human warning with the remedy as a command (redirect_gem_stale_install, and the newupstream_gem_stale_installafter an unwind), and the hostedNext steps:of a Bundler-only run say there is nothing to reinstall once the copies were replaced (#2052).
- Agent mode patches gems with native (C) extensions for real. When
- Go.
- Run from the directory holding
go.work,apply,vendorand hosted scans write one replace intogo.workfor a module the workspace shares, asgo work edit -replacedoes (when that directory has nogo.mod, another member lists the module, orgo.workalready holds Socket's replace). Before, a second member's replace was refused and ago.workroot with nogo.modcouldn't be patched at all. Hosted sum lines go togo.work.sum;--check,remove,vendor --revert,listandvexfind the replace there; a stale committed workspacevendor/modules.txtnamesgo work vendor; a replace of a module that is itself ausemember is refused, and a symlinkedgo.workorgo.modis never rewritten (#1764).
- Run from the directory holding
- General.
scan --mode hostedchecks every patch uuid the lockfiles pin with the patch API, for packages the run covers. A pin the server reportswithdrawnornot_foundmakes every fresh install fail with a 410, so the scan now restores it to its upstream registry entry, the same restorerollbackruns. It reports arolledBackevent withpatch_withdrawn, and--dry-runpreviews it. If a replacement patch is on offer, the same run pins it. A failed lookup keeps every pin and warnsredirect_withdrawn_check_failed. A restore that can't run warnsredirect_withdrawn_restore_failed(#1592).scannames the advisories a run fixes and the ones it leaves unpatched. One patch is applied per package version while the results table lists the ids of every candidate patch, so every mode now prints, under its result line,<purl>: fixes <ids>; still unpatched: <ids>for each package it patched (would fix/would stay unpatchedon a dry run), then one closing line saying that upgrading the package fixes the rest.scan --jsoncarries the same on eachpackages[]entry the run selected a patch for, in the newselectedUuids,fixedIdsandunfixedIdskeys. An advisory is reported fixed only when the selected patch names it (#2052).- An agent-mode run that records a patch (
scan --mode agent, agent-modeget) ends with aNext steps:block: commit.socket/, and runsocket-patch applyafter every install, locally and in CI, naming the install command of each patched ecosystem (bundle install,pnpm install,uv sync, …). After--save-onlyit says to runapplynow, and a Go patch addsgo.modto the commit step and needs no re-apply. Nothing is printed for a dry run, a failed or global run, or under--silent/--json(#2052). --ecosystemsandsocket.ymlpatches.ecosystemsaccept the names the output prints and everyday ones, in any case:python,ruby,go,php,pip,rubygems,rust,crates,java,gradle,sbt,dotnet,packagist,jsr, andnode/pnpm/yarn/bun/vltfornpm. Before,-e goand-e pythonwere rejected although the CLI prints "(5 go)", andsocket.ymlansweredgowith "did you meangem?". Any other name is a usage error that lists the slugs (#2052).
- General.
- Packages the project patches itself, and local edits, are left
alone unless you opt in. A Socket patch applied on top of a
project's own patch leaves an install that fails or runs only one of
the two, so this ships as a fix: socket-patch now detects the patches
a project declares itself (pnpm
patchedDependencies, Yarnpatch:descriptors, patch-package files, npm and Bun patches, Composer patches, Cargo[patch]/[replace]and Goreplace).scan(every mode) andapplyskip such packages with auser_patch_conflictwarning, and skips alone exit 0. Explicitgetandvendorrefuse them with exit 1. Agentapplyalso rejects installed files that match neither the patch's before-hash nor its after-hash instead of overwriting them; content an earlier Socket patch wrote (a stored.socket/blobsblob) is still overwritten withcontent_mismatch_overwritten. The new--allow-patch-conflictsflag is the explicit, per-run way to attempt the patch anyway (no environment or policy setting grants it) and emitsuser_patch_override.apply --forcestill overrides file drift but not project patches, and the remedyapply --checkprints for drift on a project-patched package names the flag (#1646). - Hosted scans no longer download the full patch view for every
confirmed redirect. In-run
--vexfetches only the records it attests; the gem stale-install probe compares the gem's cached.gemwith the patched artifact's sha256, and the Python probe checks the installed copy against the patched wheel the lock pins (downloaded only when a copy is installed). When neither can judge, the probe reads the patch view once; a copy still unjudged (fetch failure,--offline) is withheld by in-run VEX asstale_install_unverified. The Node binding still accepts afetchPatchprovider method but no longer calls it (#2021). - Plain
vendorwith a hosted pin in scope converts exactly the lockfile's hosted patches into.socket/vendor/artifacts, without installed packages or upstream source downloads, and ignores.socket/manifest.jsoneven when it exists (vendoring manifest patches takesscan --mode vendored). Dry runs reporteject_planned,--offlineis refused up front withoffline_eject_unavailable, and the eject heals warm vlt store entries (redirect_vlt_reinstall_required) (#1648). - The npm package's
@socketsecurity/socket-patch/schemaexport now depends on zod 4.6 (was 3.25) (#1702). - Bare
rollbackandremove <purl>now also delete orphan.socket/vendor/<eco>/<uuid>/directories that no ledger entry owns, within the run's scope. One example is the losing side of a git merge of two vendor runs. Before, onlyvendor --revertandscan --pruneswept them. An orphan that a lockfile still points into is kept and reported asvendor_orphan_still_wired, and--preserve-statekeeps them all. A failed delete is now acleanup_failedwarning inrollback,remove,vendor --revertandscan --prune(#1610, #1683). - A bare
scancovers every project in the git checkout. With no PATH, a hosted or vendoredscan(the default mode included) now scans--cwdand every other project directory below it in the same git checkout, each as if it had been named: its own lockfiles, mode and.socket/, under a== <dir> ==heading (== . ==for--cwd), with one--max-new-patchesbudget and the worst exit code. A scan from a monorepo root therefore rewrites lockfiles in subdirectories that earlier releases left alone: they scanned the root only, or printed "No packages found." and exited 0 when the root had no lockfile. A project directory is one holding an npm-family, PyPI, Cargo, RubyGems, Go or Composer lockfile; Maven, Gradle, sbt and NuGet projects are still scanned only as--cwdor a named PATH. The search honours git's ignore rules (a.ignorefile is not one of them), follows no link, never entersnode_modules,.git,.socket,.yarnorvendor, never enters a git submodule or a nested clone (another repository: run socket-patch inside it), and leaves out a workspace member whose lock the workspace root's run reads.socket.ymlpath filters and the built-in test and fixture ignores apply, with one note counting what they left out. Nothing changes outside a git checkout, with a PATH, under--global/--global-prefixor in agent mode.--jsonand--vexstill scan--cwdalone and name the directories they left in a newprojects_not_scannedwarning (#2052). listand a barerollbackcover what such a scan patched: the project at--cwdand every patched project directory below it, under the same headings.listadds one total line and, under--json, a top-levelprojects[]beside the unchanged envelope for--cwd.rollbackwith no target now also rolls back the directories below, after one confirmation that names them, and exits with the worst code;--dry-runpreviews them all. It reaches exactly as far as a bare scan from the same directory: from an agent-mode project, whose scan covers that project alone, it rolls back that project alone and warnsprojects_not_rolled_back.rollback --jsonnever changes a directory other than--cwd: it rolls back--cwdalone, as before, with the same warning, and with nothing to undo there it fails as it always did (manifest_not_found, exit 1).rollback <target>,remove,vex,repairandapplystill act on one project and keep their exit codes. When--cwdholds nothing for them they add a note (patched_projects_belowunder--json) naming the directories below and the--cwd <dir>command for each;apply --silentdoes not search (#2052).scanandgetreport "no patches" only when the patch service said so. An answer from another host (a mistyped--proxy-urlor--api-url, a captive portal, a forward proxy), a 404 the service did not write, and the API's "Organization not found" for a mistyped--orgare now errors; so is a lookup of the token-less per-package fallback that ends in a 5xx or in the proxy's "Patch API is not configured". Before, each read as "No patches available for installed packages." or "No patches found" with exit 0.scangoes on with the packages that were answered and reports the rest with apatch_lookup_failedwarning and afailedevent each: the run ispartialFailureand exits 1 in every mode, and exits 1 withapi_batch_failedwhen no package was answered.getexits 1 naming the host or the org slug to check. A lookup still throttled after its retries is reported the same way instead of failing its whole batch (#2052).- Manifest records no crawler can look for are named instead of dropped. A
record of an ecosystem this CLI doesn't support (one a newer
socket-patch wrote, or a mis-cased type such as
pkg:NPM/…) is askipped/unsupported_ecosystemevent and one warning inapply,apply --checkandrollback; it never changes the exit code, andapply --checkno longer counts it as in sync. A key that is not a purl at all now fails:applyexits 1 naming it while the other patches still apply,apply --checkreports drift (invalid_purl) androllbacka failed entry.removerefuses either kind before any change (exit 1) unless--skip-rollbackis given. Before,applyprinted "No patches to apply." and exited 0, andremovedropped the record without restoring anything (#2052). repairverifies the blobs it finds. Every referenced blob is re-hashed, and one whose bytes changed on disk (acore.autocrlfcheckout of a committed.socket/blobs/, a truncated file) is downloaded again and replaced. Before,repairsaid "All blobs are present locally" and everyapplyfailed on the blob.repair --offlinenow exits 1 when a referenced blob is missing or corrupt, with afailedevent per blob (offline_missing_sources/ newblob_corrupt) and an error list--silentdoesn't mute; it used to print "Repair complete." and exit 0.applyandrollbackname a corrupt blob by its path and say to runsocket-patch repair(#2052).- A failed checksum-sidecar fixup now fails the package. When
applyorrollbackcan't bring cargo's.cargo-checksum.json, NuGet's.nupkg.metadataor the Maven and Coursier checksum files in line with the files it wrote, the files are put back, the event isfailed("sidecar fixup failed" / "sidecar resync failed") and the run exits 1: an apply leaves the package unpatched and a rollback leaves it patched. Before, the package counted as applied with exit 0 and only asidecar_fixup_failedrecord under--json, which for cargo left a cratecargo buildrefuses. A run that finds every file already patched now rechecks and repairs those sidecars, andapplyprints sidecar notes and warnings on stderr without--json(#2052). - The programs socket-patch runs (package managers and their
version-manager shims, interpreters,
git, a gem'sextconf.rband compiler) no longer inherit Socket credentials or settings. Their environment is stripped of the API token under every name a Socket tool reads it from (SOCKET_API_TOKEN,SOCKET_CLI_API_TOKEN,SOCKET_SECURITY_API_TOKENand the_API_KEYspellings),SOCKET_CLI_CONFIG,SOCKET_CLI_API_PROXY, every flag-boundSOCKET_*variable and the Socket registry override URLs. Names are matched exactly, soHTTP_PROXY,HTTPS_PROXY,NO_PROXY,NPM_TOKENand other registry credentials still reach the child (#2052). vex --no-verify(also--vex-no-verifyandSOCKET_VEX_NO_VERIFY) says what it did not check. Each statement emitted without on-disk verification ends its impact statement with; not verified on disk, and the run warnsvex_not_verifiedwith the count. Before, an agent-mode patch whose files were unpatched or missing was attestednot_affectedwith the same text as a verified one, with no warning. A verifying run's document is unchanged (#2052).vexattests a vendored patch only while a lockfile reference wires its package to its artifact. A vendor ledger entry is no longer kept alive by text in the files the ledger names: apnpm.overridesentry left inpackage.jsonafterpnpm-lock.yamlwent back to the registry, a Gemfilepath:beside a reverted lock, or any file that merely spells the vendored directory now getsvendor_unwiredand no statement.vendor --checkapplies the same rule;scan --prunestill keeps such an entry (#2052).- A lockfile URL counts as a Socket patch reference only when every client
reads its host alike and the patch server serves its path. A URL with a
backslash, whitespace or a control character
(
https://patch.socket.dev\@evil.example/…), a percent-encoded/or\, or a path other thanpatch/<ecosystem>/…,patch-registry/<ecosystem>/…orgopatch/…(under any prefix on a--patch-server-urldeployment) is no longer attested byvexor managed bylist,rollback,removeandscan(#2052). - When two lockfiles wire one package to the same hosted patch and one of
them lacks the integrity pin hosted mode always writes,
vexno longer attests the patch from the lockfiles alone: the pinned lock doesn't vouch for the other (#2052). vexno longer accepts a vendored reference whose artifact name carries a%(.socket/vendor/npm/<uuid>/%66oo-1.0.0.tgz): npm, yarn and pnpm open the literal file while pip percent-decodes it, so it names no single file to verify. No vendor backend writes such a name (#2052).
- Packages the project patches itself, and local edits, are left
alone unless you opt in. A Socket patch applied on top of a
project's own patch leaves an install that fails or runs only one of
the two, so this ships as a fix: socket-patch now detects the patches
a project declares itself (pnpm
- RubyGems.
- Hosted mode keeps your
gemdeclaration and always converges the lock.scan/get --mode hostedused to replace a gem's Gemfile declaration with asource "<patch registry>" do … endblock holding the exact version, which lost the version requirement, quoting and comment for good, and it left a lock withoutCHECKSUMSalone, so frozen installs on Bundler older than 2.6 exited 16. It now appends one option,, source: "<index url>", to the declaration and changes nothing else. That covers parenthesized calls, and the multi-line,if-modified and;-joined declarations that were refused before. A transitive gem gets one line at the end of the file (gem "<name>", "<version>", source: "<url>", require: false, marked# added by socket-patch (transitive dependency)).Gemfile.lockis always edited to the bytes the Bundler that wrote it produces, now also a lock with noCHECKSUMSsection, which earlier releases did not write, so frozen and deployment installs accept the pair as committed.redirect_gem_no_checksums_sectionis raised once per run instead of once per gem, and a lock written by Bundler older than 2.2 warns the newredirect_gem_merged_lock_layout. A gem whose pair can't be made consistent is skipped with neither file changed (newredirect_gem_dependency_not_locked,redirect_gem_lock_unconvergeableandredirect_gem_inconsistent_plan), and a gem inside your ownsource/git/github/pathblock is now refused withredirect_gem_source_optionwhere it used to get a nested Socket block.rollbackandremovetake the option or the added line off again, byte for byte. Block pins written by 5.0 / 5.1 stay recognized: a re-scan keeps the block and now converges its lock, and their unwind still restores the exact pin, with a newupstream_gem_declaration_pinnedwarning. The scan prints one line naming the declarations it changed (#2052). vexattests a gem under a Socket section ofGemfile.lockonly when the manifest beside the lock gives the gem that source: thesource:option, the 5.0 / 5.1source … doblock, orpath:to the vendored directory. With a lock that names the Socket source and a Gemfile that does not, Bundler fails a frozen install and an unfrozen one rewrites the lock and installs the upstream gem (checked on Bundler 1.17, 2.1, 2.4, 2.5 and 4.0), so that state is no longer attested: a hosted pin readspatched_ref_unattributableand a vendored onevendor_unwired. Both stay among the pinsrollbackandremoveunwind. A declaration whosesource:/path:is not a plain string literal, and a lock checked out without its manifest, are withheld too (#2052).
- Hosted mode keeps your
- npm family.
- A package the project patches itself (pnpm
patchedDependencies, a yarnpatch:descriptor or lock locator, patch-package files), including in workspace members, is never pinned by the in-memory hosted engine (skippeduser_patch_conflict) and never attested byvex(vex_npm_patched_dependency), even when an--allow-patch-conflictsrun wired it. A Bun patch made on top of the Socket pin no longer attests either, anypnpm-lock.<branch>.yamlwithholds the pnpm lock pins whatevergitBranchLockfilesays (vex_pnpm_git_branch_lockfile), and an unreadable lock or patch evidence outside the repository fails closed (#2018). vexno longer trusts a lock the project tells its package manager not to read: npmpackage-lock=false(orshrinkwrap=false) in.npmrc, yarn 1--install.no-lockfilein.yarnrc, a yarn berrylockfileFilenamenaming another file, or pnpm'slockfilesetting turned off. A hosted pin in such a lock is attested only from an installed tree that verifies, and a vendored pin is omitted with the newvex_lock_not_read, except a pnpm pin that the override map pnpm reads still routes to the artifact.list,rollback,removeandvendor --checkstill see the pin (#2052).vexwithholds a pnpm pin (patched_ref_unattributable) when the lock installs a second copy of the samename@version: an entry from a git remote or a URL tarball, afile:copy whosepackage.jsoncan't be read, or, in lockfileVersion 5 / 6 locks, another registry key of the release. A root-relative.socket/vendor/npm/…path in a per-member or Rush lock is reported aspatched_ref_invalidinstead of attested, since pnpm resolves it against that lock's own directory (#2052).- Run from a pnpm or yarn 1 workspace member,
vexreads the member's own straypnpm-lock.yamlor classicyarn.lockas absent, because the package manager installs the member from the workspace root: its pins are not attested, and onepatched_ref_unattributablenames the root to run from. Per-member pnpm locks (sharedWorkspaceLockfile: false) and a berry lock in a member are read as before (#2052). - Hosted
scanandgetrun from such a member refuse instead of pinning a lock nothing installs from. Apnpm-lock.yamlinside a member thatpnpm-workspace.yamllists is never read by pnpm, which installs the member from the workspace's shared lock (checked with pnpm 10.34 and 11.27), and yarn 1 never reads ayarn.lockinside apackage.jsonworkspace member. Earlier releases wrote the pin there and reported the package switched while installs kept the unpatched copy. The run now exits 1 withredirect_workspace_lockfile_elsewhere, names the workspace root to run from and writes nothing. A workspace that setssharedWorkspaceLockfile: false, a directory the workspace does not list and a yarn berry lock in a member are pinned as before (#2052).
- A package the project patches itself (pnpm
- PyPI.
- Vendored uv refuses a patch for another version than
uv.lockresolves (pypi_uv_lock_package_missing, exit 1), before any download and in the--dry-runpreview. Earlier releases wired it with exit 0 by rewriting the lock entry'sversion, so a patch left behind by a dependency bump moved the lock down to the older release anduv syncinstalled it. With patches for two releases, the one for the locked release is wired and the other is refused. A project an earlier release vendored that way keeps working and still reverts (#2052). vexwithholds a Python pin that another entry or requirement installs unpatched. That covers a second entry of the same release in the sameuv.lock,pylock.toml,poetry.lock,pdm.lockorPipfile.lockthat resolves from the index (a marker fork, an extras variant, the other Pipfile category), and a PEP 723 script lock that disagrees with the project lock: script locks now take part in the cross-lock check. Re-runningscanorvendorrewires every entry and clears both. A reference behind an environment marker, extra, dependency group or Hatch environment is also withheld (patched_ref_unattributable) while a plain requirement can install the same release where the reference doesn't apply (#2052).
- Vendored uv refuses a patch for another version than
- Cargo.
vexattests a Socket-sourcedCargo.lockentry only while a declaration cargo honors routes the crate to it: a live dependency pinned to the patch registry whose requirement can select that version, or a live[patch]registry pin in the root manifest or.cargo/config.toml. A lock entry nothing routes (a transitive-only crate, a shadowed[patch.crates-io]entry) makescargo fetch --lockedfail and an unlocked build link the upstream crate;vexattested it and now withholds it withpatched_ref_unattributable. The entry stays among the pinsrollbackandremoveunwind. Run from a workspace member,vexattests nothing from the member's own[patch]or lock, which cargo ignores for the workspace root's, and names the workspace root (#2052).
- JVM.
vexwithholds a Gradle statement when the derived-cache check of its patched copy doesn't finish: the purl is omitted asgradle_unpatched_copywith the newvex_gradle_derived_cache_check_failedwarning. A check that crashed used to count as nothing to check (#2052).- Maven: a patch pinned only through
<dependencyManagement>, for a package the root pom does not declare, is weighed against the parent poms. When a parent in the checkout, a grandparent or an active parent profile declares the package with a literal version, Maven builds the upstream jar, sovexomits the patch (vex_maven_parent_declares). When a parent cannot be read (<relativePath/>, a parent outside the checkout, a missing one), the pin is no longer attested from the pom alone: it readspackage_not_founduntil a build has resolved the patched version. That withholds the pre-build statement in projects that inherit from a published parent such asspring-boot-starter-parent; the statement returns after a build (#2052).
- Composer.
vexno longer attests a hosted Composer pin from the lock alone whencomposer.jsonputs the install outside the project (config.vendor-dir) and an install exists there that the crawler does not read: the patch is omitted withcomposer_install_unread. SetCOMPOSER_VENDOR_DIRto have that tree read and verified (#2052).
- RubyGems.
- With Bundler 4.1
override "other", with: "gem", in: "parent", hostedrollbackandremovetreat the override target as transitive and give back the original lock and Gemfile byte for byte, instead of writing a new exactgem "gem", "x.y.z"pin that blocked later upgrades (#1989, #1991). - Gemspec directives and development dependencies are read as Ruby code:
=begin/=endblocks, heredoc bodies and__END__data are skipped, so a commented-out dev dependency no longer adds a bogus requirement and a heredoc no longer refuses the Gemfile (#1932, #1936). - A gem declared inside nested
source … doblocks is attributed to the innermost block, as Bundler reads it, so hostedrollbackandremoveof a pin inside the user's own source block no longer refuse withhosted_wiring_contestedon Bundler 2.1 and older (#1997, #1999). - CRLF Gemfiles and Gemfile.locks: hosted and vendored writers keep the file's line endings instead of adding LF lines, revert restores both byte for byte, and vendored mode no longer fails a CRLF lock with "no GEM section"; a lock mixing endings fails closed (#1808, #1811, #1821, #1822, #1831, #1841).
- A merged Bundler 1.17-2.1 lock with several hosted gems in one GEM
section is read per Socket remote, so
vexattests them and a re-scan no longer skips them asredirect_unattributable(#1749, #1750). - On Bundler 2.7+, a
gemspecdev dependency merged into a latergemline is pinned with the requirement Bundler writes, so frozen installs stop failing; hosted warnsredirect_gem_gemspec_dependency_unreadablewhen it can't read the gemspec, and vendored mode refuses the shape withgemfile_declaration_not_editable(#985, #1718). vendor --dry-runover a hosted gem inside agroup ... doblock previews the refusal the real run gives instead of planning a clean eject (#1761, #1762).- A Bundler config setting
disable_shared_gemsto a false value is read likepath.system, soapplypatches the system gem homes Bundler loads, not a leftovervendor/bundle(#1931, #1943). - On a Bundler 1.17–2.1 lock, which has one merged
GEMsection, a superseding hosted patch now moves the lock's Socket remote along with the Gemfilesourceblock. Before, every scan skipped the new patch asredirect_unattributable(#1688, #1690). - A superseding re-scan on a lock without
CHECKSUMScan move only the Gemfile block. Hostedrollbackandremoveafter it now also undo the newer patch's Socket source block, so the nextbundle installno longer brings the patched gem back. Restoring only the newer pin is refused, naming the older patch (#1626, #1664). - Vendored unwinds (
remove,rollback,vendor --revert,scan --prune) warnupstream_gem_stale_cachewhenbundle cacheleft a patched archive invendor/cache, and name it, as hosted unwinds already did (#1594, #1667). rollbackandremovekeep a gem's!source pin inDEPENDENCIESwhen itssourceblock opens after a;or another expression, spans several lines, or uses{ }. Bundler 4.0.19+ frozen installs no longer fail (#1509, #1520, #1531, #1537).vexreads a gem library's product from its gemspec the way Ruby does, skipping comments,=beginblocks, heredocs and__END__. A commented-outspec.versionorspec.nameno longer sets the product purl (#1573, #1582).- Agent
applyruns no Ruby for a gem's native-extension rebuild until the patch's files verify against the installed gem, and none on a dry run. Before,apply --dry-runevaluated the installed gemspec, a Ruby program that a committedvendor/bundlelets the scanned repository supply. The gemspec is now read as text, a dry run reports the pending rebuild as anative_extension_rebuiltwarning, and refusal codes are unchanged (#2052). - Interrupting
applyduring a gem's native rebuild (Ctrl-C, SIGTERM) ends the build with it, instead of leavingruby,extconf.rband the compiler running into a gem no lock protects. The nextapply,rollbackorremoveputs the previous build back first, then rebuilds or restores; before, it deleted the parked build or refused with "installed by a RubyGems older than 2.2". A gem that another process is still rebuilding is refused withnative_extensions_unsupported(#2052).
- With Bundler 4.1
- npm family.
- pnpm workspace globs with brace sets and character classes
(
packages/{a,b},packages/[ab]*) are read the way pnpm reads them in every mode instead of being refused or read as every directory; shapes pnpm reads its own way (extglobs,[!a], a+after a class, stepped or punctuation-spanning brace ranges) are still refused, and commented importer lines inpnpm-lock.yamlare ignored (#1846, #1863). - Quoted values, YAML escapes and duplicate keys in
pnpm-workspace.yaml,.yarnrc.ymland.modules.yamldecode one way, as pnpm reads them, so settings such asmodulesDirorlockfileDirno longer resolve to different paths depending on the command (#1918, #1973). - yarn classic's
.yarnrcis read as yarn 1.22 reads the whole file:--modules-folder 'deps'no longer sends the crawler to a folder yarn never installs into, and a file yarn can't parse sets nothing (#1901, #1915). - A malformed
package.jsonworkspacesfield (a non-string item,null, the wrong type) is judged the same way everywhere; the Bun member walk falls back tobun.lockinstead of reading no members (#1900, #1971). applyandrollbackno longer touch package copies inside a store nested in another store's entry, whichscannever reports, and keep the peer-variant copies of a project that lives under~/.bun,~/.denoor~/.vlt(#1899, #1986).- Hosted npm warns
redirect_npm_omit_lockfile_registry_resolvedwhen any npm config layer setsomit-lockfile-registry-resolved, which makes the nextnpm installdrop the hosted URL and fail withEINTEGRITY, and names where it is set and the remedies (#1871, #1872). - Hosted vlt scans no longer download every granted patch archive to
check its encoding, and no longer withhold dependencies with
redirect_vlt_artifact_unverifiable; a mirror that re-encodes archives makesvlt cifail closed withEINTEGRITY. The in-memory hosted engine (the Node addon andhosted-bundle) now pins vlt dependencies instead of withholding them as offline (#2013). - The lock inventory skips Rush subspace locks with unsafe names, as lockfile discovery already did (#2003).
vexno longer attests npm-family pins the hosted and vendored writers refuse: pnpm pins whilegitBranchLockfileis on and a branch lock exists (vex_pnpm_git_branch_lockfile), and npm or Bun packages the project patches itself (vex_npm_patched_dependency) (#2010).- A registry dependency that the root
package.jsonoverridessend to a tarball URL, git or path spec (npm installs that spec, not the lock'sresolved) is skipped by hosted mode withredirect_npm_non_registry_entry_skipped, refused by vendored mode, and no longer attested byvex, including overrides scoped to a workspace member (#1965, #1966). - pnpm locks are read the same way by every reader: pnpm 11's env
lockfile document (config dependencies) is its own install tree, so
its entries no longer contest the project's, and a config-dependency
pin isn't attested while the project document resolves that
name@versionelsewhere (hosted names the copy withredirect_copy_unpatched). Rush subspace locks are always read beside the root lock. Vendored pnpm now edits a CRLFpnpm-lock.yamlorpnpm-workspace.yamland writes it back CRLF (only mixed endings are refused,vendor_lockfile_crlf_unsupported); ashrinkwrap.yamlthat apnpm-lock.yamlshadows is left alone by hosted mode, and a pnpm 1/2 project beside one is refused by vendored mode (vendor_lockfile_version_unsupported) (#2009). - A commented-out importer key in
pnpm-lock.yamlis no longer read as a workspace member, and every reader agrees on which importers a root lock lists (#1949, #1952, #1960). - Hosted yarn classic scans read a
file:copy outside the project root (file:../ext/x) the way vendored mode andvexdo, and name it withredirect_copy_unpatchedinstead of reporting a clean pin (#1951, #1984). - yarn 1 bundled copies (
bundleDependenciesunpacked under a parent) are no longer attested as patched:vexwithholds them, hosted scans name them (redirect_copy_unpatched), and vendored scans warnvendor_bundled_instance_skipped(#758, #1712). - After a plain
npm installre-saved a vendored lockfileVersion 2 lock withoutresolvedin its legacydependenciesmirror,rollback,removeandvendor --revertrestore both lock halves and remove the artifact instead of half-reverting and exiting 1 (#1937, #1938). - npm bins with a CRLF shebang:
apply,apply --check,rollbackandvexaccept and write the LF form npm's bin-links give a linked bin, so a pristine install no longer fails as drift and a patched bin keeps running. Bins v5.0.1 left as#!/usr/bin/env node\r(exit 127) are repaired on the nextapply; bins npm didn't link keep their CRLF bytes (#1756, #1757, #1791, #1792, #1804, #1805). - Run without
--cwd,scanandapplylook above the working directory as--cwd <abs>does: a parent.yarnrc--modules-folder, a root's relocated yarn pnpm store, and a rootpnpm-workspace.yamlmodulesDirare found from a member (#1902, #1906). - Agent mode crawls npm workspace members declared under a hidden
directory the pattern spells (
.github/actions/*,.config/tool) instead of leaving theirnode_modulesunpatched (#1827, #1829). - Agent mode finds Yarn 4 pnpm-linker stores moved by
pnpmStoreFolder(.yarnrc.yml, home rc orYARN_PNPM_STORE_FOLDER) inside the project, instead of reporting transitive packages not installed (#859, #1721). - Global scans find packages installed with yarn 1.0.0-1.0.2
yarn global add(noyarn global dirthere), including a configured--global-folder(#437, #1798). - Deno global installs:
scan -g,apply -gandrollback -gpatch the$DENO_DIR/npm/<registry>copiesdeno install -gtools load (peer-variant copies included),--global-prefix $DENO_DIRor$DENO_DIR/npmexpands to every registry below it, a registry served one path segment below another on the same host is no longer skipped, and a prefix naming a toolnode_modulesDeno never loads is a usage error (exit 2) (#444, #1719, #1741, #1784, #1785). - In a Deno project that also keeps an npm-family lock, hosted and
vendored runs warn when
deno.locklocks the samename@version(redirect_npm_deno_lock,vendor_npm_deno_lock), sincedeno installnever reads the rewired lock (#406, #1711). - Hosted
rollbackandremovein a pnpm workspace with per-member locks weigh every member lock when deciding whethertrustLockfileis still needed, and warnpnpm_trust_lockfile_leftafter restoring a member lock (#1268, #1781). - Vendored pnpm names each
file:copy of the vendored release that the override can't reach (vendor_copy_unpatched) instead of reporting a clean success (#935, #1736). - yarn berry: hosted
rollback/removeand vendored revert refuse, writing nothing, when the user hasyarn patched the pinned package, instead of leaving a lockyarn install --immutablerejects (#962, #1733); vendoring beside a parent-scoped or globresolutionsentry for the package refuses withvendor_override_conflict(#783, #1705). - Vendored yarn classic reverts a lock block yarn re-keyed (merged or
dropped a range) under its current key, so
rollback,removeandvendor --revertno longer fail forever (#692, #1728). - A
bun patchmade after socket-patch rewired a package is keyed by the package's current resolution, the hosted URL or vendored tarball that Bun 1.4 writes. A superseding hosted run, a takeover or a vendored re-run moved the entry and orphaned that key, so the project's patch silently stopped applying. Scoped packages keyed this way were also unprotected. Such keys now protect the package.rollback,removeandvendor --revertwarnredirect_bun_patched_dependency_dropped/vendor_bun_patched_dependency_droppedonly when the unwind takes out a key the project's patch uses (#1019, #1699). - Bun workspace membership is read the way Bun reads it. Wildcards no
longer match dot directories, so a project in
packages/.toolswith its ownbun.lockis independent.!exclusions are honoured for Bun 1.3.0/1.3.1 locks, which carry no config-version tag, and for a binarybun.lockbwritten by Bun 1.3+. Hosted and vendored runs from such a project now patch its own lock instead of refusing withredirect_workspace_lockfile_elsewhere/vendor_lockfile_missing(#1492, #1496, #1521, #1523, #1631, #1655). - Vendored npm
scan --vex,vexandvendor --checkno longer attest a package that afile:directory dependency also installs at the patched version (patched_ref_unattributable). When a git, folder, URL,file:tarball or bundled copy sits beside the vendored copy,vendor --checkin npm and Bun projects now reports "copy unpatched" and names the lock entry. Before, it said the wiring was lost and gave re-vendor advice that looped (#1511, #1513, #1600, #1675). - Yarn 4's
injectEnvironmentFiles(.env.yarnby default) are applied when${NAME}is expanded in.yarnrc.yml. AnodeLinkerset through.env.yarnno longer makes vendored mode refuse a node-modules project as Plug'n'Play, or agent mode skip a live PnP project as not installed (#1611, #1676). - A hosted scan of a yarn 1 workspace now refuses with
redirect_yarn_classic_offline_mirrorwhen a member's.yarnrcor.npmrcsetsyarn-offline-mirror. Before, it pinned the root lock and reported success, installs from that member failed integrity checks, andvexattestednot_affected(#1115, #1694). - A
yarn.lockcan also install the patchedname@versionunder another dependency name: a renamedfile:tarball in yarn classic, or any renamedfile:, URL,portal:orlink:copy in yarn berry. Hosted scans now warnredirect_copy_unpatchedand vendored scans warn the newvendor_copy_unpatched. When the package is installed only under other names, vendored scans refuse withvendor_lock_entry_not_rewritable. Lock-onlyvexno longer attests the package (#939, #1236, #1489). - Yarn copies whose package can't be identified now fail closed instead
of being dropped. This covers absolute, missing or empty
portal:/link:targets; a manifest that doesn't parse or has a missing or invalid name or version; berryexec:dependencies; and git remotes that aren't http(s). Scans name these copies (redirect_copy_unpatched/vendor_copy_unpatched), andvexwithholds the versions they may install. Copies outside the project, and tarballs whose top directory isn'tpackage/, are read the way yarn reads them (#1516, #1517, #1540, #1551, #1561, #1571, #1579, #1586, #1598, #1605, #1630, #1644). - Yarn classic pins are compared with the patched
package.jsonthe way yarn 1 normalises it. A""range, or a name listed under several dependency kinds, no longer drops the pin from lock-onlyvex. A dependency that every pinned block already names no longer needs a lock key of its own. That check refused patches with a remedy that couldn't clear it (#1558, #1578, #1596). vexandscan --vexcheck the dependencies a patched npm package declares. The installed check finds the copy node actually loads and omits the patch withdependency_not_installedwhen that copy's version is outside the declared range. Lock-onlyvexof a yarn classic hosted pin requires the block's dependency sub-maps to match the patchedpackage.json. If they don't, the patch is omitted withdependency_not_installed, or withdependency_manifest_unverifiablewhen the patchedpackage.jsoncan't be read (#1538, #1548).- Yarn berry workspace globs that use picomatch syntax are read the way
yarn reads them: regex groups like
packages/(a|b),?and+quantifiers, POSIX classes and..segments. A run from such a member now refuses withredirect_workspace_lockfile_elsewhereand names the root. Before, it pinned nothing and exited 0 (#1490, #1493). - vlt project roots and workspace members are found the way vlt finds
them. That means the nearest ancestor
vlt.json, extglob members (packages/+(a|b)), vlt 1.2/1.3!rules, and the members vlt 1.4 reads from a rootpnpm-workspace.yaml. A hosted or vendored run from a directory that vlt installs from another root now refuses withredirect_workspace_lockfile_elsewhere/vendor_lockfile_missingand names that root. Before, it rewrote avlt-lock.jsonvlt never reads while vendoredvexattestednot_affected(#1542, #1555, #1564, #1574, #1584, #1658). - Hosted vlt scans can skip a package resolved from a named or scoped
registry, git, a remote tarball or a local folder. The skip warning
now points to agent mode only, since vendored mode refuses those
nodes too. It no longer comes with a duplicate
redirect_vlt_entry_not_found"runvlt install" warning. The vendored refusal also names agent mode (#1601, #1677). - Vendored vlt refuses packages whose only build scripts are
preprepareorpostprepare(vendor_vlt_build_scripts_unsupported), including directories an earlier release already vendored.vlt ciruns those scripts inside the committed vendor directory (#1633, #1637). - pnpm 12.0–12.12 record a
tarball:URL for packages from a scoped registry.rollback,removeand the hosted-to-vendored takeover now keep that URL, so the restoredpnpm-lock.yamlmatches what pnpm wrote (#1619, #1678). - Vendoring a pnpm package with an approved build script now copies the
allowBuilds/onlyBuiltDependenciesdecision to the vendoredname@file:key, which pnpm 10.34.2+ matches. pnpm no longer skips the script or fails frozen installs withERR_PNPM_IGNORED_BUILDS. Revert,rollbackandremovetake the added keys out. The key is written only when the project's pnpm pins rule out releases older than 10.34.2. Otherwise, or when the value is flow-style,vendor_pnpm_build_approval_lostnames the key to add (#1597, #1668). - Hosted pnpm reads
pnpm_config_*variables the way pnpm does. On pnpm 11+, apnpm_config_trust_lockfileset to anything other than true is the user's own setting. NotrustLockfile: trueis written for the variable to override, and the warning names the variable. A boolean variable that pnpm 11 ignores (an empty CI variable,null) is ignored, and pnpm 12's trimming is followed (#1514, #1533, #1557, #1575). - npm 12's
allowScriptscan approve a patched package's install script only by a version-pinned key, or deny a pinned version. That key stops matching once the lock points at the patch, so the nextnpm cisilently skipped the script. Hosted and vendored (package-lock.json) scans now warnredirect_npm_allow_scripts/vendor_npm_allow_scripts(#1595, #1657). - The vendored refusal of a lockfileVersion 4
package-lock.jsonnow namesnpm install --lockfile-version 3. The old advice kept the lock at v4 and refused again forever (#1576, #1651). - Packages under the root project's own
bundleDependenciesare wired like any other lock entry. Before, hosted mode skipped them, vendored mode failed with exit 1 andvexleft them out (#1618, #1661). - The npm-family crawler now walks the
node_modulesof declared workspace members under a directory namedbuild,dist,vendor,tmpand the like. Agentapplypatches them, andapply --checkandvexno longer report an unpatched copy as patched (#1627, #1628). - A root hosted or vendored scan can meet an independent nested npm
project, one with its own lock or installed with no lock. The scan now
leaves that project's packages to their own run, with one
nested_project_skippedwarning naming the path to scan. Before, every vendored run failed withvendor_lock_entry_not_foundand lost its in-run VEX (#1534, #1543, #1554, #1591). - Run from a vlt, pnpm or npm-linked workspace member,
apply,rollback,apply --checkandvexnow follow the member's npm alias link ("lp": "npm:left-pad@1.3.0") into the root store. Before, they reportedpackage_not_installed(#1494, #1500). - Vendoring into a pnpm or yarn berry project edits
package.jsonin place instead of re-printing it. Only thepnpm.overrides, build-approval orresolutionsmembers change; formatting, escapes, number spellings (5.173e28), duplicate keys, line endings and a BOM stay, sovendorthenvendor --revertis byte-exact. A yarn berry revert removes aresolutionstable only when vendoring created it and leaves one the project already had, even empty (#2052). - A yarn berry vendored revert reads
package.jsonbefore it writesyarn.lock. A manifest that doesn't parse (a merge-conflict marker) now fails with "nothing was reverted" and both files untouched, instead of leaving the lock restored whileresolutionsstill pointed at the vendored tarball (#2052). - A vendored pnpm or yarn revert finishes only once nothing resolves
through the artifact. A pnpm revert points dependency references added
after vendoring (a new package or workspace importer) back at the
registry release, and refuses with
vendor_lock_still_wired_revert_blocked, writing nothing, when one can't be restored; before, it deleted the artifact and pnpm failed with "Broken lockfile". Once a dependency was upgraded past the patched version, pnpm and yarn reverts remove the artifact and ledger entry with the newvendor_revert_dependency_moved_onwarning instead of reporting the same drift forever (#2052). - Vendored pnpm no longer corrupts a
packages:entry whosedeprecatedmessage is a block scalar, or one with a peer dependency namedresolution. The rebuilt entry is read back before anything is written, and a lock that doesn't read back is refused with the project untouched (#2052). - Hosted pnpm keeps a CRLF
pnpm-workspace.yamlCRLF when it addstrustLockfile: true.rollbackrestores the file byte for byte and removes a scaffold hosted mode created in either line ending (#2052). - Reverting two or more packages vendored into one
bun.lockbrestores the lock's exact pre-vendor bytes in any order, also for a ledger 5.0 / 5.1 wrote when onevendor --revertrun reverts them all. Before, the first package's tarball path stayed in the lock while the revert reported success (#2052). - In a
bun.lockbworkspace, vendoring copies a tarball only into the members whose dependencies reach the package, instead of into every member's.socket/vendor/npm/<uuid>/.repairasks for the same members, and a revert still removes the copies an earlier release made (#2052). - A Bun vendored revert keeps the artifact and ledger entry
(
vendor_revert_residual_reference) while anybun.lockorbun.lockbentry still installs from it. Vendoring now refuses abun.lockthat lists the key it would rewrite more than once, as a merge that kept both sides leaves it (vendor_lockfile_version_unsupported); before, the revert restored one line and deleted the tarball the other still pointed at (#2052). - A vlt
vendor --revertchecks what it leaves behind. An unrecorded lock edge that still reaches the vendored node leaves the revert alone as drift, and anything else still naming the uuid directory keeps it withvendor_revert_residual_reference, instead of deleting the artifact with exit 0.vendor --revert --dry-runreports the same keeps as the real run. A workspace directory that is a symlink is refused (vendor_lock_entry_unsupported) instead of being written through, and avlt-lock.jsonwhosenodesoredgessection can't be located for certain is refused before any write (vendor_lockfile_version_unsupported) (#2052). - The
yarn_classic_workspace_member_install_riskwarning says that a cold-cache install fails from every workspace member, whether or not it depends on a patched package, and names the install that works from a member directory:yarn install --cwd <workspace root>(#2052). - A broken project file is reported as a broken file. A
package.jsonthat isn't valid JSON now reads "not patched, because this project's own patches for it could not be checked", names the file and the parse error and says to fix it, instead of advising--allow-patch-conflicts. Apackage-lock.jsonthat isn't JSON warnsnpm_lock_invalidand the scan ends "No packages found (see the warning)." instead of advising an install. Both runs still exit 0 (#2052). vexreads a yarn berry lock that holds a YAML complex-key entry (? "<descriptors>", yarn's spelling of a key longer than 1024 characters). Before, the whole lock was rejected as "not a yarn lockfile" and no patch in it could be attested (#2052).
- pnpm workspace globs with brace sets and character classes
(
- PyPI.
- A requirements line pinned through an environment variable
(
six==${SIXV}) is refused byvendorwithpypi_requirement_env_templated, andvendor --checkandvexread it as possibly installing any release, so the patch is no longer attested whilepip install -rcan install the unpatched one (#1873, #1874). - A hosted scan of a Python project with no lockfile the rewriters read now
warns
redirect_pypi_no_lockfile, naming the files looked for and the project's own lock command, instead of passing silently (#638, #1885). - After a hosted rollback, a vendored rollback or
vendor --revertof a requirements-file pin, the reinstall advisory covers pip and names the requirements files to reinstall from (#1953, #1954). - The hosted Pipenv stale-install warning ends with the same runnable remedy as vendored mode and never prints an option-like package name into a shell command (#1878, #1883).
- A
poetry.lockin an old format (0, 1.0, 1.1) keeps its "written by Poetry < 1.4" warnings and refusals even when a hand-merged header claims a newer Poetry (#1847, #1865). - Hosted
rollback,removeandscanrestore the pins hosted mode writes into uv 0.2[[distribution]]locks (uv < 0.2.35) instead of refusing them (#2014). - Vendored dry runs and rollout planning apply the PyPI pre-build
refusals the wet run gives (
pypi_uv_no_sources,pypi_no_requirements, symlinked script andpylock.<name>.tomlfiles), so a refused patch shows as would-refuse and no longer takes a--max-new-patchesslot (#891, #1813, #1908, #1930). - A pip project with both
requirements.txtand thepylock.tomlpip lockwrote from it now has the exactrequirements.txtpin (and in-root-rincludes) wired to the vendored wheel too, so a hosted -> vendored switch no longer leavespip install -runpatched. A fresh wiring whererequirements.txtpins the package with extras is refused (pypi_extras_unsupported), and a pin only in an out-of-root-rinclude refuses withpypi_requirements_outside_rootand blocksvendor --check/vex(#1758, #1759, #1794, #1796, #1851, #1853). - A vendored requirements line whose
# socket-patch vendor:comment was respaced, extended or stripped still reverts, and a re-run no longer appends a duplicate line (#977, #1825). - A pip marker split whose branches live in different
-rfiles (six==1.16.0 ; python_version < "3.12"here,six==1.17.0 ; ...there) vendors instead of failing with "not pinned" (#1104, #1703). - When a pip file reads an edited requirements file as constraints
(
-c requirements.txt), hosted and vendored runs warnredirect_requirements_constraints_link/vendor_requirements_constraints_link: pip 20.3-21.0 reject link pins in constraints (#740, #1726). - Vendored Pipenv warns
pypi_pipenv_hash_export_unsupportedwhenpipenv requirements --hashwould drop the vendored wheel's hash and make pip refuse the whole install (#981, #1731). - Vendored PDM projects with a pre-2.11
pdm.lockwarnpypi_pdm_stale_installwith the right reinstall remedy when the env already holds the unpatched release (#641, #1727). - A Pipfile
[pipenv] venv_in_project = truewith no./.venvno longer hides theWORKON_HOMEvenv older Pipenv uses (#842, #1729); a lockless uv project with a Poetry plugin table no longer skips itsUV_PROJECT_ENVIRONMENT(#1916, #1922). scan --mode vendored/vendorover a hosted pin at an older patch now vendors the newer patch instead of exiting 1 with "no installed package found on disk" (#1105, #1713).- Hosted mode refuses pins it can't restore or that uv would bypass: a
uv project with
dynamic = ["dependencies"](redirect_uv_metadata_unsupported) (#639, #1709), and a Hatch env using the uv installer whose[tool.uv.sources]shadows the pin (redirect_hatch_unsupported) (#673, #1706). Vendored mode refuses a Hatch 1.16+ workspace member (pypi_hatch_unsupported) instead of breaking the workspace root env (#505, #1724). - Agent
applyandrollbackdelete the__pycache__bytecode compiled from each.pythey rewrite, so a same-size patch in the same second as the install no longer keeps the old code running; a.pycthat can't be deleted warnsstale_bytecode_not_removed(#819, #1722). - uv installs a package covered by
no-binaryorno-binary-package(in the uv settings, or on theuv pip compileline in a pylock header) only from an sdist, and every Socket patch is a wheel. Hosted and vendored scans now refuse such packages before writing. Before, the nextuv sync/uv pip syncfailed. Vendored mode uses the newpypi_uv_no_binary, and hosted mode its uv unsupported warnings (#1603, #1649, #1672). - uv settings inherited from a parent directory are honoured. A uv
project with no
[tool.uv]of its own, below a parentuv.tomlor[tool.uv]whose settings the wiring would hide, is refused. Vendored mode uses the newpypi_uv_ancestor_settingsand hosted moderedirect_uv_project_unsupported. A PEP 723 script below a parent that setsno-sourcesis refused, where before it was wired and exited 0 (#1501, #1504, #1545, #1566). - Hosted uv
rollbackandremoverefuse to restore a lock entry whose files uv filtered, instead of writing back every file of the release. Filters include a per-packageexclude-newer-packagecutoff, and auv pip compilepylock built with--no-build,--only-binary,--no-binary, anexclude-newercutoff, or flags it doesn't record (#1604, #1613, #1614). - Hosted uv scans warn
redirect_uv_override_requires_uv_0_5_6/redirect_uv_constraints_require_uv_0_5_6when they wire overrides or repoint[manifest]constraints that old uv ignores, as vendored mode already did (#1569, #1609). - Hosted uv
rollbackdrops the bare==<version>[manifest] overridesrecord that uv before 0.5.6 left for the hosted pin (#1525). - Reverting one of several vendored packages in a
pip lockpylock.tomlkeeps its wheel hashes table under its own package. Before, the table landed after the next[[packages]]header and pip refused the lock (#1623, #1625). - Vendoring and reverting a
requirements.txtthat mixes CRLF and LF lines keeps each line's own ending, sovendor --revertandremoverestore the file byte for byte (#1567, #1612). - A vendored PyPI entry wired across flavors (for example
requirements.txt, then apylock.toml) is reverted record by record through each owning backend. Before,vendor --revert,removeand the takeover exited 0 and restored nothing (#1524, #1526). - When Poetry re-locks a vendored unit, for example with another Poetry
version,
vendor --revert,rollback,removeand the takeover restore it while it still carries exactly the wiring socket-patch wrote. Before, they reported drift forever (#1615, #1662). - A
source = "pypi"pin on a patched package over a lock-version 1.0 or 1.1poetry.lockis refused by hosted, vendored and the takeover. Before, it was rewired with exit 0, and every Poetry 1.3+ then failed to install it (#1503, #1506). - Pipenv locks whose categories pin different versions of a package
are patched: only the categories that pin the patched release are
rewired. Before, hosted mode switched nothing and vendored mode
failed with
pypi_pipenv_version_mismatch(#1616, #1617). - The Pipenv stale base-interpreter remedy exports comma-joined lock
categories (
default,docs). Before, Pipenv 2022.12–2023.2.4 read the export as empty, so pip did nothing (#1507, #1508). vexandvendor --checktreat morerequirements.txtandPipfile.locksources as installing the upstream release, which contests a Socket wiring in another lock. These are any exact pin spelling (===1.2.3,==1.2.3.0,==v1.2.3), bare wheel or sdist paths (wheels/six-1.16.0-py3-none-any.whl), direct URL and path references, and editable or#egg=VCS lines. Such a wiring now getspatched_ref_unattributableinstead of an attestation (#1549, #1562, #1587, #1599, #1602, #1632).- The VEX product taken from
pyproject.tomlnow uses the PEP 503 name and PEP 440 version that SBOMs record (my-service@1.0.0rc1, notMy_Service@1.0.0-RC1). Before, consumers silently ignored every statement. Poetry 2 ([project]name,[tool.poetry]version) and PDM 0.x ([tool.pdm]) layouts are detected instead of failing withproduct_undetected(#642, #1527, #1529, #1547, #1577, #1589, #1653). - Hosted
rollbackrestores auv.lockwhose only registry package is the patched one (a project with one dependency) instead of failing with "no sibling registry package". The entry is rebuilt on PyPI's default index with the fields uv writes at the lock'srevision, and the run warnsupstream_uv_entry_rebuilt. It still refuses when the revision is newer than 3 or uv is configured with a package index (#2052). - A patched wheel's
Requires-Dist/Provides-Extraheaders reachuv.lockonly as TOML strings. A header holding a double quote could add a key to the lock or leave it unparseable while vendored uv exited 0. A header a lock entry can't hold now refuses the wheel before any file is written (pypi_uv_metadata_unsupported;pypi_lock_parse_failedfor a vendored script lock; hosted mode skips withpython_metadata_unavailable). That includes the direct references (name @ url) vendored mode used to wire while leaving a lockuv lock --checkcalls stale (#2052). - Vendored uv keeps each line's own ending in a
pyproject.tomlthat mixes CRLF and LF, instead of rewriting the file all-LF, andvendor --revertrestores it byte for byte (#2052).
- A requirements line pinned through an environment variable
(
- Go.
- In a go 1.16 (unpruned) module where a dependency imports a fork that
the root
go.modreplaces with the patched module,apply,apply --check, hosted mode andvexread the module graph to tell whether the patch is really built, instead of reporting it applied whilego buildlinked the unpatched code (#1840, #1843). - In a go 1.16-or-older module where a downgrade left the old version's
go.sumlines behind,apply,apply --check, hosted mode andvexuse the version the module graph selects instead of the highestgo.sumline. When the module cache can't answer, they fail closed:applyrefuses with a remedy,apply --checkreports drift, and the patch is not pinned or attested (#1862, #1893). vexreads thego.workthat governs the project (GOWORK, else the nearest one at or above it, with members anywhere), as hosted and vendored modes already did, and no longer attests a Socket replace the workspace keeps out of the build (go_workspace_override). Such a replace stays visible tolist,rollbackandremove, and a rootgo.workreplace thatgonever reads no longer withholds the rootgo.mod's copy of it (#2011, #2031).- The in-memory hosted engine judges a Go workspace from the project's
own files instead of probing the process's working directory, so a
root module the
go.workdoes notuseis no longer refused for a missinggo.mod(or answered from the server's) (#2031). - Go workspaces are read as go reads them:
GOWORK=autoisn't a file name, a relativeGOWORKisn't in effect, the built version comes from all members' requirements, and go's replace precedence applies; an unreadable membergo.modmeans nothing is attested. Hosted scans checkgo.workbefore writing and skip a replace it would override, ignore or reject (redirect_golang_workspace_conflict), androllbackrestores hosted pins ingo.work,go.work.sumand membergo.modfiles (#1763). - When another
go.workmember requires a newer version,applyandvendorrefuse,apply --checkfails,vexomits the patch (go_version_not_selected) and hosted refuses the pin (redirect_golang_version_mismatch), removing one written earlier (#1751, #1752, #1753, #1755). - After
go mod tidydrops the Socket module's zip line,vexstill recognises a hosted pin from its/go.modgo.sum line when the module graph selects it, and a hosted re-run restores the zip line (#1748). - A
-modfileinGOFLAGSnaming another file is treated like ago.workoverride: nothing is written,apply --checkandvendor --checkfail (go_workspace_replace_ineffective), andvexomits the patches (go_workspace_override) (#1799, #1800). WithGOWORKnaming a sibling file, the replace no longer goes into the unread./go.work(#1810, #1812). - A module the build uses only as the target of another module's
replace is refused by
applyandvendor, flagged byapply --check, omitted byvex, and refused by hosted mode (redirect_golang_not_in_module_graph) (#1823, #1824). - Undoing a Go patch (
vendor --revert,rollback,remove, including in ago.workmember) while a committedvendor/modules.txtstill records the replace warnsgo_vendor_modules_txt_out_of_syncnaminggo mod vendor/go work vendor, andvendor --checkfails on it (#1766, #1767, #1777, #1778). - A global
applywarnsgo_installed_tool_stalefor eachgo installbinary built from a patched module before the patch, naming the rebuild command, andvex -gomits the patch until it is rebuilt (#422, #1717). GOMODCACHE/GOPATHare resolved as go does (firstGOPATHentry only, relative or~entries refused), so the crawler no longer patches a module cache go never reads (#1834, #1876).- Go delivery hardening:
vexandapply --checkcheck thevendor/tree go selects before attesting; upstream go.sum lines are kept and restored on rollback aftergo mod tidy, conflicting checksums are refused, and after the module cache is cleared a re-apply reuses the verified project-local copy and repairs a missing rootgo.modmirror (#1698).
- In a go 1.16 (unpruned) module where a dependency imports a fork that
the root
- Cargo.
vexreads workspace member manifests: a Cargo pin that another root or member declaration resolves from crates.io (or another source) is withheld (vex_cargo_unpinned_declaration), as is a vendored copy a member's other registry, git source or stale pin bypasses (vex_cargo_patch_bypassed);[patch]entries cargo shadows or canonicalizes are weighed the way cargo does (#2012).- Hosted Cargo pins dotted-key (
serde.version = "1.0.0"), literal-string (serde = '1.0.0') and multi-line inline-table dependencies instead of refusing them withredirect_cargo_toml_dep_unrewritable; rollback restores the manifest byte for byte (#757, #1735, #1743). - Agent
applyandvexignore a registry-cache copy of a crate thatCargo.locktakes from git, a[patch]path or a[replace], which cargo never builds (#506, #1715). - Workspace member globs with character classes (
crates/[ab]*) expand as cargo's glob crate does (#1833, #1866). - With
HOMEandCARGO_HOMEunset, the cargo home is found from the passwd entry as cargo does (#1135, #1779), and the build-cache cleaner reads the.cargo/configchain as cargo does, including from a member or a relative--cwd(#1832, #1849). - Hosted
scanrefuses Cargo patches withredirect_cargo_old_cargowhenrust-versionorrust-toolchain[.toml]declares a cargo older than 1.68, which can't use sparse registries, and points to--mode vendored. Before, it reported success and every build failed.rust-version.workspace = trueis followed to[workspace.package], so vendored mode no longer warns that a standard workspace declares norust-version(#651, #653, #1429). rollbackandremoveof a hosted Cargo pin no longer panic (exit 101) on aCargo.lockpackage whose name is empty or not ASCII, or on a v1 lock that lists the hosted block twice. A pin with such a block is refused whole ("is not a crates.io crate name") before any lookup, with no file changed (#2052).
- JVM.
- A Maven 4 root that builds its subdirectories implicitly (model 4.1.0,
pompackaging, no<subprojects>) is treated as a reactor by hosted mode,vexand the Maven cache scope, not only by vendored mode, so a root-only pin is no longer attested while a module keeps its own version (#1809, #1886). - Hosted Maven keeps every line of
.mvn/checksums/checksums.sha256it doesn't own (single-space or*sha256sum lines, tabs, comments) and the file's line endings, instead of silently dropping them (#2016). - Vendored Maven
vexno longer attests a build that may still use the unpatched jar: an executable classifier copy of the patched artifact, or a suffixed version left only in a plugin, profile or unused property, now withholds attestation (#2006). - Vendored Gradle withholds attestation when a
gradle.lockfilelocks the patched artifact at another version (gradle_lock_overrides_vendored) or a customlockFilelocation is used (gradle_custom_lock_file) (#2004). - sbt:
vexno longer attests a hosted pin that a build reassignment ofdependencyOverridesorresolvers,sbt.override.build.repos=true, or newer resolution evidence shows the build doesn't apply (vex_sbt_pin_bypassed) (#2030), nor a vendored pin without current sbt resolution evidence or with its vendored tree missing (vex_sbt_resolution_unverified,vex_sbt_vendored_tree_missing) (#2008). vexand hosted mode weigh profiles, properties and coordinates the way Maven resolves them: a<profiles>declaration, profile<properties>,.mvn/maven.configuser properties, nested${a}/${b}properties, or a profile that redefines a groupId/artifactId property now withhold attestation (vex_maven_profile_unpatched), and hosted writes no pin a profile version or an executable classifier copy bypasses (redirect_maven_profile_dependency_unpatched,redirect_maven_classifier_unsupported) (#2005).${property}groupId / artifactId are resolved: hosted pins the real declaration and refuses an undefined one (redirect_maven_coordinate_unresolved), and the vendored planner, check and revert match interpolated coordinates (#655, #683, #1773, #1788).- Vendored reactors leave a root unpinned when a module's version is an
undefined or profile-moved
${property}(#459, #513, #1787), and a plan that wires nothing is refused (vendor_jvm_shape_unsupported, reasonnothing_wired) instead of exiting 0 (#513, #1826). .mvn/maven.configis read one argument per line as Maven 3.9+ does: commented lines are skipped and--define=k=v/-D k=vcount (#535, #550, #1754).- Vendoring a Maven 4 root covers its implicit subprojects (#622, #1807);
vexwithholds a reactor where a module is left at the base version (#584, #1802); a parent<relativePath>naming a directory no longer failsvexandvendor --check(#534, #1789). - Agent mode and
-gfind the local repository as Maven does (MAVEN_REPO_LOCAL,-Dmaven.repo.local,<localRepository>in settings, thenuser.home), not$M2_HOME/repository(#423, #1801). - A stray file in a vendored Gradle or Maven tree (such as
.DS_Store) counts as drift:vendordeletes it andrepairremoves it offline, instead of reportingalready_vendoredwhile the build fails (#656, #1819). - A vendored Maven pom resolves what its upstream pom does. When a parent
pom manages siblings at
${project.version}(log4j up to 2.17, logback), the suffixed pom resolved them at<base>-socket.<hex8>, which nothing serves:vendorandvendor --checkreported success and the build failed. The vendored pom now declares what it inherits at the base version, betweensocket-patch:inheritedmarkers.vendor --checknow fails on a tree 5.0 / 5.1 wrote with that defect when a local Maven or Gradle cache holds the parent poms, naming the dependency and the parent, andvendorrewrites it. Shapes the pom can't express refuse withinherited_version_unresolvable, and a first vendoring whose parent poms can't be read withparent_unavailable. A parent or BOM that only a corporate repository holds is read from the local repository instead of leaving the root unpinned (#2052). - Vendoring keeps the bytes of a
.mvn/maven.configthat is not UTF-8 (a Latin-1-Duser.name=José) instead of rewriting them as U+FFFD, and the revert restores the file instead of leaving the added lines behind (#2052). - A Gradle module declared with a computed classifier
(
${osdetector.classifier}) is refused: by vendored mode with the newgradle_classifier_computed, by hosted mode withredirect_gradle_classifier_declared. Before,vendorandvendor --checkreported success while Gradle could no longer resolve the classifier jar (#2052). vendor --revertfinds itsgradle/verification-metadata.xmlentry after the file was re-serialised (./gradlew --write-verification-metadata) and puts the original entry back; before, it reported success and left the patched jar's sha256 in place. When the patched hash sits where no recorded shape applies, nothing is reverted andvendor_lock_entry_driftednames the entry (#2052).- In a
settings.gradle.kts, vendoring createspluginManagement {}after the file annotations (@file:Suppress(…)) instead of above them, where the script stopped compiling (#2052). - The Gradle and Maven wrapper version is read from the
distributionUrl = …anddistributionUrl: …spellings too, so a Gradle 6.7 wrapper no longer passes vendored mode's 6.8 gate and a Maven 3.9.9 wrapper no longer reads as unknown (#2052).
- A Maven 4 root that builds its subdirectories implicitly (model 4.1.0,
- NuGet.
- When a
packages.lock.jsonlocks a patched package at a version the writers refuse to wire (another target framework or project),vexno longer attests that version (vex_nuget_lock_other_version);1.0and1.0.0count as one version (#2007). - Agent mode patches the packages folder the project restores into
(
globalPackagesFolder,RestorePackagesPath,repositoryPath,obj/project.assets.jsonfolders) instead of the default~/.nuget/packages(#397, #398, #1769), and-gruns leave a vendored project's own install alone (vendored_ownership_retained) (#489, #1790). rollbackno longer panics on a package id with non-ASCII letters (Ünicode.Pkg) whennuget.configmaps package sources by prefix pattern (#2052).
- When a
- Composer.
- A
composer.lockwith a UTF-8 BOM is read everywhere, and vendor then--revertkeeps the BOM byte for byte; a revert no longer deletes a vendored artifact such a lock still installs from (#2017). - On Composer 1, packages
composer/installersplaces elsewhere (WordPress plugins, Drupal modules,installer-paths) are found (#463, #1723). - Hosted
scanandgettake over a vendored Composer package by reverting it first, and leave an unledgered vendored entry alone withredirect_composer_vendored_entry(#536, #1725).
- A
- Vendoring.
- Vendored dry runs leave already-vendored patches out of the "Would download and vendor" headline and list them as skipped, matching the wet run and the JSON (#1850, #1856).
- Run from a workspace member with no lockfile of its own, a vendored
scan,getorvendornames the lock that installs the member and where to run from (#1904, #1913). - npm package-lock packages installed from a URL or git spec, and workspace
members, are refused before their patch is downloaded; dry runs preview
them as refused and they no longer hold a
--max-new-patchesrollout slot (#1976, #1977). - In a vlt project,
vendorno longer requests service downloads for packages it then refuses (#1909, #1947). - Every vendored entry point (
scanpreviews and rollout plans, the download phase,get, andvendor) applies the same refusals before any download: a v1package-lock.jsonproject, or a hosted pnpm pin whose takeover would be refused, is refused before its patch is fetched;get <uuid> --mode vendoredreports every such refusal and writes nothing; andvendorrefuses before restoring a hosted pin instead of restoring it and rolling back. Codes and details are unchanged (#2015). - A vendored revert keeps an artifact the lock still references through
an escaped path spelling (
\/in an oldercomposer.lock, Windows backslashes) instead of deleting it (#2022). - The committed vendor ledger can only undo vendoring's own edits. A
tampered
.socket/vendor/state.json, which a pull request can carry, could makevendor --revert,remove,rollback,scan --pruneor a takeover write.git/config, a CI workflow or any root file, with exit 0. A record may now name only the files its backend edits, never a path under.gitor through a symlink, and may put back only the pre-vendor form of the package's own entry. A record that doesn't fit fails that package's revert (exit 1) with nothing written for it, and the other packages still revert.nuget.configsnapshots are recorded without credentials (ones an earlier release recorded are dropped at the next save but stay in git history, so rotate them), and vendored gems refuse agemline whose positional argument is computed (gemfile_declaration_not_editable), since it couldn't be reverted (#2052). - Finishing an interrupted vendored commit can no longer write under
.git, and is never silent. A checkout can carry a hand-written.socket/vendor/.commit-journal.json; one that names a path inside a git directory, in any spelling a filesystem folds onto.git, is now set aside with nothing applied, like one naming an absolute or..path. A replay prints the files it rolled forward on stderr and adds avendor_commit_recovered(orvendor_commit_set_aside) warning under--json. A--dry-runofapply,rollback,remove,repairorvendorno longer replays a pending journal: it exits 1 with the newvendor_commit_pendingerror, where it used to finish the commit and then preview (#2052).
- General.
- Workspace runs never read or rewrite project files outside the
enclosing git repository root. Ancestor workspace roots, settings
files,
go.work, NuGet parent configs,.cargo/configand out-of-tree requirements above the root are not read: writers refuse (newredirect_cargo_config_outside_root,redirect_nuget_inherited_config_outside_root,pypi_requirements_outside_root) andvexwithholds. Vendored writers andvendor --revertonly claim.socket/vendor/paths anchored at this project, so a sibling checkout's wiring is never rewritten. Global runs (-g,--global-prefix) and projects outside a git checkout are unchanged (#2025). - The nested-project remedy quotes its path for the shell it prints for,
including Windows and
%(#1859, #1884). - Patch and vendor download errors read the same on every path (size
limit, HTTP status with a bounded body excerpt), and artifact downloads
log under
--debug(#1869, #1870). - Vendored re-runs in every ecosystem keep the older vendored patch
(
vendor_prebuilt_pending/vendor_prebuilt_unavailable) while a newer one has no prebuilt artifact, instead of exiting 1 (#1235, #1707). apply,apply --check,vendor,repair,removeandrollbackrefuse withmanifest_unreadablewhen the manifest path can't be read (a.socketfile, a symlink loop, a directory), instead of treating it as no manifest, and scan's GC passes warn (#998, #1063, #1123, #1776).vendor,vendor --revertandvendor --checkrun from an npm-family workspace member with no lock of its own refuse withvendor_lockfile_missing(exit 1) and name the workspace root to run from, instead of exiting 0 having checked nothing (#1921, #1928).- An unscoped
rollbackover lockfile wiring the vendor ledger doesn't own (a git merge kept one side ofstate.json) refuses withvendor_ledger_missingand writes nothing (#991, #1739). rollbackin the wrong scope keeps a record whose copy in the other scope is still patched and warnspatched_in_other_scopeinstead of dropping it (#450, #1714).- An interrupted
vendoreject (Ctrl-C, CI cancel) leaves the project hosted, or rolls forward on the next run, instead of leaving the lock on the unpatched registry tarball (#1782, #1783). - When the public proxy rejects a batch search, the per-package
fallback honours the proxy cap and
SOCKET_API_CONCURRENCYinstead of sending 10 requests at once (#614, #1817). vexno longer attests a pre-v5 ledger record wired through a hosted-shaped URL on a patch server the run was not given (staging without--patch-server-url, look-alike hosts): it reportsredirect_unwiredwith avex_claim_unwirednote (#2012).- socket-patch's records are no longer lost silently to a
.gitignorerule. Vendored runs, including the hosted-to-vendored eject, refuse before writing withvendor_ledger_gitignoredwhen git would ignore.socket/vendor/state.json. They refuse withvendor_artifact_gitignoredwhen a.socket/or.socket/vendor/rule hides a gem, PyPI, Cargo, Go or Composer artifact. Agentscanandgetwarnmanifest_gitignoredfor an ignored.socket/manifest.json. Files git already tracks don't count (#1638, #1640). - Vendored artifacts now survive
.gitignoreand Git LFS rules. The npm-family uuid.gitattributesalso unsetsfilter,identandworking-tree-encoding, so*.tgz filter=lfsno longer commits LFS pointers. Vendored PyPI wheels get the same.gitignore/.gitattributespair.repairrewrites both files, and it fails withvendor_artifact_gitignoredwhen a rule they can't override hides the artifact (#1541, #1559, #1572, #1581, #1593, #1656). - Lockfile and manifest rewrites (hosted, vendored,
rollback,remove,vendor --revert) keep the file's owner and group. A run as root in a Docker bind mount or under sudo no longer leavespoetry.lockowned by root (#1635, #1636). socket-patch --updateand the update notice now name the package manager that installed the CLI. Bun installs getbun add -g,bun addorbunx. Yarn installs getyarn global add(also for a global folder moved withglobal-folder),yarn add/yarn upgradein yarn 1, oryarn upin yarn 2+. Before, the npm command they got left the copy stale or wrote a straypackage-lock.json(#1621, #1622, #1629, #1680, #1696).- Revert drift warnings print ledger keys plainly (
default:six,requirements.txt:1,<unknown>) instead ofSome("default:six"), and escape control characters from the committedstate.json(#1608, #1679). - Failed upstream registry lookups and downloads now name the transport cause (refused, reset, DNS, TLS, timeout), with URLs redacted. Before, they said only "error sending request" (#1650, #1652).
- A global
scan --prune/--sync(--global,--global-prefix) keeps the manifest entries and blobs of the--cwdproject. Before, it judged the project's manifest against the global crawl alone, emptied it and deleted.socket/blobs/with exit 0. A pruned manifest that can't be written now prunes and sweeps nothing (manifest_write_failed), and the blob, diff and package sweeps ofrepair,remove,rollbackandscan --prunerefuse a symlinked.socket/level (cleanup_failed) instead of deleting the files at the link's target (#2052). - Hosted mode checks each patch reference the API grants before any of it
is written into project files: a canonical uuid,
httpsURLs of plain URL characters, digests of the right length and alphabet, names and versions in their ecosystem's grammar. A grant that fails is skipped with the newredirect_grant_malformed(the detail names the field), and a plainhttpURL anywhere but the--patch-server-urlorigin or a loopback address withredirect_grant_insecure_url; nothing is written for that package and the exit code is unchanged. Before, a quote, newline or markup in a grant could add fields toyarn.lockorGemfile.lock, breakpom.xmlor panic the run (#2052). - Hosted multi-file writes are all or nothing.
rollback,removeand the withdrawn-pin sweep check every file a restore would write before touching the first, and put back the files already written when a later write fails. Before, a symlinkedpom.xmlleft.mvn/maven.configdeleted and the pin live whilerollbackprinted "Restored" and counted it. Such a pin is never reported restored now:rollbackemitshosted_write_failedand exits 1, and--dry-runpreviews that failure with exit 1 where it previewed a success. A hostedscanwhose write fails says "(nothing was changed)" only when every file was put back, and otherwise names each file and the state it is left in (#2052). - Agent
applyandrollbackof a package are all or nothing and keep file modes. Every file is staged with its final mode and owner before the first is renamed into place, and a failure puts the files already replaced back, so a corrupt blob orEACCESon a later file no longer leaves the earlier ones patched, and an interrupt no longer leaves a0755script at0644. Errors name the file (Cannot apply patch: <file> - <cause>). A killed run can leave.socket-stage-*/.socket-orig-*files beside the package's files; the nextapplyorrollbackof that package finishes it and removes them once they are ten minutes old. When a failed write can't put a file back either, its original is kept beside it as.socket-kept-<file>-<uuid>, which no later run removes (#2052). - Commands that rewrite
.socket/manifest.json(remove,rollback,get,scan) keep the top-level and per-record fields this version doesn't know, such as ones a newer CLI wrote, and the file's mode. Before, the first rewrite dropped them and a0600manifest came back0644(#2052). apply,rollback,removeandrepairset up the API client only when they have something to download. A run that finds everything on disk makes no API request, prints no "No SOCKET_API_TOKEN set" notice and reports noapi_auth_fallback. A no-opapplywith a token and no--orgused to wait on the organization lookup first: 12 s against an endpoint that drops packets, 65 s against one that never answers. Anapplywith a Maven patch in scope still sets the client up (#2052).- Telemetry opt-outs that did nothing are honoured:
DO_NOT_TRACK(any value except0,falseand empty) andSOCKET_PATCH_TELEMETRY_DISABLED, the v3/v4 name that v5 had dropped, now turn telemetry off likeSOCKET_TELEMETRY_DISABLED. README anddocs/configuration.mdgain a Telemetry section listing every event, field and switch, and the--no-telemetryhelp no longer calls the events anonymous (#2052). - A closed output pipe no longer cuts a command short.
apply | head -1orrollback --yes | trueused to die of SIGPIPE (exit 141) at the line being printed, with.socket/apply.lockleft behind and, forrollbackandremove, possibly between the restores and the manifest cleanup. The command now stops printing, finishes its work, releases the lock and exits with its own code; a read-only command piped intoheadexits 0 (#2052). - Staging entries an interrupted run leaves under
.socket/are swept: every lock-taking command except a dry run removes.socket-stage-*entries older than an hour.applynow stages the blobs it downloads in.socket/.socket-stage-apply-blobs-*, removed when the run ends, instead of the system temp directory, so it no longer needs a usable$TMPDIRand a killed run no longer leaves a copy of the blob cache there (#2052). apply --forcenames the patch files it skipped because they are missing on disk (newpatch_files_skippedwarning), and a re-run that writes nothing reportsalready_patchedinstead of anappliedevent with no files (#2052).apply,apply --checkandlist --jsonwarnvendor_state_unreadablewhen.socket/vendor/state.jsoncan't be read, instead of silently treating vendored packages as in-place patches or leaving them out of asuccesslisting. Exit codes are unchanged (#2052).gem envand thepython3probes run from--cwdfor a project and from the home directory under--global, not from wherever the CLI was started. A version manager's shim (rbenv, pyenv, asdf, mise) therefore picks the project's interpreter, and the directory the shell is in no longer steers a global scan (#2052).removeandrollbackno longer promise a later restore for a patch whose package isn't installed. The warning and therollback_not_installedevent now say that nothing was restored, that the record is gone, and that reinstalling the package is the remedy (#2052).--helpwraps to the terminal (100 columns at most) instead of running lines of up to 564 characters, and the root help defines hosted, vendored and agent mode. The npm launcher explains a missing platform package (npm install --omit=optional) and how to get the binary instead of printingspawnSync … ENOENT, and lists the supported platforms on an unsupported one (#2052).- In a project with no patch of any mode,
rollback,repair,removeandvexsay "No patches in this project; nothing to roll back." (or to repair, to remove, to attest) instead of "Manifest not found at ./.socket/manifest.json", a file a hosted project never has. Exit codes and the--jsonmanifest_not_founderror are unchanged (#2052). - When
vexcan't detect a product, the error lists what detection reads, names the project files that lack a usable name and version, and ends with the run's own flag and a value to paste (--product pkg:generic/<directory name>, or--vex-producton an embedded run). It still exits 2 withproduct_undetected(#2052). --product/--vex-productand--doc-id/--vex-doc-idvalues are emitted trimmed; surrounding whitespace used to land in the document's@id. A document id with no IRI scheme is honoured with a newdoc_id_not_iriwarning (#2052).- A report-only
scan(--prune,--global,--global-prefix) previews as the report it is: its--dry-runno longer prints "Would download and apply N patches" for a run that applies nothing, and the listing is headedPatches available:(#2052). - The hosted summary counts only the packages the run switched and gives
the others a sentence of their own: "Switched 3 packages to hosted
patches; rewrote 2 files. 1 package was already on hosted patches."
--jsonis unchanged (#2052). - Crafted project files no longer hang, exhaust memory or crash a run: a
workspace glob with many
*,**, brace sets or extglob groups (a pattern past the bounds fails closed and lists every directory); a Hatchmatrix(read up to 1,024 combinations); a chain of Maven${property}references (a value past the bound reads as unresolved); a Gradle string nested more than 64 templates deep (the script is refused as unparseable, wherescanaborted with exit 134); a small craftedbun.lockbthat madevendorallocate gigabytes; and a tornbun.lockbsnapshot in the vendor ledger, whichvendor --revertnow leaves alone as drift instead of panicking (#2052). - The
git checkoutremedy printed when a hosted pin can't be restored names every file hosted mode edited:GemfilewithGemfile.lock, and thepyproject.tomlor script paired with auv.lock(#2052).
- Workspace runs never read or rewrite project files outside the
enclosing git repository root. Ancestor workspace roots, settings
files,
- npm family.
- Bun scans parse each
bun.lockonce instead of three times, and the vendored preflight no longer parses it once per patched package (#1606, #1700). - Hosted vlt scans index
vlt-lock.jsononce instead of re-parsing it per patched package: about 47% (hosted) and 50% (rescan) faster on the bench (#1570, #1691). - Rescans of already-patched bun and yarn berry projects drop repeated per-package lookups that had made them about 20% slower since early October (#1606, #1607, #1693).
- Reverting a vendored pnpm package that much of the lock depends on reads the lock section once instead of once per dependent. On a 60,000-package lock a revert took 1 min 51 s against 0.77 s to vendor; a revert over 20,000 dependents drops from 94 s to about half a second in a debug build (#2052).
- Vendoring into a large
vlt-lock.jsonno longer decodes lock edges for every comparison. It took 20.9 s for 24 packages on a 20,000-node lock; planning one package drops from 2.1 s to 12 ms in a debug build (#2052).
- Bun scans parse each
- PyPI.
- Hosted Poetry and PDM scans parse each lock once instead of five or six times (about 31–34% fewer instructions on the bench). VEX and the lock inventory read Python locks with a read-only TOML parse instead of copying them (#1473, #1624, #1684, #1486).
- Go.
- Hosted Go rescans parse
go.sumonce per line instead of re-tokenizing it for every patched dependency, recovering the ~30% slowdown on largego.sumfiles (#1424, #1746).
- Hosted Go rescans parse
- JVM.
- A Maven pom whose declarations share
${property}texts is interpolated once per profile combination instead of once per declaration. A 1,000-declaration pom that cost 263 million property lookups, 28 s insocket-patch list, now takes under 600,000 units of work (#2052).
- A Maven pom whose declarations share
- General.
- The project-patch check and purl canonicalization are cheaper on large Yarn Berry locks, about 3% fewer instructions per rescan (#1607, #1701).
- Hosted rescans no longer ask the patch API's reference endpoint twice about the same pinned patches (#1606, #1607, #1695).
- Agent
applyandrollbackflush the device cache twice per package instead of twice per file. On a 2,000-file package on macOS,applydrops from 16.4–18.8 s to 1.2–1.6 s androllbackfrom 17.5–18.2 s to 2.0–2.8 s (#2052). - Telemetry never delays a command. Events are sent in the background and
the exit waits at most 300 ms for them. With an endpoint that accepts
and never answers,
listdrops from 5.02 s to 0.32 s and prints its output after 0.02 s instead of 5.02 s. An event still in flight after the wait is dropped (#2052).
- Code consolidation with no output change: one lock-rewrite driver for Poetry and PDM (#1898) and module sweeps across the CLI and core (#1855, #1864, #1875, #1884, #1892, #1897, #1903, #1907, #1914, #1920, #1923, #1929, #1939, #1948, #1967, #1975, #1985, #1990).
- Test and CI infrastructure: shared test env guard, docker harness, PTY
runner,
copy_treeand apply fixtures; hermetic children scrub color variables; failing tests are annotated in CI; offline bun.lockb rollback mock and a Windows venv layout in the PyPI fixture (#1891, #1890, #1905, #1944, #1955, #1964, #1972, #1978, #1988, #1994, #2000); release workflow artifact steps move off Node 20 actions (#2027). A design doc and line-count budget plan one lock model for every lockfile reader (#2001), and the Socket-origin classifier moves into one module (#2002). Test fixes: PDM e2e compat pins bounded by release, a Windows-only scala-cli liveness test, NuGet walk tests isolated from the host cache (#2034, #2035, #2037); CI retriessetup-rubyandsetup-denoonce on a release-CDN error (#2041, #2042),lint-ecosystemsinstalls retry release 504s, andvexctlis cached on every OS (#2040, #2039). - Other CI, test, refactor and dependency work with no change in behavior: #1488, #1491, #1495, #1497, #1498, #1499, #1502, #1505, #1510, #1515, #1518, #1519, #1522, #1528, #1530, #1532, #1535, #1536, #1539, #1544, #1546, #1550, #1552, #1553, #1556, #1560, #1563, #1565, #1568, #1580, #1583, #1585, #1590, #1620, #1634, #1641, #1642, #1643, #1647, #1654, #1659, #1663, #1666, #1669, #1673, #1674, #1682, #1686, #1692, #1697, #1704, #1708, #1710, #1716, #1730, #1734, #1738, #1740, #1742, #1744, #1747, #1760, #1765, #1771, #1775, #1780, #1786, #1797, #1803, #1806, #1814, #1816, #1820, #1828, #1842, #1852, #1854, #1882, #1894, #1895, #1896, #1946, #1961, #2028, #2046.
- Tests for the hosted gem, vendored Maven and pnpm revert fixes: real-Bundler and real-Maven (log4j-core) capstones, a byte-exact pnpm revert in the ledger schema e2e, and expectations reconciled across the merged fixes (#2052).
- 5.0.1 is the first v5 release on npm and crates.io. v5.0.0 was tagged
and its standalone binaries were published on GitHub, but its npm and
crates.io publishes failed on a trusted-publisher mismatch, so
@socketsecurity/socket-patchand the Cargo crates stayed on 4.0.0. npm now publishes from the Release workflow its trusted publisher names. Upgrading from 4.x through npm or Cargo goes straight to 5.0.1; read the v5 migration guide and the 5.0.0 notes below first. - Hosted
rollback/removebehind a registry mirror that passes npmjs tarball URLs through keep Yarn Berry's::__archiveUrl=binding and pnpm'starball:field, so the next cold install no longer 404s (#1465). - A Yarn Berry vendored revert rebuilds the restored
yarn.lockentry from the live lock instead of the one recorded at vendor time, soyarn install --immutablekeeps passing after later lock changes (#759). - Hosted pnpm reads
npm_config_*/pnpm_config_*environment settings only where the project's pnpm major reads them, so an npm-erapackage-lock=falseundernpxno longer blocks pinning a lock pnpm 11/12 installs from (#1460). - A yarn classic pin written by v4 for a patch that adds a dependency the
lock never locked is no longer confirmed on rescan
(
redirect_yarn_classic_dep_manifest_unlocked), andvexomits hosted npm-family copies whose new dependencies aren't installed (dependency_not_installed) (#1462). - uv: after
uv removeor an upgrade of a hosted-pinned transitive package's parent,listwarnshosted_resolution_orphanedandrollback/removeretire the leftover override wiring instead of failing withhosted_wiring_contested(#1445). Reverting a vendored PEP 723 script lock restores the specifier the script declares now, not the one recorded at vendor time (#869). - Poetry 1.3 locks are refused for a
source = "pypi"dependency, like 1.4–2.2 locks, since Poetry 1.3 can't solve the rewired lock (#1447). - The stale-install remedy for a Pipenv
--site-packagesvenv importing a base-interpreter copy installs over it inside the venv, instead of an uninstall pip refuses (#1450). vendor --checkexplains a missing or changed vendored artifact in plain words, naming the file and therepairstep, in human output and the--jsoneventreason(#1467).- A hosted pin beside a
file:copy of the same release (tarball or directory) no longer passes silently. The registry entry is still pinned, but the in-run--vexno longer attests the packagenot_affected, since npm and pnpm keep installing thefile:copy unpatched. npm now also warns about the directory shape (#1481), and pnpm warnsredirect_copy_unpatchednaming the lock and entry (#935; vendored pnpm still writes its override silently beside such a copy). - Path-scoped
scan --mode agent <member>androllback <member>reach an npm alias dependency ("lp": "npm:left-pad@1.3.0") that pnpm's isolated linker or npm'sinstall-strategy=linkedlinks into the member, instead of reportingsuccesswith the copy unpatched (#1266). - A hosted
scan/getfrom a workspace member reads the root'sworkspacesglobs the way the lock's package manager does (yarn classic ignores!negation, yarn Berry supports extglobs), so it refuses withredirect_workspace_lockfile_elsewhereinstead of reporting success with nothing pinned (#1097). - Bundler: a Gemfile
lockfile "custom.lock"call is honored, so hostedscanpins the lock bundler actually reads instead of a leftoverGemfile.lock(#1477). Hostedrollback/removekeep theDEPENDENCIES!for a gem in the user's ownsource … doblock after an assignment-formif/case/beginconstruct, so frozen installs on Bundler 4.0.19+ keep passing (#1476). - Hosted scans of Hatch, Poetry and PDM projects with many patched
dependencies are much faster: the Hatch files and the
poetry.lock/pdm.lockare rewritten in one pass instead of once per package (#1471, #1473).
- Release binaries build with one codegen unit, about 16% smaller.
- npm publishing runs inline in
release.yml(publish-npm.ymlis removed), and the Release workflow gains annpm-retry-versioninput that re-stages npm from an existing GitHub release.
v5 centers the workflow on scan (hosted patches), vex (OpenVEX attestations),
and vendor (committed patched packages), with list for inspection. See the
v5 migration guide before updating existing automation.
scanandgetdefault to hosted mode for a project that has no patches yet.scannever prompts, and hosted or vendoredgetselects without an interactive menu. Explicit--mode agentretains in-place patching;get --save-onlyand global targeting select agent behavior by default. A mode-less global scan orscan --prunedoes not acquire new patches, though--prunestill cleans up obsolete state.- A bare
scan/getkeeps the mode a project already uses: a vendor ledger selects vendored mode and an agent manifest selects agent mode, so the quick start no longer converts an existing project to hosted in place. Switching modes (in either direction) takes an explicit--mode; a project holding both kinds of state, or vendored packages beside hosted pins, is a usage error (mode_ambiguous, exit 2) that names the packages. Human runs print a note naming the mode kept (#1088, #1437). - Every command's
--jsonoutput is one envelope:command,status,dryRun,events,summary,warnings, pluserror: {code, message}on failure andgcwhen a sweep ran.scan,getandrollbackdrop their own shapes: per-package outcomes (includingapply.patches,download, the nestedvendorenvelope,redirect.patches, rollback'sresults[],rolledBackandhosted.*lists) are events, and the count keys (found,redirected, …) are replaced bysummary, which always equals the event counts. Statuses and actions are camelCase only (new actionrolledBack;would_*previews areverified), warnings are always top-level{code, detail}, and events from the vendored and hosted legs carrydetails.mode. Usage, lock and early errors print a full envelope, andscan --mode agent --jsonno longer prints two documents. A corrupt or unreadable manifest ismanifest_invalid/manifest_unreadableon every command. See the CLI contract (#931). - Artifact garbage collection reports one shape on
repair,remove,rollbackandscan --prune: a top-levelgcobject (removedBlobs,removedDiffArchives,removedPackageArchives,bytesFreed) andsummary.bytesFreed.repair's GC event no longer bumpssummary.removed; rollback counts every leg it undid (#1257, #1066). get,removeandrollbackshare one package-target grammar: a UUID,CVE-…/GHSA-…, a purl, or an exact package name.get <name>no longer fuzzy-matches (get yamlused to patchyaml-ast-parser) and searches every installed version of that name; with no match it reportsnoMatchand suggests close names. A name that reaches several packages (core) is refused with the candidates listed (ambiguous_target).removeandrollbackaccept a bare name or versionless purl, selecting every recorded version. Package identity is case-preserving where the ecosystem is, soremove jsonstreamno longer also removesJSONStream.gethonors--ecosystemsand socket.yml policy on every path, includingget <uuid>, and the bare-UUID shortcut works after global flags (socket-patch --json <uuid>) (#453, #1280, #1292).--download-modeis removed: patch content is always fetched as per-file blobs, and the diff download path is gone (#792).- Hosted mode writes no ledger.
list, VEX, and update discovery read the live dependency references.rollbackandremoverestore upstream registry entries rather than replaying saved edits, and refuse where restoration is unavailable (including offline operation and binarybun.lockb). Legacy hosted ledgers are read only for compatibility and removed by full rollback. rollbackrestores dependencies and removes patch records and unused artifacts.--preserve-state, also available onremove, keeps local state for reuse. Hosted state has no local artifact to preserve.- Vendored scans and targeted gets are manifest-free. The vendor ledger embeds
patch records;
repairno longer reconstructs a missing ledger from lockfiles.vendorcan eject hosted pins when no agent manifest exists. Reverting an ejected package restores upstream dependencies. - Vendored Cargo patches use workspace-root
Cargo.tomlwiring and tagged<version>+socket.<uuid>versions, visible toCARGO_PKG_VERSION. Re-running vendoring or repair migrates older config-file wiring and untagged copies. socket.ymlpolicy now constrains scans. Invalid patch policy fails before requests or writes. Discovered test and fixture projects are excluded by default; literal project targets skip those defaults. Hosted/vendored PATHs outside the repository are rejected.- Automatic patch selection prefers the highest severity, then the most advisories fixed, then publication date. Existing patches change only when the new patch outranks them; tier/UUID tie-breaking alone does not trigger replacement.
setup, its publishing helpers, and the PyPI/RubyGems CLI distributions are removed. Standalone binaries, Cargo, and npm remain supported; Python and Ruby project support is unchanged. Remove old hooks using the migration guide.- Removed
scan --redirect,scan --detached, mode aliaseshost/redirect/vendor, the unimplemented--one-offflags, and the three legacySOCKET_PATCH_*environment aliases listed in the migration guide..socket/packages/archives are no longer consumed; cleanup removes leftovers. liston an empty project exits 0;getusage errors exit 2. Human help and output are grouped by task, with diagnostic codes retained in JSON and verbose output. Hosted JSON identifies lockfiles instead of a ledger; rollback'svendoredresults contain vendor-owned entries only. See the CLI contract for exact schemas.- VEX requires live hosted/vendored wiring and reports corrupt vendor ledgers.
Verified agent patches no longer require a
setuphook for attestation. - The core crate removes setup-related modules, obsolete public helpers, and the
unused
DepOverride::berry_zip_urlfield. Patch references containingberryZipUrlstill parse. - Removed
scan --apply(use--mode agent),scan --vendor(use--mode vendored),get --no-apply(use--save-only), and thedownload(forget) andgc(forrepair) subcommand aliases. Each is now a usage error (exit 2).--syncremains shorthand for--mode agent --prune(#966). SOCKET_FORCEis no longer read.--forceonapply,vendorand--updateis flag-only, so a stale export no longer weakens checks in unrelated commands (#615).- Every
--jsontop-level error is now anerror: {code, message}object; the top-levelerrorCodefield and string-valuederrorare gone.scan,getandrollbackuse{status: "error", error: {code, message}}, and usage errors (exit 2) print the coded error on stdout under--json(#704). --cwd,--global-prefixand--manifest-pathare validated before every command. A path that does not exist is a usage error (exit 2) instead of an empty project that passes silently.getandscanmay still create a missing manifest directory (#1029).apply --checkverifies every in-scope manifest patch against the installed copies, not only Go redirects. An unpatched, tampered or incomplete copy is drift (exit 1,not_applied/hash_mismatch/file_not_found/no_matching_variant); a Yarn PnP tree is refused asapplyrefuses it (#1029).- Agent-mode
applyandrollbackrefuse a patch that would write outside the package's install tree (OutsideInstallTree), for example through a Composer path repository symlinked into first-party source, a flit--symlinkinstall, or a Homebrew/Nix package linked into site-packages. - A patch that creates a new file where other content already exists is now a
content mismatch:
--strictrefuses it, and the default overwrites it with acontent_mismatch_overwrittenwarning instead of overwriting silently. - A symlinked
.socketdirectory is refused for vendored writes and reverts (vendor_dir_symlink_unsupported) and for the hosted/vendored ledgers. Agent-mode manifest and blob state under a linked.socketstill works. Inline patch blobs are verified against their hash before they are stored. - The npm wrapper exits
128 + signal(130 for SIGINT, 143 for SIGTERM) when the binary is killed by a signal, instead of 1. The npm package no longer shipssrc/, the tsconfig or compiled tests. - Hosted mode now wires Gradle builds itself instead of printing a paste-able
exclusiveContentsnippet:scanwrites.socket/gradle/files, settingsapply fromlines, lock entries and verification metadata to commit. The snippet remains only as the fallback after a refusal (redirect_gradle_manual_snippet) (#646). - A Gradle-only build no longer has
~/.m2scanned or patched unless the build or an init script declaresmavenLocal()(or that can't be ruled out). Agent mode now patches every cache copy a JVM build consumes instead of only the first, and fails runs it cannot fully patch (gradle_ro_cache_shadows,gradle_copy_unexpected_bytes, …) (#349, #551). scan --mode agent --jsonandget --jsonreport a patch whose in-place apply failed as afailedevent with its error code, count it insummary.failed, and count only patches that really applied as applied. They used to list it asaddedwithfailed: 0(#424).vendor --checkis stricter: it fails (vendor_check_failed, exit 1) when a lockfile no longer points at a vendored entry in any ecosystem, not just Maven/Gradle, and reports lockfile references to.socket/vendor/that no ledger entry owns asvendor_ledger_missing(#725, #831).- Single-module Maven projects are vendored like reactors: the dependency is
pinned to
<version>-socket.<hex8>and served from a committed.socket/vendor/maven2/tree, with.mvn/maven.configand a fallbacksocket-patch-vendorrepository, replacing the same-version<repository>wiring and.socket/vendor/maven/<uuid>/. A project vendored by an earlier release is refused withvendor_jvm_shape_unsupported(reasonlegacy_maven_root) and nothing is written; runvendor --revert, then vendor again.remove,rollbackand hosted takeover still unwind the old wiring. VEX attests the new pin from.socket/vendor/state.json, so commit it. Without a Maven Wrapper, vendoring warnsvendor_jvm_degraded; severalvendor_maven_*codes andvendor_gradle_unsupportedare retired (see the migration guide) (#973). - A token whose organization cannot be resolved (no
--org,SOCKET_ORG_SLUGor socket-clidefaultOrg, andGET /v0/organizationsfails) no longer queries/v0/orgs/default/…while downloads go to the public proxy. The org is resolved once per run and the whole run uses the public proxy anonymously (free patches only), warning once;--jsonoutput ofscan,get,apply,vendor,vexandrepairreports it asapi_auth_fallbackinwarnings[]. An embedded--vexreuses the run's org instead of resolving it again (#648).
- Vendored Maven reactors, with committed repositories, reversible wiring,
repair, rollback, and VEX. Reactors use suffixed versions;
vendor --checkaudits artifacts and wiring offline;--local-repochecks Maven cache conflicts and--maven-config=noneselects the fallback file repository. Single-modulepom.xmlprojects are vendored the same way, as a reactor of one (#973). - Full Gradle support (6.8+, Groovy and Kotlin DSL; tested on 6.9 through 9.8)
in every mode (#646):
- Discovery reads Gradle's
files-2.1cache andGRADLE_RO_DEP_CACHE, finding the user home the way Gradle does;--global-prefixaccepts a Gradle user home. JSON packages gaininLock, and run warnings gain alevel(#349, #551). - Agent mode patches every hash-dir copy a build consumes, swaps the whole
jar for jar-member patches (#264), keeps
~/.m2.sha1/.md5sidecars in step, and refuses or warns on verification metadata, read-only cache shadowing, stale transform copies and daemon-held jars. - Hosted mode pins the patched
-socket.<hex8>version through an owned settings script that covers transitive, range, dynamic, rich, catalog,buildSrcand included-build requests, with a sha256 tripwire. Rollback and remove restore offline (#347, #348, #396, #511). - Vendored mode supports mixed
pom.xml+ Gradle roots, autocrlf checkouts, classifiers, ranges and pgp-only verification entries, and refuses subprojects (#395, #428, #429, #461, #487, #511, #533). - VEX re-hashes every copy a build may load and withholds on unpatched
derived copies (
vex_gradle_unpatched_copy).
- Discovery reads Gradle's
- sbt, Mill and scala-cli support within the
mavenecosystem (#690):- Agent mode patches Coursier caches (sbt 1.3+, sbt 2, Mill, scala-cli) and Ivy caches (sbt 0.13–1.2), joining Gradle's every-copy patching and VEX.
- Hosted sbt (0.13.18+) writes one generated
socket-patch.sbt, gated on freshsbt updateresults; vendored sbt writessocket-patch-vendor.sbtover a committed suffixed tree. - Vendored scala-cli directory builds get an owned
socket-patch.scalaand a committed Coursier tree (Linux/macOS). Mill and hosted scala-cli get paste-able snippets.
- Hosted Yarn Berry pins dependencies declared as
catalog:/catalog:<name>(#632). - vlt 1.3 locks with Brotli tarball nodes are accepted in hosted and vendored mode (#372).
- Vendored Pipenv projects can move to a newer patch for an already vendored package, including from a venv installed from the older vendored wheel (#769).
- Vendored
requirements.txtsupports marker-split pins (as written byuv pip compile --universal), rewriting only the matching branch (#928). vendor,scan --mode vendoredandget --mode vendoreddry runs warn about symlinked files the real run would refuse to rewrite (#627).socket.ymlpatch policy for paths, ecosystems, packages, severity, and per-run limits.scan --package,--min-severity,--max-new-patches, and--no-socket-ymlsupport targeted and gradual rollout. Already-patched packages retain protection when excluded; updates do not spend the new-patch budget. Disk scans and the in-memory hosted engine share these rules.- Manifest-free VEX discovery from hosted and vendored references, including fresh checkouts. Product inference covers Go, Composer, Maven, NuGet, and RubyGems in addition to existing formats. Embedded VEX supports the same evidence checks.
- vlt support across agent, hosted, and vendored workflows, with native installer coverage and explicit version/layout refusals.
- Native binary
bun.lockbreading and rewriting, alongside textbun.lock, without invoking Bun or converting binary locks to text. - Expanded Python lockfile support for uv, Poetry, PDM, and Pipenv, including lock-only inventory, supported multi-version/marker shapes, and stale-install diagnostics. Unsupported installer formats are refused before writes.
- npm 12 hosted
allow-remoteconfiguration and dual-lock handling, plus pnpm trust-lockfile configuration. Explicit user settings are respected. - Path targeting on scan and rollback, hosted update detection from lockfiles, and configurable API request concurrency.
- Hosted
--jsonoutput reports every granted patch as an event: pinned / would-pin, skipped (with its reason) or unpinned (redirect_unconfirmed), so unwired patches are visible to tooling (#1029). - vlt 1.3.0–1.3.7 are supported (#1087).
- Unknown subcommands get a precise usage error:
setupandunlockname the release that removed them and the replacement,updatepoints at--update, and hidden internal subcommands are no longer suggested (#1043). - Re-vendoring a uv project, a uv script lock /
pylock.toml, or a Hatch project to a newer patch of the same release now works instead of failing withpypi_uv_source_already_exists/pypi_hatch_unsupported;vendor --revertstill restores the original files byte for byte (#742, #650). - Hatch's out-of-tree environments (including Hatch 1.0–1.2 layouts) are
found by agent mode, stale-install checks and VEX. Stale Hatch envs warn
with the
hatch env remove/pruneremedy (pypi_hatch_stale_installin vendored mode) (#335). - Hosted re-pins to a superseding patch replace the old generation's wiring:
Cargo drops the old
[registries.socket-patch-*]block, Go drops stalego.sumlines (go mod tidy -diffstays clean), and Maven re-pins an earlier-socket.<hex8>literal and refreshes a rotated token URL (#864, #682, #266).remove/rollbackof either generation's uuid unwind the live pin too (#999). removeandrollbackaccept any PEP 503 spelling of a PyPI name (typing_extensions,Jinja2,ruamel.yaml) (#1024).- VEX product detection resolves submodules and linked worktrees to their own
origin, honors
GIT_CEILING_DIRECTORIES, stops at the home directory, and warns when it skips a checkout owned by another user. - HTTPS trusts the platform certificate store and
SSL_CERT_FILE/SSL_CERT_DIRin addition to the bundled Mozilla roots, so socket-patch works behind a TLS-inspecting corporate proxy. Certificate verification is never disabled (#1107). - socket-patch reads Go settings saved with
go env -w(GOPRIVATE,GOPROXY,GOMODCACHE, …) the way thegocommand does, so private modules are no longer requested fromproxy.golang.org, mirrors are used and a relocated module cache is found (#344). - Every Go mode warns when a committed
vendor/modules.txtor the consumergo.modneeds a resync to build with the patch (go_vendor_modules_txt_out_of_sync,go_requirements_out_of_sync), naminggo mod vendor/go work vendororgo mod tidy;apply --checkandvendor --checktreat it as drift (#343, #618). - Vendored Pipenv and uv projects also wire an exported
requirements.txt(pipenv requirements,uv export) and Pipenv'spylock.toml(use_pylock) to the same committed wheel, so installs from those files get the patch andvendor --check/vexstop reporting contested wiring (#612, #1368). --updateand the update notice give pnpm installs pnpm commands (pnpm add -g …,pnpm add …, orpnpm update --recursive --latest …at a workspace root) instead of an npm command that does nothing or writes a straypackage-lock.json(#1111).scanandapplywarngem_bundler_plugin_stalewhen Bundler still registers the v4socket-patchplugin at a deleted path, namingbundle plugin uninstall socket-patch(#1295).- Vendored yarn classic workspaces warn
yarn_classic_workspace_member_install_risk: a cold-cache install from a member directory can't fetch the vendored tarball, so install from the workspace root or use hosted mode (#691). - Telemetry records hosted-mode outcomes (
patch_applied/patch_apply_failedwithmode: "hosted"), self-updates (cli_updated/cli_update_failed), the requested and already-wired modes onpatch_scanned, and the ecosystems the crawl actually found.CLI_CONTRACT.mdlists which command sends which event. - docs/ecosystems.md defines v5 support tiers
(Supported, Beta, Legacy, Not supported) per ecosystem and mode. Hosted and
vendored Maven/Gradle/sbt are Beta; binary
bun.lockb, vendored pnpm 7/8 locks and early vlt lock eras are Legacy (#1156).
See ecosystem support and the compatibility guides for format boundaries, integrity limits, and required install commands.
-
Credentials no longer leak to logs, JSON, telemetry, VEX or the patch server. Hosted Composer pins drop the lock entry's
transport-options(andsource), socomposer installno longer sends a private registry's auth headers, client certificates or proxy settings to the patch server (redirect_composer_transport_options_removed) (#399). URL userinfo, grant tokens and secret query values are redacted from warnings,--jsonevents, error messages, debug output and telemetry;patch_fetch_failedsends auuidonly when the target was a UUID; and the VEX product@idnever carries credentials from a git remote. Subcommand--helpno longer prints the value ofSOCKET_API_TOKEN/SOCKET_CLI_API_TOKEN. -
--manifest-pathselects one project for every command.rollback,remove,repair,apply --check,vexand agent-modescan/getused to read (androllback/removeto write) the vendor ledger in--cwdinstead, so one run could unwind two projects (#745). -
repairandscan --prunekeep the original-file blobs of patches still in the manifest, so an offlinerollbackafter a repair works again (#893). -
Human
scan --mode vendored --pruneruns the vendored GC when packages are found but none is patched, as--jsonalready did (#1127). Human and--jsonscanexit 0 alike when the patch API answers with no records, and partial detail-query failures warn in human output too (#1062). -
The
apply --checkdrift remedy repeats the check's own--cwd,--manifest-path,-g/--global-prefixand--ecosystemsflags, instead of a baresocket-patch applythat acts on another tree (#1219). -
Hosted
rollback/remove, takeover and eject look up upstream registry entries a few at a time instead of all at once, so many pins no longer trip registry rate limits orEMFILE(#1220). -
The OpenVEX document is written with stage + rename, so a failed write no longer leaves a truncated document behind (#1144).
-
Vendored Maven, Gradle and NuGet artifacts are protected from
.gitignorerules such as*.jar/*.nupkg, and vendoring is refused when git would still drop them, as npm-family vendoring already was (#1061). -
scan --vexin hosted mode no longer attests an npm patch asnot_affectedwhenpackage-lock.jsonalso lists a bundled copy of the samename@version(inBundle, orbundledin a v1 lock). npm unpacks that copy from its parent's tarball, so it stays unpatched; the run already warnedredirect_npm_bundled_instance_skippedand now leaves the patch out of its attestation, like a standalonevexrun (#325). When apackagesmap exists, stale bundled flags in the legacydependenciesmirror do not suppress an attestation for the actual install tree. -
vexno longer attests an npm or Bun patch asnot_affectedwhile a second entry for the samename@versionin the same lockfile still resolves from the registry (for example a workspace member added after vendoring). That copy installs unpatched, so the patch is now reported as contested.vendor --checkreports the same lockfile entry as drift (#588). -
Global mode (
-g) finds npm, yarn, pnpm, bun, RubyGems and Composer on Windows, where they install as.cmd/.batshims, instead of reporting an empty scan. The yarn and npm-family global lookups no longer run from the scanned project, so a Yarn Berry project'sglobalscript can't run or pick the directory treated as the global install. Composer's global home also falls back to%APPDATA%\Composerand$XDG_CONFIG_HOME/composer. -
Agent-mode PyPI
applypatches every installed copy of a release, not just the first one found. A Pipenv project with both a WORKON_HOME venv and a./.venv, or a global install with the same release in the user site and a system dir, no longer keeps the copy Python imports unpatched whilevexattests it (#529, #501). -
Gem hosted and vendored modes wire only the manifest Bundler loads. A
gems.rbtwin or aBUNDLE_GEMFILEsetting (environment or.bundle/config) no longer leads to an edit of an ignoredGemfilethat reports success and attests an unpatched gem; unsupported layouts are refused before any write (#341, #390). -
Gem modes read Bundler settings in Bundler's own priority. A
BUNDLE_GEMFILEin.bundle/confignow outranks the environment variable, so a dual-boot project with an exportedBUNDLE_GEMFILE=Gemfileis no longer wired through theGemfileBundler ignores (#507). The hosted stale-install guard checks the committed archive in Bundler's configured cache dir (cache_path/BUNDLE_CACHE_PATH) instead of alwaysvendor/cache, so a stale archive there now warns and keeps the same run's VEX from attesting it (#483). Both settings skip.bundle/configunderBUNDLE_IGNORE_CONFIG, as Bundler does. -
npm dependencies installed from git, a URL or
file:are no longer reported patched. npm installs such a dependency from the dependent's spec (github:user/repo,https://…/x.tgz,file:…) and ignores the lock entry'sresolved, sonpm cikept installing the original bytes afterscan --mode hostedorvendorrewired the entry andvexattested it. Both modes now skip such an entry with a loud stays-UNPATCHED warning (redirect_npm_non_registry_entry_skipped/vendor_non_registry_entry_skipped; vendoring refuses withvendor_lock_entry_not_rewritablewhen no registry copy is left), andvexattests nothing for aname@versionwhile such a copy is in the lock (#326). A dependency the project'soverridessend back to a registry version is not one of these: npm installs the override's registry release, so hosted and vendored modes patch it again, andvexattests it (#490). -
Agent mode finds Poetry's virtualenv in more setups. Three cases missed the virtualenv Poetry installed into. Each fell back to the wrong interpreter, skipped the patch as
package_not_installedand still exited 0:- a nameless
package-mode = falseproject (Poetry names its envnon-package-mode-…); - a Poetry 2 project with both
[project] nameand[tool.poetry] name(Poetry uses[project] name); - an explicit
virtualenvs.in-project = falsenext to a stray./.venv.
Every Windows project missed it too, because the cwd hash included the
\\?\prefix that path canonicalization adds (#327, #329). - a nameless
-
Hosted Maven warns
redirect_maven_trusted_checksums_unenforcedwhen.mvn/wrapper/maven-wrapper.propertiespins a Maven older than 3.9.4. Maven 3.9.0–3.9.3 never enforce the Trusted Checksums pin that hosted mode writes; the 4.0.0 notes and docs wrongly said every 3.9 release does. The version suffix still fails closed. CI runs the real-Maven hosted capstone on 3.9.3 and 3.9.4 (#258). -
Patch application, reversal, and cleanup handle missing files, release variants, corrupt state, newer ledger formats, and unsafe manifest paths without silently dropping protection. File ownership restoration failures produce warnings.
-
Hosted Cargo handles v1 locks, CRLF files, and repeated declarations, and refuses transitive dependencies its registry pin cannot reach. Vendored Cargo preserves multiple versions and warns about old-toolchain limitations.
-
Go preserves user-authored replacements, handles
+incompatibleversions, restores checksums, and unwinds hosted references during vendoring. Registry fetches honorGOPROXYand private-module settings. -
Yarn Berry preserves supported line endings and checksum spellings. Mode preflights, including Bun's, run before discarding existing protection.
-
Yarn Berry hosted references no longer send npm registry credentials to the patch server. The old
npm:locator made yarn attachnpmAuthToken/YARN_NPM_AUTH_TOKENto scoped packages (and to every package undernpmAlwaysAuth). Hosted mode now pins the way yarn does for a rootresolutionsentry:package.jsonroutes the locked descriptor to the hosted tarball and the lock entry is keyed by it, which also passes yarn's hardened mode (on by default for public pull request CI). A user-authoredresolutionsentry for the package is never overwritten. Locks pinned by earlier releases are re-pinned on the next hostedscan. -
Composer hosted references remove upstream source fallbacks and mirrors; RubyGems hosted locks preserve source order; NuGet edits use the active config and survive
<clear />entries. -
Python rewrites preserve supported markers, groups, extras, source metadata, and integrity pins. Relocks, out-of-tree environments, and lock-only VEX are handled consistently with each installer's supported behavior.
-
Hosted Pipenv scans read the
Pipfile, so a conflictingPipfile.lockentry refuses the patch project-wide instead of half-redirecting a siblingrequirements.txt(#333). -
Vendoring reuses valid committed artifacts during service outages. Updates do not build from a previous patch's modified bytes. Verified service artifacts keep their identity; integrity failures do not fall through to a local rebuild. Repair rebuilds against recorded pins and reports unavailable inputs.
-
VEX no longer attests an npm package that also ships a bundled, unpatched copy of the same
name@version(inBundle: true, or v1bundled: true). npm unpacks that copy from the parent's tarball, so no rewire reaches it; the reference is now reportedpatched_ref_unattributable, naming the bundled copy, in hosted and vendored mode (#325). -
API throttling uses bounded retries, failed queries appear in JSON diagnostics, and hosted reference resolution handles batches larger than 500 patches.
-
Transient apply locks are removed on normal command exit; no-op scans and full reversal avoid leaving unused
.socket/state. Terminal output, telemetry timeouts, and update-check handling are more consistent. -
Agent mode finds transitive npm packages in npm's linked store (
install-strategy=linked,node_modules/.store) and in a relocated pnpmvirtualStoreDir, instead of reporting thempackage_not_installed(#359, #362). A store outside the project, such as pnpm's global virtual store, is shared with other projects and is still not patched in place. -
Agent mode and
vexfind packages installed under pnpm'smodulesDir(modulesDir:inpnpm-workspace.yaml, ormodules-dirin.npmrc). From pnpm 10.12 the virtual store moves there (<modulesDir>/.pnpm), soapplyexited 0 with the package unpatched as "not installed", and hostedvexattestednot_affectedover the unpatched install. Hostedvexalso no longer attests a pinned npm package the crawler cannot see because pnpm keeps the installed store outside the project (#661, #696). -
npm locks keep their own layout when edited.
scan --mode hosted,scan --mode vendored,rollbackandvendor --revertre-serializedpackage-lock.json/npm-shrinkwrap.jsonwith LF line endings (and, in hosted mode, a fixed 2-space indent), so a CRLF or tab-indented lock got a whole-file diff and the undo did not restore its bytes. A lock with a UTF-8 BOM, which npm installs from, was skipped as unparseable (hosted) or refused asvendor_lockfile_version_unsupported(vendored). The lock now keeps its BOM, indent and line endings, and the undo is byte-exact (#324). -
Agent mode no longer patches other projects through a store they share. PDM 2.0–2.12 with
install.cacheandcache_method = symlinklinkssite-packages/<pkg>into its package cache, and pnpm's global virtual store (enableGlobalVirtualStore) linksnode_modules/<dep>into<store>/links.apply(also-g) wrote the patch into that shared directory, so every project using it was patched, and arollbackin one project silently unpatched the rest.applyandrollbacknow fail on such a package, naming the store and how to get a private copy (#332, #361). -
vendorunder--global/--global-prefix(orSOCKET_GLOBAL/SOCKET_GLOBAL_PREFIX) is now a usage error (exit 2,global_scope_unsupported), likescanandgetwith--mode vendored. Run inside a project,vendor -gvendored the manifest's records into that project and rewired its lockfile, andvendor --revert -gunwound the project's vendoring, so its next frozen install was silently unpatched. Global installs have no project lockfile to vendor into (#498). -
Patch API requests (
scan,get,applyandvexlookups, and blob and diff downloads) no longer hang forever on a stalled proxy, load balancer or half-open connection. A connect now fails after 10 s, and a connection that sends nothing for 60 s fails as a network error. Downloads that keep streaming are not cut off (#570). -
Patch blob and diff downloads stream straight to the
.socketcache instead of being held in memory whole first, so a large patch artifact no longer costs its full size in RAM duringapply,get,repairorrollback(#571). -
Hosted scans no longer write a pin that
vex,list,rollback,removeandvendorwould then refuse as contested (for example aPipfile.lockbeside arequirements -rinclude that still pins the registry, or a Maven pin inside a<profile>). Such a patch is skipped asredirect_unattributableand nothing is written for it (#567, #260). -
A stale hosted URL in an unrelated field, inactive lock or comment no longer counts a patch as already pinned, so it cannot slip past
--max-new-patches. Lockless NuGet/Cargo pins warnredirect_pin_locklessand name the lockfile to create (#1058). -
Taking a vendored package over to hosted mode is atomic: if the hosted pin cannot be written, the package stays vendored byte for byte (
redirect_takeover_kept_vendored) instead of being left unpatched in both modes, and the dry run now reports the real outcome (#1039). -
scan --prunecan reclaim unused vendored entries for every ecosystem (previously only npm, Cargo and requirements.txt), and scan reports them asvendor_ledger_entry_unwiredinstead of resurrecting them. Entries the package manager still installs, or whose wiring cannot be read (including Gradle), are kept (#1050). -
Vendored Maven and NuGet no longer treat a commented-out (or profile-scoped) repository or package source as existing wiring.
vendor --checkreports orphaned JVM trees whenever the ledger has no JVM entry, not only when it is empty (#1050). -
Purl matching folds NuGet case, PEP 503 names and Composer version padding everywhere, so
scan --pruneno longer drops a live NuGet entry whose API and installed spellings differ, andremove/rollbackaccept either spelling. Policy and rollout reports show the folded spelling (#1045). NuGet versions are also normalized (1.0.0.0matches1.0.0), so a package vendored under a 4-partpackages.configversion is no longer treated as unused by VEX andvendor --check, or reverted byscan --prune(#1202). -
Yarn classic
file:, URL and hosted-git copies are no longer repointed at Socket's registry artifact (hosted or vendored); they are skipped with a stays-unpatched warning, and rollback refuses such a pin from an older release. A block with noresolvedline is reported instead of counted as patched; mixed CRLF/LFyarn.lockfiles keep each line's ending, and a$in a patch-server URL no longer corrupts the rewrite (#1057). -
sbt builds marked only by
project/build.propertiesand scala-cli builds with only.scala-build/are discovered.--ecosystems mavennow includes vendored JVM ledger entries in rollback, repair and GC, and JVM vendoring no longer requests server builds it will not fetch (#1032). -
Vendored Gradle reads
verification-metadata.xmllike Gradle: markup in CDATA is text, and an unterminated section is refused asgradle_verification_unparseableinstead of being edited (#715). -
Gem hosted and vendored modes refuse a custom
BUNDLE_LOCKFILEthey would not pin, wire a Bundler 1.xGemfile+gems.rbtwin through theGemfile, and refuse twins whose locks disagree on the Bundler major. A lock-only scan reportsgem_lock_unsupportedinstead of finding nothing (#749, #751). -
npm workspace members holding a stray
package-lock.jsonare refused in hosted and vendored mode, naming the workspace root, instead of pinning a lock npm never reads (#1094). -
Rollback and remove of an agent patch superseded by a hosted pin restore orphaned store copies (Bun isolated linker) that still hold the patch, and fail without dropping the record if one cannot be restored (#1084).
-
apply.lockis removed when a run is interrupted with Ctrl-C, SIGTERM or SIGHUP (and Ctrl-Break/console close on Windows) (#808). -
Vendored PyPI revert keeps the wheel and ledger entry while any other project file still installs it (a
uv export, apylock.toml, a-rinclude, a sibling or subdirectory requirements file, including UTF-16 and non-UTF-8 files), warningvendor_revert_residual_reference(#996, #867, #1167). -
Vendored revert retires an entry whose dependency was removed by
pipenv uninstall,uv removeorbun remove(bun.lockb) instead of reporting drift forever (#1132, #1140, #1142). -
Re-vendoring Poetry and PDM projects to a superseding patch works instead of failing with
pypi_poetry_source_already_exists(#1136). -
A
requirements.txtand its-rincludes are treated as one install set, so a duplicate pin in an include no longer contests hosted or vendored wiring (#1086). -
Python lock inventory and repair treat
cp311-none-any-style wheels as platform-locked, consistently with hosted and vendored mode, and repair pairs each wheel URL with its own hash (#1150, #1079). -
Vendored Poetry 2.x locks are written one file per line, as Poetry does, and locks spelled
name="pkg"or without afilesarray now wire (#936). -
Vendored crates whose
Cargo.tomlhas a BOM, a legacy[project]table or dotted keys are found (#693). -
Manifests with uppercase
beforeHash/afterHashno longer failapplyandrollbackverification (#707). -
Lines inserted into files with mixed line endings use the majority ending, so one stray CRLF no longer turns every new line CRLF (#815).
-
Remedies no longer suggest a per-package
vendor --revert, vendored symlink refusals share hosted mode'sredirect_symlinked_file_unsupportedcode, and a refused hostedremoveprints its error once (#1043). -
VEX no longer attests a package beside an unpatched copy in the same lock or loader: a pnpm
file:copy orbundledDependenciescopy, a yarn classic registry block beside the Socket block, a yarn berryfile:/url copy or registry locator of the same version, a stale yarn PnP loader that still resolves the registry copy, and adeno.lockentry that Deno installs instead ofpackage-lock.json(#935, #938, #939, #519, #406). Bundled anddeno.lockcases are reported asvex_pnpm_bundled_copy/vex_deno_lock_copyand do not failvendor --check. -
VEX strips credentials, query and fragment from a non-GitHub/GitLab/Bitbucket git remote used as the product id, so CI tokens no longer leak into the OpenVEX document. A FIFO at
--outputno longer hangs a failedvexrun. -
Mode switches no longer strand a package unpatched. When a takeover is refused, the previous wiring is kept byte-for-byte and
--dry-runpreviews the refusal:- hosted to vendored, for any backend refusal after the upstream restore
(pnpm catalog, CRLF lock, uv inline sources, split requirements pins, a
gem declared in a
groupblock, lock-only yarn PnP) (#853, #944, #775); - vendored to hosted, for a uv lock at another version
(
redirect_uv_takeover_version_unreachable), a Poetry 0.x lock, a platform-only wheel, or a UTF-16requirements.txt(#723, #945, #721).
- hosted to vendored, for any backend refusal after the upstream restore
(pnpm catalog, CRLF lock, uv inline sources, split requirements pins, a
gem declared in a
-
rollbackandremoveof an agent record superseded by a hosted pin of a newer patch drop the record (rollback_record_superseded) and restore the lock instead of failing with "modified after patching" (#933). -
Hosted
scan/getrun from an npm, yarn, Bun or vlt workspace member refuse withredirect_workspace_lockfile_elsewhere, naming the root, instead of pinning nothing and exiting 0. Brace sets, character classes and nested workspaces are matched (#884, #1071, #942). -
vendor --checknames the real cause of an unwired entry: a contesting lock to delete, or a removed dependency to clean up withscan --mode vendored --prune(#900). -
Every file a NuGet, Maven, Hatch or pnpm vendored run writes is scanned for references, so the orphan sweep no longer deletes a unit the project still uses when its ledger entry is missing, and
repairreports it (#832, #958). -
npm and yarn:
- Yarn PnP is decided from the effective
nodeLinker(env, project, ancestor and home rc,YARN_RC_FILENAME), so a stale.pnp.jsafter switching tonode-modulesis ignored and a lock-only PnP berry project is refused before vendoring (#975, #539). - Hosted yarn classic resolves
yarn-offline-mirrorthe way yarn 1.22 does (parent, user and global rc files,YARN_*/npm_config_*env, BOM files) and refuses to pin under a mirror (#1013, #1078). It warnsredirect_yarn_classic_berry_migration_riskwhen a yarn 2+ install would drop hosted pins, unlesspackageManagerpins yarn 1 (#907). - Hosted yarn berry refuses a root
package.jsonwith mixed line endings, as vendored mode does (#628, #629). - Agent mode patches npm linked-store alias copies
(
"lp": "npm:left-pad@…") and VEX checks them (#852). - The npm wrapper runs the musl binary on musl hosts (yarn classic on Alpine) and prints the spawn error instead of exiting silently (#974).
- Hosted npm warns when npm would not install the pin as written:
redirect_npm_replace_registry_hostwhenreplace-registry-hostrewrites the hosted URL so installs fail E404 (#812), andredirect_npm_shrinkwrap_onlywhennpm-shrinkwrap.jsonis the only lock, which npm 12 ignores; VEX then omits the patch (vex_npm_shrinkwrap_only) (#899). Vendored mode warnsvendor_npm_allow_filewhen npm'sallow-filesetting would refuse the vendoredfile:tarball, andvendor --checkfails it (#969). - Packages carrying the project's own npm 12
npm patch(patchedDependencies, or apatchedlock record) stay on the registry withredirect_npm_patched_dependency_skippedinstead of being pinned into an install that failsEPATCHFAILED; an entry an earlier release already pinned names therollbackthat restores it. The vendored lockfileVersion 4 refusal namesnpm patchand the real remedies (#711). - Lock entries beneath a dependency that ships its own
npm-shrinkwrap.json(hasShrinkwrap), which npm installs from that shrinkwrap, are skipped with a warning in hosted and vendored mode and never attested;vendor --checknames the shrinkwrapping dependency (#753). - Vendored npm no longer refuses a registry package because a
file:dependency or workspace member has the samename@version(#688), no longer failsvexandvendor --checkafter npm 7–12 dropsresolvedfrom a v2 lock'sdependenciesmirror (#879), and keeps the vendored patch with avendor_prebuilt_pending/vendor_prebuilt_unavailableskip instead of failing the run while a superseding patch's artifact is not yet built (#954). - Agent scans apply
socket.ymlpath filters and the built-in test/fixture exclusions to nested npm projects; a package shared with a skipped project warnspolicy_shared_copy(#554).
- Yarn PnP is decided from the effective
-
pnpm:
- BOM-prefixed
pnpm-lock.yamlandpnpm-workspace.yamlare read correctly, so hosted mode no longer skipstrustLockfileor duplicates keys (#903, #904, #905). - Vendoring a scoped package into a pnpm 7/8 lock quotes its
name:, and a re-vendor fixes locks written by earlier releases (#956). Quoted scoped aliases are refused like unscoped ones (#957). - Hosted scans pin the per-member locks of a
sharedWorkspaceLockfile: falseworkspace from the workspace root instead of reporting success with nothing pinned, andlist,vex,rollbackandremovesee those pins; an unlistable member set is refused withredirect_pnpm_member_locks_unresolved(#492). WithgitBranchLockfileon and apnpm-lock.<branch>.yamlpresent, hosted and vendored modes refuse (redirect_pnpm_git_branch_lockfile/vendor_pnpm_git_branch_lockfile) instead of pinning the stalepnpm-lock.yaml(#556). - A project under an ancestor
pnpm-workspace.yamlwhosepackages:globs do not list it (dot directories included) is patched standalone again instead of being refused as a member (#1006). On pnpm 9.0–10.4, where it would breakpnpm add, the root-onlypnpm-workspace.yamlscaffold is no longer created (#734). - Rush: hosted runs on pnpm 11+ give a Rush-specific trust remedy
(
pnpm_config_trust_lockfile=true rush install,usePnpmFrozenLockfileForRushInstall), re-issued on a re-run, and warnredirect_rush_repo_state_stalefor subspacerepo-state.jsonfiles (#713, #714). - Hosted rollback and remove restore each entry from the registry the
project resolves it against (
.npmrcregistry/@scope:registryandpnpm-workspace.yamlregistry/registries, per pnpm major), and writetarball:fields only when the installed pnpm would, warningupstream_pnpm_tarball_setting_guessedwhen that is unknown (#919, #902). - Vendoring accepts a user's exact-version pin in
pnpm-workspace.yamloverrides:(quoted or commented too), edits only the project document of a pnpm 11+ two-document lock (others are refused withvendor_pnpm_lock_multi_document), and reverting a vendored package no longer clobbers a vendored dependency's ref inside it (#854, #466, #830).scan/get --mode vendored --dry-runpreview a refused hosted-to-vendored takeover aswould_refuse(#853). - Agent mode:
scan --mode agent <member-path>finds the member's linked copies (#778),applyandrollbackno longer report a member-linked package twice (#633), and global scans of pnpm 11+ patch everypnpm add -ginstall's copy, not just one (#435).
- BOM-prefixed
-
PyPI:
requirements.txtincludes are followed the way pip reads them:-rafter other options, quoted paths,${VAR}expansion and UTF-16/BOM files (#1028, #994, #721). A non-UTF-8 candidate file refuses hosted runs withcandidate_file_unreadableinstead of reporting success.requirements.txtfiles are decoded the way pip decodes them (BOM, then a PEP 263 coding line, then UTF-8). A Latin-1/cp1252 file is no longer skipped as absent by lock-only scans, and a UTF-16 export besideuv.lockis seen by vendored routing,vendor --checkandvexinstead of being attested over (#1119, #1120).- A plain-ASCII
requirements.txtwhose coding line names any ASCII-compatible codec Python knows (iso-8859-15,cp1250,mac-roman,gbk, …) is read instead of treated as unreadable, so scans no longer find nothing andvexandrollbacksee its hosted pins (#1212). - Pipenv projects with
use_pylock = trueare wired throughPipfile.lock, which Pipenv installs from, instead ofpylock.toml, and a pylock-only Pipenv checkout is refused (redirect_pipenv_pylock_unsupported/pypi_pipenv_pylock_unsupported) and not attested, instead of reporting success whilepipenv syncinstalls the original release (#912, #1122). - Hosted mode withholds platform-, ABI- and interpreter-bound wheels
(
cp311-none-any, manylinux) from cross-platform locks withredirect_pypi_platform_wheel; vendored mode givesvendor_platform_locked(#701, #932, #1048). - A fresh uv checkout with no env, or PEP 723 script locks only, no longer
scans the system Python (#964).
vendor --dry-runpreviews the uv inline-table refusal (#979). - Vendored mode warns
pypi_multiple_lockfileswhen a rootrequirements.txtbeside the governing lock stays unpatched (#612).
-
RubyGems:
- Hosted gem no longer pins a version the project's
Gemfile.lockdoes not resolve (redirect_gem_version_not_locked) (#1055). - The stale-install guard judges only the gem homes Bundler uses, removing
false warnings that broke
scan --mode hosted --vexon fresh checkouts and naming project-localvendor/bundlecorrectly (#1001, #729). - Agent mode patches the gems Bundler loads under
path.systemand in the<root>/.bundleinstall root (#915, #967). - Trailing
#comments in.bundle/configvalues are stripped as Bundler 2.5.6+ does (#951). - A hosted re-scan that moves a patched gem to a new URL keeps the lock's GEM sections in Bundler's sorted order, so frozen installs on Bundler 4.0.19+ no longer fail with "Your lockfile needs to be updated"; locks left out of order by earlier runs are healed on the next scan (#1186).
- Hosted gem no longer pins a version the project's
-
Project-mode crawls of a Cargo project with a
Cargo.lock, a Go module with ago.sum(and nogo.workin effect), a restored .NET project (obj/project.assets.json) or a Deno project with adeno.locklook up only the packages that project resolves instead of walking the whole shared$CARGO_HOMEregistry,GOMODCACHE,~/.nuget/packagesor JSR cache, so scan and agent mode no longer patch, and VEX no longer attests, packages other projects downloaded. Projects without a readable lock or restore, Go workspaces,vendor/trees, solution roots and global mode keep the full walk (#1204, #1207, #427, #1216). -
scan --mode agent --jsonandget --jsonreport files whose contents matched neither patch hash and were overwritten, ascontent_mismatch_overwrittenentries inwarnings[], asapply --jsonalready did (#1004). -
remove --preserve-stateon a hosted patch prints the "no preservable local state" note and carrieshosted_state_not_preservablein--jsonwarnings[], likerollback --preserve-state(#433). -
Registry downloads for hosted restore and vendored Maven use the 10 s connect / 60 s idle timeouts instead of a 60 s total deadline, so slow large downloads no longer fail (#872). Vendor-service retries honor HTTP-date
Retry-After(#677). -
Home-relative probes (Cargo, Maven, NuGet, Python, Ruby, Deno) no longer resolve against the working directory when
HOMEis unset. -
JVM.
- Gradle rollback refuses a before-blob that does not hash to its cache directory, and dry runs predict it; a crafted jar header can no longer exhaust memory; Gradle cache and build files are read without hanging on a FIFO (#646).
- Hosted Maven edits only the markup Maven builds the project from: it
skips comments,
<profiles>and plugin dependencies, honors<classifier>and self-closed sections, and never writes a second top-level section Maven refuses (#259, #262, #342). - Hosted Maven refuses a multi-module reactor root
(
redirect_maven_multimodule_unsupported) instead of pinning only the root while a module's own<version>keeps the unpatched jar;vexno longer attests such a pin (#261). - Project-mode Maven scans look up only the coordinates the project's poms
reach, instead of everything other builds left in
~/.m2(#265). - Vendored Maven leaves a root unpinned, with a warning, when an imported BOM or an external parent manages a different (or unknown) version, so vendoring no longer silently downgrades or freezes it (#488).
- scala-cli fails closed on an unreadable or truncated Bloop project file, as sbt does, instead of judging the remaining projects alone (#1270).
-
NuGet.
- Hosted unwinds restore the lock's real
contentHash, not nuget.org's catalogpackageHash, sodotnet restoreno longer fails NU1403 afterrollback/remove(#624). - Hosted and vendored modes warn when the global packages folder already
holds the upstream bytes of the patched version
(
redirect_nuget_stale_global_package/vendor_nuget_stale_global_package), and the same run's VEX no longer attests it (#352). - The written
packageSourceMappingkeeps feeds defined in parent and user configs working and routes the patched id exclusively to the Socket source (#354, #462). - Every lock the root
nuget.configgoverns is pinned: member projects' locks,packages.<Project>.lock.jsonand a literalNuGetLockFilePath(#353, #514). Hosted lock reads accept a UTF-8 BOM and leave other resolved versions of the id alone in multi-targeting locks (#593, #623). - Vendored revert works on
core.autocrlfcheckouts and never leaves the config and lock half-reverted (#537);</packageSources >and other valid spellings are recognized (#685).
- Hosted unwinds restore the lock's real
-
Go.
- A
replacefor a version the build list doesn't select is no longer written as if it worked:applyrefuses,vendorrefuses (vendor_go_version_not_selected),apply --checkreports a mismatch andvexomits the patch (go_version_not_selected) (#391, #392, #509). - In a Go workspace,
applyandvendorno longer write a go.modreplacethat thego.workin effect overrides, ignores or rejects;--checkfails on such a replace andvexno longer attests it (#393, #458, #531). - Agent
applyandvendorwork on Windows with Go's read-only module cache instead of failing withAccess is denied(#346). vexattests an applied agent-mode patch for a+incompatiblemodule instead of reportingnot_applied(#484).
- A
-
Composer.
- Reinstall hints name the configured vendor directory
(
COMPOSER_VENDOR_DIR,config.vendor-dir) instead ofvendor/(#658). scan -gand agent-mode scans find installs whosevendor-diris set in the global or another inherited config, not only the project'scomposer.json(#439, #686).- Global discovery finds a Composer 2 home in the XDG config dir even when
an old
~/.composerdirectory remains (#586).
- Reinstall hints name the configured vendor directory
(
-
npm family.
- npm's
${VAR}expansion is applied toreplace-registry-host, so a setting that expands toalwaysgets the hosted-pin warning (#1233). vendor --checkfails, andrepairreportsvendor_dep_manifest_unlocked, for a yarn classic lock an older release vendored with dangling descriptors (#1379).- Hosted yarn classic pins always carry the
#<sha1>fragment yarn keys its cache on, so a warm cache can't serve unpatched bytes; a tarball that can't be verified is skipped (npm_tarball_unavailable) (#558). Lock blocks for empty-range dependencies ("left-pad": "") are found (#1271). - A yarn classic package also installed through an
npm:alias is no longer attested byscan --vex, and a takeover that would unwire a vendored alias block is retracted (#1081, #1158). - Yarn Berry: a re-scan pins a range added after the hosted pin, and
unwinds no longer leave two entries with one resolution (#1082);
restores read the registry from
npmScopes,YARN_NPM_REGISTRY_SERVERand parent/home.yarnrc.ymllike yarn (#1017) and put back the registry'sbin:map (#1131); pins of native addons drop the registry-onlynode-gypdependency (#737); and aresolutionsselector left byyarn removeis reportedhosted_resolution_orphanedand removed byrollback/removeinstead of failing them (#1203). - pnpm: a keyless
pnpm-workspace.yamlgets apackages: ['.']scaffold (removed again on unwind) so pnpm 8–10.4 keep working (#1096); vendored scans skip orphaned.pnpmstore entries (#1197); and a custommodulesDirwithnode-linker=pnpis no longer mistaken for Yarn PnP (#1129). - Bun: hosted unwinds read the registry from the user's
.npmrcand global bunfig, as Bun does, instead of querying the public registry for private scopes (#1276); abun.lockbpin shared with a bundled copy can be listed and unwound (#1243). - Upgrading or downgrading a vendored npm or Bun package reverts cleanly instead of being kept as drift (#1155).
- Vendored
bun.lockbworkspaces check that git would commit the member-relative tarball mirrors, as the root artifact already was, so a*.tgzignore rule can't break fresh frozen installs (#1116). - Hosted mode leaves pnpm locks alone when pnpm's
lockfilesetting is off (lockfile=false,lockfile: falseorpackage-lock=false), since pnpm then installs from the registry; it warnsredirect_pnpm_lockfile_disabledand suggests vendored mode, and lock-onlyvexno longer attests those pins (#1074). - Yarn Berry rollback / remove after a
yarn removein one workspace member restore the registry entry keyed only by the descriptors still declared, soyarn install --immutablepasses (#1435). .yarnrc.ymlsettings are read the way yarn reads them, including${VAR:-default}and quoted YAML values, so a PnP project configured through a variable is refused as PnP (#1100).- Appending
allow-remote=allto a CRLF.npmrcwith no final newline keeps its line endings intact (#1196). - Hosted scans pin npm aliases in a v2 lock's npm 6
dependenciesmirror and in v1 locks, vendoring rewires them, and VEX withholds while the mirror still resolves from the registry (#432). vexno longer attests a patch wired in only one ofnpm-shrinkwrap.json/package-lock.jsonwhen the other lacks thatname@version(#798).- Hosted and vendored modes no longer drop a project's own
bun patch; such packages stay on the registry with a warning (hosted) or are refused (vendored) (#367). - Hosted Yarn classic with
yarn-offline-mirroris refused withredirect_yarn_classic_offline_mirrorinstead of reporting unpatched installs as patched; a vendored package is kept vendored (#364). - Yarn classic
file:directory copies are named in hosted runs and never attested; vendoring a package with only git/link/file:copies is refused asvendor_lock_entry_not_rewritable(#921, #857). - Hosted rollback/remove of Yarn Berry and vlt pins restore from the
project's own registry (
npmRegistryServer, the vlt node's registry), falling back to the default withupstream_registry_fallback(#908, #521). The warning no longer includesuser:token@credentials from the registry URL. - Vendored npm-family tarballs are protected from
.gitignorerules such as*.tgz; a vendor dir git would still ignore is refused up front withvendor_artifact_gitignored(#831). - Hosted runs from a pnpm workspace member whose lock lives elsewhere
(including a root
lockfileDir) are refused withredirect_pnpm_lockfile_elsewhereinstead of pinning nothing (#590). - pnpm members with their own lock no longer get settings written to an
ignored nested
pnpm-workspace.yaml; hosted refuses withredirect_pnpm_settings_elsewhereuntil the root trusts the lock, and vendored refuses withvendor_pnpm_settings_elsewhere(#880, #881). - Agent mode finds transitive dependencies in pnpm's global virtual store (also from workspace members) and refuses them as shared instead of reporting success (#362).
- Apply and rollback report writes to every pnpm/vlt peer-variant store copy, not just the first (#756, #772).
- npm's
-
Cargo.
- Hosted runs from a Cargo workspace member are refused (naming the root) instead of rewriting the member as a lockless project (#417).
- Agent mode patches, checks, attests and restores every crates.io copy in
a shared
CARGO_HOME(one per registry index dir, as different cargo versions use), not just the first (#339). - After an in-place
applyorrollback, the nextcargo buildrecompiles the changed crate instead of linking the rlib it cached before (#387). applyon a cold or pruned registry cache exits 1 again and namescargo fetch, instead of reporting success while the next build compiles the unpatched crate (#616).- An agent-to-vendored takeover followed by
rollback/remove, andscan --pruneafter a lock bump, no longer drop the record of a crate still patched in the shared cache (#336, #1278). - Hosted mode refuses a crate the user overrides with
[patch](redirect_cargo_dep_overridden) instead of replacing the override (#480).vexwithholds, and restore handles, a hosted crate when a later dependency locks its own crates.io copy of the same version (#679, #863). - Cargo source replacement (
[source.crates-io] replace-withto a directory source) is followed through every config cargo reads, so agent mode patches, andvex/ rollback judge, the copy cargo builds from instead of assuming<cwd>/vendor. Avendor/that noreplace-withwires is no longer crawled. Hosted mode skips every crate when crates.io is replaced by an on-disk source (redirect_cargo_directory_source) (#338, #455). - Hosted mode pins a dependency declared with an explicit
registry = "crates-io"instead of skipping it (#386).
-
RubyGems.
- Gemfile lines joining declarations with
;are refused instead of losing the second gem; a declaration ending in a bare;is rewritten again (#826). - Vendored rewrites drop splat, constant and method-call version arguments
that Ruby rejected after
path:(#847). - Hosted mode refuses gems from git sources (
gitlab:, customgit_source, string-keyed"git" =>) and lock GIT/PATH sections, and keeps string-keyed options (#652). - Hosted mode refuses a redirect a Bundler mirror (
mirror.all, a mirror for the patch source, Bundler 4.1 quoted keys) would bypass, with a remedy, and hosted VEX checks existing pins against mirrors (#681). - Gem readers use the lock Bundler loads, including
gems.locked(#736). - The crawler finds Bundler 4 standalone installs in
./bundle(#796), honorspath.systemover a leftovervendor/bundle(#915), and VEX checks an out-of-tree.bundle/configpath for stale copies (#709). - Agent
apply,rollback,apply --checkandvexhandle every platform build of a gem version (ffi-1.17.2-x86_64-linux-gnu), the copy RubyGems actually loads (#1092). - A
BUNDLE_GEMFILEthat names the project's Gemfile through a symlinked directory (macOS/tmp, a linked workspace) is recognized in every mode (#896). - Hosted unwinds keep the
!Bundler writes for a gem declared in the user's ownsource … doblock, so frozen installs keep working (#1056). - A hosted scan of a Gemfile with no lock no longer pins whatever version
the shared gem home holds (
redirect_gem_no_lockfile) (#1125). - Gem checks ignore system gem homes Bundler never loads for the project
(
deployment, explicitpath) (#1098, #1109). - With
BUNDLE_GEMFILEpointing into another directory (Appraisal-stylegemfiles/rails_7.gemfile), agent mode,vexand the stale-install check probe the stores Bundler actually uses (#952). - Vendored revert no longer half-reverts when the wired Gemfile line was commented out (#988).
- Gemfile lines joining declarations with
-
PyPI.
- A Pipenv project without a Pipenv venv no longer falls back to the system Python (agent patched system packages; vendored failed) (#504, #947).
- Pipenv venv discovery honors settings from
.env(orPIPENV_DOTENV_LOCATION) and patches./.venvtoo where older Pipenv still uses it (#546, #645). get <name>,socket.ymlpackage lists andscan --packagematch PyPI names by PEP 503 form, sotyping_extensionsorruamel.yamlmatch (#910, #926).- Hosted rollback/remove restore a
requirements.txtmade up only of hosted pins (#410) and pip-writtenpylock.tomlfiles (#804). - Vendored uv revert writes the specifier
pyproject.tomldeclares now, souv sync --lockedkeeps working after a requirement edit (#840). - Hosted Poetry and PDM scans rewrite each lock in one pass, which makes scans with many patches several times faster (#760, #762).
- Every
requirements.txtpin is read by one grammar, so vendoring accepts the legacysix (==1.16.0)form that scan and hosted mode did (#1365). Lock-only discovery also looks up equivalent PEP 440 spellings (==1.16,==1.16.0.0), so those pins get patched on a fresh checkout (#604). - Hosted mode no longer replaces a user's own direct reference
(
name @ <url>to a mirror, fork orfile://path) inrequirements.txt(redirect_requirements_direct_reference) (#542), and neither uv writer touches a PEP 508 direct reference inpyproject.toml(#767). - uv: reverts handle a vendored package later added directly (#1374), each
PEP 723 script /
pylockpair separately (#1285, #890), and a package whose parent wasuv removed (#1287); hosted unwinds keep the user's ownoverride-dependenciespin (#411); and a scan from a workspace member uses the workspace root'suv.lock(#1138). - Hatch-derived
pylock.tomlfiles are no longer rewritten alone, since Hatch regenerates them frompyproject.toml(redirect_hatch_lock_regenerated) (#479). - PDM restores of a
static_urlslock from a private index are refused instead of pointing the lock at files.pythonhosted.org (#413). - A vendored revert kept because another file still installs the wheel is reported as a residual reference, not drift, with a remedy that works (#1184). After an unwind, PDM, uv and Pipenv projects get the correct reinstall command for removing the patched build (#477).
- Vendored
Pipfile.lockedits change only the patched entry (keeping\uXXXXescapes and a leading BOM) (#1128). - A Poetry dependency pinned to
source = "pypi"is refused in a lock written by Poetry 1.4–2.2, which can't install the rewired lock (redirect_poetry_lock_unsupported/pypi_poetry_source_constraint); other Poetry versions are rewired with a warning (#1282). - Venvs created with
--system-site-packages(including Poetry's and Pipenv's options for it) are crawled through to the base interpreter's packages, sovexno longer attests a lock pin while an unpatched base copy is imported, and stale-install guards warn about it (#409). - Hosted and vendored uv refuse to wire through
[tool.uv.sources]whenno-sources = trueorno-sources-packagemakes uv ignore it (redirect_uv_project_unsupported,pypi_uv_no_sources) (#564). - Vendored Hatch refuses (
pypi_hatch_unsupported) a checkout whose path contains%,#,;or?, which Hatch's{root:uri}turns into a broken file URL (#547).
-
Vendoring. Vendored mode refuses to replace a symlinked lockfile or manifest (
redirect_symlinked_file_unsupported) instead of detaching it from its target (#627). -
General.
- Rollback and remove delete directories (and stale
__pycache__) that the patch created (#838). - Crawler probes of version-manager shims (
gem,python3,npm,composer, …) time out after 10 s instead of hanging every crawling command (#845). - The report-only
scan -g/--global-prefixhint keeps the global flag (#464). vexproduct detection reads Go's block-formmodule ( … )directive (#781).
- Rollback and remove delete directories (and stale
- Shared format models, project snapshots, ledger views, and a vendored backend consolidate discovery and lifecycle handling. Grouped writes and bounded concurrency reduce repeated disk and network work.
- CI reuses compiled test binaries and splits broader compatibility matrices into dedicated jobs. Release publishing uses separate Cargo and npm workflows.
- Documentation now separates usage, configuration, migration, compatibility, and development guidance; completed plans, prototype research, and historical run reports are removed from the maintained docs.
- Further consolidation of purl identity, lockfile and XML parsing, BOM and
line-ending handling, path normalization, digests and atomic writes;
faster
bun.lock, Poetry and PDM scans. CI runs through a merge queue with sharded, decoupled platform jobs and retries for registry test downloads. rustls is bumped to 0.23.45. - Help text, the README and the usage and migration guides are shorter and
describe lockfile-first patching, the package-target grammar and
service-only vendoring.
vendor --forceis documented as bypassing only the installed-variant probe;vendor_content_mismatch_overwrittenis no longer emitted. - Obsolete local-build vendoring machinery, the
qbsdiffdependency and duplicate lock readers are removed; Pipfile.lock, nuget.config, purl classification and BOM handling each go through one shared reader. - Linux CI runs on Depot runners with a lean per-package-manager gate on PRs and the merge queue (the full matrix runs nightly), reuses the merge queue's verdict on main, and spawns test binaries in a hermetic environment.
v4.0 is the three-modes release. What began as an agent-style tool that
patches installed packages in place now offers three deployment modes,
selected with scan --mode <agent|vendored|hosted> (each mode's detailed
entries follow below; per-ecosystem mechanics live in docs/ecosystems.md):
-
Agent mode (the default, and the original behavior):
applypatches installed packages in place on the current machine —node_modules/, site-packages, the cargo registry cache, and so on — tracked in the local.socket/manifest and re-applied after installs by the hookssetupconfigures. Requires socket-patch (and Socket API access, or pre-fetched blobs) on every machine that installs dependencies. -
Vendored mode (
vendor, orscan --mode vendored): ejects each patched package into a committed.socket/vendor/<ecosystem>/<patch-uuid>/<artifact>and rewires the ecosystem's lockfile so the project consumes the vendored copy. After committing, a fresh checkout builds with the patched dependency on machines with no socket-patch installed and no Socket API access — fully offline/airgap-friendly, and the strictest install flags (npm ci,--frozen-lockfile,--locked,--deploy, …) verify the vendored artifact like any other. A committed ledger records the verbatim original lockfile fragments, sovendor --revertrestores them byte-exactly. Covered in v4.0: the whole npm family (npm, yarn classic, yarn berry node-modules, pnpm, bun), pypi (uv, requirements, poetry, pdm, pipenv), cargo, go, composer, gem, maven, and nuget. -
Hosted mode (
scan --mode hosted, new in v4.0): rewrites lockfiles / registry configs so ONLY the patched dependencies resolve to Socket-hosted, integrity-pinned artifacts on patch.socket.dev — no artifact bytes land in the repo and no CI changes are needed. The package manager's own integrity checking pins the patched bytes (tamper fails the native install), and re-runs are idempotent. Covered in v4.0: the npm family (package-lock/shrinkwrap, pnpm — including Rush monorepos — yarn classic, yarn berry, bun), pypi (requirements, uv), cargo, composer, gem, nuget, maven (fail-closed version suffixing + Trusted Checksums), and golang for references carrying agoproxyregistry override (free tier; otherwise Go stays vendored — see the documented NO-GO analysis).
All three modes feed VEX attestation, with a provenance marker per mode:
plain (agent), (vendored), and (redirected) (hosted).
- The
unlocksubcommand. Folded intorepair, which now deletes the leftover<.socket>/apply.lockfile as its final housekeeping step (skipped under--dry-run, refused withlock_heldwhile another live socket-patch process holds the lock). Rationale: a leftover lock file from a crashed run never blocked acquisition in the first place — the OS releases a dead holder's advisory lock along with its file handle — sounlock's inspect path had no recovery scenario, and its--releasefile deletion is now automatic. Migration:unlock --release→repair; the probe-style "is anything holding the lock?" check → run the mutating command (optionally with--lock-timeout) and branch onerrorCode: lock_held.SOCKET_UNLOCK_RELEASEis gone with the subcommand, and thepatch_unlocked/patch_unlock_failedtelemetry events are retired. - The global
--break-lockflag andSOCKET_BREAK_LOCKenv var. It never stole a live holder's lock (deliberately, since that defeats mutual exclusion) and a stale file never contends, so all it did was emit alock_brokenaudit event for a reclaim that plain acquisition performs anyway. Thelock_brokenwarning event and rollback'swarnings[]lock_brokenentry are no longer emitted (warningsstays present, now always empty). Thelock_heldstderr hint now advises waiting /--lock-timeoutinstead of pointing at the removed commands.
--helpcommand order is now workflow-first:scan,apply,vex,vendor,setup, thenrollback,get,list,remove,repair.
-
get --mode <hosted|vendored|agent>— per-advisory hosted/vendored patching.get(fetch/apply a single patch by CVE/GHSA id, patch UUID, or package name) now honors the same mode selector asscan: hosted rewrites lockfiles for just the selected patches through scan's redirect engine verbatim; vendored routes through scan's vendor step with scan's save-only download posture. CVE/GHSA fan-outs are narrowed to versions actually installed (disk ∪ manifest, plus the lockfile inventory and vendor ledger in hosted/vendored modes); when narrowing leaves nothing, the new additivenot_installedstatus reports it at exit 0. Exempt from narrowing: UUID ids, exact-versioned purls,--save-only,--all-releases, and the package-name path. -
Hosted mode for Go (free tier).
scan --mode hostednow redirects golang dependencies when the reference carries agoproxyregistry override: a fork-stylereplace <mod> <ver> => patch.socket.dev/gopatch/<uuid> <ver>-socketpatch.<n>ingo.modplus the socket module's twoh1:lines ingo.sum(and the replaced original's lines pruned — the tidy-stable state). Day-2 machines need no configuration: go consults the checksum database only for modules absent fromgo.sum, so the committed pair is the whole redirect — validated end-to-end ine2e_golang_hosted_build.rs(fresh caches, bogusGOSUMDBtripwire,go mod tidybyte-level no-op, tampered-hashSECURITY ERROR). Fails closed (per-depredirect_golang_*warnings, no partial writes) on missing hashes, an out-of-namespace module path, a require-version mismatch, or a user-authored replace conflict; references without the override keep the historicalredirect_golang_unsupportedwarning (paid tier stays vendored — seedocs/ecosystems.md#go-directory-replaces-and-gosum). Wire schema gainsintegrity.goModH1andregistryOverride.identifiers.goModuleVersion(additive). Requires server-side publication of the grant-freegopatchartifact flavor — production publishes no golang hosted modules yet, so behavior is unchanged until it does. -
Version-bump automation + release-readiness gate.
scripts/bump-version.sh <X.Y.Z> --prperforms the whole bump chore — stamps every packaging site viaversion-sync.sh, rolls[Unreleased]into a dated## [X.Y.Z]CHANGELOG section, and opens therelease/vX.Y.ZPR (also dispatchable from the Actions tab as the Version Bump workflow). A newrelease-readinessCI job runsscripts/release-lint.shon every PR: version-coherence always (version-sync must be a no-op, so a hand-edited version in any one packaging site fails CI), plus the full gate — non-empty CHANGELOG section, no pre-existing tag — on PRs that bump the workspace version. TheReleaseworkflow'sversionjob now runs the same script, so the publish gate and the PR gate cannot drift. Playbook: docs/releasing.md. -
socket-patch --update— self-update. Downloads the release for the compiled target from GitHub Releases, verifies it against the publishedSHA256SUMSbefore extraction, sanity-execs the staged binary, and atomically swaps it in place (Windows uses the rename-dance viaself-replace; a setuid/setgid install is refused).--update 3.4.0(orSOCKET_PATCH_VERSION) pins a version, up or down; bare--updatenever downgrades;--forcereinstalls.--dry-runis a check-only probe (zero downloads,updateAvailablein the--jsondetails). Package-manager-managed installs (npm, pip, cargo, the gem launcher cache, Homebrew) are detected from the canonicalized executable path and refused with that manager's own upgrade command;--forceoverrides.--offlinerefuses up front and--forcecannot bypass it. Concurrent updates are single-flighted via an advisory lock; every failure path leaves the installed binary untouched. -
Passive update notice. Interactive runs mention a newer release at most once a day, on stderr only, after the command's own output: suppressed under
--json/--silent/--offline, in CI, when stderr is not a terminal, or withSOCKET_NO_UPDATE_CHECK=1(suppressed means zero network I/O). The background check can never alter a command's exit code, stdout, or add more than ~500 ms; state corruption degrades to "never checked". An explicit--updaterefreshes the notice's cache. -
shellcheck scripts/install.shin CI (and a fix for the SC2144 glob-with--emusl-loader probe it found). -
socket loginnow configures socket-patch. The JS Socket CLI's persisted config (<data dir>/socket/settings/config.json) is read — never written — as a fallback layer below env vars forapiToken,defaultOrg, andapiBaseUrl: precedence per key is CLI flag > env varsocket-cli config > built-in default. Four
SOCKET_CLI_*env names are accepted as silent peer aliases (SOCKET_CLI_API_TOKEN,SOCKET_CLI_ORG_SLUG,SOCKET_CLI_API_BASE_URL,SOCKET_CLI_NO_API_TOKEN); the canonicalSOCKET_*names win. Two new env-only toggles:SOCKET_NO_API_TOKENignores ambient tokens (env + config; an explicit--api-tokenstill authenticates) andSOCKET_NO_CONFIGdisables the config layer. A corrupt config file warns once on stderr and is ignored;--jsonstdout is unaffected. The telemetry endpoint now resolves the API base through the same chain as client construction, so a config-suppliedapiBaseUrlapplies to both. Design notes:docs/configuration.md. -
Hosted patch mode:
scan --mode hosted(a.k.a. the hidden--redirect). The third patch-application mode: instead of applying in place (agent) or committing artifacts (vendored),scanrewrites lockfiles / registry configs so ONLY the patched dependencies resolve to Socket-hosted, integrity-pinned packages on patch.socket.dev — no artifact bytes land in the repo and no CI changes are needed. Per ecosystem: npm rewritespackage-lock.json/npm-shrinkwrap.jsonresolved+integrity(v2 legacydependenciesmirror included),pnpm-lock.yamlinline resolutions, and yarn classicresolved/integrityblocks; pypi rewritesrequirements.txtpins toname @ <url> --hash=sha256:…(pip-compile continuation lines are refused rather than corrupted) anduv.lockwheel entries; cargo defines a per-patch sparse registry in.cargo/config.tomlplusCargo.tomlregistry =keys and Cargo.locksource/checksumsurgery; composer rewrites the lock entry'sdisturl/shasum; nuget adds anuget.configsource +packageSourceMappingand repinspackages.lock.jsoncontentHash; gem adds a per-depsourceblock + aCHECKSUMSpin (bundler ≥ 2.6). A dep counts as redirected only when its hosted URL (or per-dep registry index) actually landed in a project file; re-runs are idempotent (zero new edits over already-rewritten output). The Rust rewriters are held byte-identical to the depscan backend's TS twins (the GitHub-app hosted PR flow) by shared golden fixtures undertests/fixtures/redirect/. JSON output gains aredirectsub-object withmode: "hosted",redirected,rewrittenFiles,skipped,warnings. -
scan --mode <hosted|vendored|agent>: the documented mode selector. One value-enum flag replaces the boolean spellings (--redirect== hosted,--vendor== vendored,--apply/--sync== agent), which remain supported as aliases. Combining--modewith a boolean of a DIFFERENT mode is a usage error (exit 2); the same mode spelled both ways is accepted, and--detachednow requires vendored mode in either spelling. -
VEX support for hosted mode: the
(redirected)provenance marker + the redirect ledger.scan --mode hostedpersists its recorded file edits and the full patch records (file hashes + vulnerabilities) into.socket/vendor/redirect-state.json(merge-on-rewrite, append-only edits — the pre-redirect originals a future revert needs are never clobbered). Redirected patches carry the impact-statement marker "Patched via Socket patch<uuid>(redirected)", completing the provenance trio (plain = agent,(vendored),(redirected)). In-runscan --mode hosted --vexattests confirmed redirects from the ledger WITHOUT hash verification (the bytes are fetched at install time; the JSONvexsummary carriesverified: false), while a post-installsocket-patch vexreads the ledger back and hash-verifies the redirected patches against the installed tree. A confirmed redirect whose record fetch failed surfaces arecord_fetch_failedwarning (the patch is missing from VEX until a re-run). -
NuGet + Maven vendor backends (
vendor/scan --mode vendored). NuGet: the uuid dir is a committed folder feed holding a deterministically rebuilt.nupkg(embedded signature dropped; unsigned is accepted under NuGet's default validation), wired via anuget.configsource +packageSourceMappingand apackages.lock.jsoncontentHashrepin —dotnet restore --locked-modethen fails NU1403 on tamper. Maven: the uuid dir is a committed maven2file://repository (rebuilt.jar+ the verbatim upstream pom so transitives survive +.sha1sidecars), wired via apom.xml<repository>withchecksumPolicy=fail; multi-module aggregator poms (vendor_maven_multimodule_unsupported) and gradle-only projects (vendor_gradle_unsupported) are refused fail-closed, and the always-onvendor_maven_local_cache_shadowadvisory carries themvn dependency:purge-local-repositoryone-liner (a warm~/.m2copy silently shadows any repository). Both are proven by docker capstones against the real .NET SDK / Apache Maven (cold-cache,--network none, RED + TAMPER probes).nugetandmavenare now DEFAULT compile features; theSOCKET_EXPERIMENTAL_NUGET/SOCKET_EXPERIMENTAL_MAVENruntime opt-ins that briefly gated in-place agent apply were retired later in this cycle (see the "promoted to fully available" entry under Changed). The vendored path convention + uuid recovery rule now coversnugetandmavendirs, and--vendor-sourceprebuilt downloads cover nuget. -
Maven hosted rewriter (pom projects) — fail-closed version suffixing + Trusted Checksums. Hosted mode's maven leg pins the patched jar the only way a lockfile-less ecosystem can: the serve route exposes the patch under a Socket-only
<version>-socket.<hex8>suffix (existing ONLY on the injectedsocket-patch-<uuid>repository), and the rewriter pins that version explicitly — it rewrites the literal<version>, or (for a transitive / managed dependency with no literal version) adds a<dependencyManagement>entry — alongside the<repository>insert (releases enabled,checksumPolicy=fail, snapshots disabled). An outage or tamper on the Socket repo then HARD-FAILS the build: the suffixed version resolves nowhere else, so there is no silent fall-through to Central (the base version 404s). A${property}version is refused (redirect_maven_dep_unpinned— a literal edit would break the reference and a depMgmt pin could strand sibling artifacts); a literal version matching neither the base nor the suffixed value is skipped (redirect_maven_dep_version_mismatch); a non-jar<type>is skipped (redirect_maven_unsupported_packaging). When the serve route supplies both the jar and pom sha256, the rewriter also emits Maven 3.9+ Trusted Checksums files —.mvn/maven.configresolver args (originAware=false,failIfMissing=false) +.mvn/checksums/checksums.sha256entries pinning both artifacts under the suffixed version's local-repo path, merging into any pre-existing user config / checksum set (a conflicting value is never overridden —redirect_maven_trusted_checksums_conflict). The.mvn/*files are silently inert below Maven 3.9 (the version suffixing is still fail-closed on its own); on 3.9.0–3.9.8 a mismatch is enforced but reported unclearly (readability fixed in 3.9.9, MNG-8182). When the upstream pom is unavailable / unsuffixable the rewriter falls back to the legacy same-GAV repository injection with aredirect_maven_same_gav_fallbackwarning (NOT fail-closed: a Socket-repo failure falls back to the unpatched artifact). Gradle build scripts are never edited: a presentbuild.gradle*/settings.gradle*emits a paste-ableexclusiveContentsnippet carrying the suffixed version (redirect_gradle_manual_snippet) plus a reminder to bump the dependency declaration — fail-closed by repository exclusivity. -
Hosted mode now rewrites yarn-berry and bun lockfiles. The hosted npm family gains two flavors beyond package-lock / pnpm / yarn-classic. yarn berry (
__metadata:v2+ lock): the rewriter edits ONLY the lock entry —resolution:gains yarn's own::__archiveUrl=<encodeURIComponent(url)>binding andchecksum:becomes the precomputedyarnBerry10c0cache-zip sha512 — leaving the descriptor key andpackage.jsonuntouched, soyarn install --immutable --check-cachepasses and tamper fails YN0018. Whole-file gates refuse acacheKey ≠ 10c0or a.yarnrc.yml compressionLevel ≠ 0(redirect_yarn_berry_cache_unsupported) — no offline-reproducible checksum. Validated e2e against realcorepack yarn@4.12.0on the node-modules linker; PnP is not exercised for hosted (the lock rewrite fires, but PnP's.yarn/cacheresolution is untested). bun (textbun.lockv1): the packages-entry registry 4-tuple["name@ver","<reg>",{deps},"sha512-…"]is rewritten to a URL 3-tuple["name@<url>",{deps},"sha512-…"], fail-closed on any grammar deviation;bun install --frozen-lockfilethen installs the hosted bytes and tamper fails the integrity check. A binarybun.lockbwith no text lock is auto-migrated first via the user's ownbun install --save-text-lockfile --frozen-lockfile --lockfile-only(deletesbun.lockb, recorded as aremovedledger edit, offline, fails closed;redirect_bun_lockb_would_migrateon--dry-run,redirect_bun_lockb_unsupportedif the migration is unavailable). The Rust rewriters are byte-identical to the depscan backend's TS twins via shared golden fixtures. -
Hosted mode supports Rush monorepos. A Rush repo has no root
package.json/lockfile pair — its pnpm source-of-truth lock lives atcommon/config/rush/pnpm-lock.yaml(plus one per subspace undercommon/config/subspaces/<name>/).scan --mode hosteddiscovers those locks whenrush.jsonis present and repoints them in place (the pnpm rewriter is now basename-generalized, so nested locks rewrite path-generically). Editing a Rush lock outsiderush updatedesyncs thepnpmShrinkwrapHashincommon/config/rush/repo-state.json, so aredirect_rush_repo_state_stalewarning fires when a lock was touched and that file exists —rush installfails underpreventManualShrinkwrapChangesuntilrush updaterefreshes it, but the redirect survives the refresh (pnpm keeps locked resolutions for unchanged specifiers). Agent mode already works through Rush's generated project symlink farm; vendored mode is refused (vendor_rush_unsupported) becauserush installcopies the lock intocommon/temp, so vendor's relativefile:specs can't survive — the refusal routes to hosted mode. -
pnpm hosted rewriter generalized to nested lockfiles. The
pnpm-lock.yamlrewriter now matches anypnpm-lock.yamlat the project root OR at any nested path (*/pnpm-lock.yaml), so Rush subspace locks and other nested-lock layouts are rewritten in place under their repo-relative keys. Write-back and confirmed-redirect gating are path-generic. -
Golang hosted mode is a documented NO-GO. Hosted redirect for Go is deliberately unsupported — sumdb hard-fails the patched pseudo-version on every day-2 machine and the only escapes are uncommittable machine-local config; Go's module-path identity would force per-grant artifacts against the build-once converter; and the default
GOPROXYchain would leak licensed bytes / tokened URLs to the public mirror. The full analysis lives indocs/ecosystems.md#go-directory-replaces-and-gosum; both the CLI rewriter and the depscan backend twin emitredirect_golang_unsupportednaming the remedy (use vendored mode, which gives Go everything hosted promises elsewhere). The one sanctioned exception — an ephemeral-CI GOPROXY recipe — is documentation-only and never written into a repository. -
vendornow supports every major npm and pypi package manager. The npm ecosystem gained four lockfile flavors beyondpackage-lock.json— yarn classic (yarn.lockv1), yarn berry with the node-modules linker (resolutions+ a cache-zip10c0checksum reproduced offline from the vendored tarball), pnpm (pnpm.overrides+pnpm-lock.yamlsurgery, pnpm 9 & 10), and bun (bun.lock) — all sharing the one vendored tarball and selected by a content-sniffing probe (yarn-berry PnP and bun's binarybun.lockbare refused with pointers to the native flow). The pypi ecosystem gained poetry, pdm, and pipenv (lock-only[[package]]/ entry splices, like the existing uv/requirements flavors). Every lockfile checksum/reference field for a vendored package is now recomputed coherently (the v2 "update checksums and references" directive); the gem backend handles bundler ≥ 2.6's optionalCHECKSUMSsection; composer'sdist.referencecarries the patch UUID intoinstalled.json. Each flavor has a real-package-manager build-proof capstone (fresh-checkout, cold-cache, strictest-install —--frozen/--immutable/--deploy/--locked— with byte-identical revert).vendor --force/--revertaccept empty env vars (SOCKET_FORCE=) as false, matching the global-flag contract. -
New
vendorsubcommand: committable vendoring of patched dependencies. Whereapplypatches installed packages in place (machine-local state),socket-patch vendorejects each patched package into a committed.socket/vendor/<ecosystem>/<patch-uuid>/<artifact>and rewires the ecosystem's lockfile so the project consumes the vendored copy — after committing, a fresh checkout builds with the patched dependency on machines with no socket-patch installed and no Socket API access. Per ecosystem (each mechanism validated against the real package manager): npm rewritespackage-lock.jsononly (deterministic patched tarball, recomputed integrity,npm ci-verified); cargo writes a[patch.crates-io]entry in.cargo/config.tomlplus surgical Cargo.lock edits socargo build --locked --offlineworks; golang reuses thereplace-directive engine pointed at the vendor tree; composer rewrites the lock entry to adist: pathcopy; gem edits the Gemfile + Gemfile.lock pair in bundler's canonical form; pypi rebuilds a valid wheel (regenerated RECORD) wired through uv'spyproject.toml/uv.lockpair (uv-first) or requirements.txt (pip/uv pip). The patch UUID is recoverable from the lockfile path string alone (a documented convention for external tools), a committed.socket/vendor/state.jsonledger records the verbatim original lockfile fragments, andvendor --revertrestores them byte-exactly.vendor --vexmirrorsapply --vex; VEX generation attests vendored patches by hashing the committed artifacts, andapplyyields ownership of vendored packages (vendoredskip reason). -
Cargo support (
cargois now a default feature).applypatches a Rust dependency in place wherever the crawler finds it — the projectvendor/directory or the shared$CARGO_HOMEregistry cache — rewriting the crate's.cargo-checksum.jsonsidecar socargo buildaccepts the modified files.rollbackrestores the original bytes from thebeforeHashblobs, like npm/PyPI/gem.cargoships on by default (alongside the always-on npm + PyPI- Ruby gems support), so released binaries and a plain
cargo install socket-patch-clipatch Rust dependencies out of the box;maven/composer/nuget/denoremain opt-in.
- Ruby gems support), so released binaries and a plain
-
Project-local Go
replace-redirect backend (golang, default feature). The Go module cache is shared, read-only and checksum-verified, so in-place patching would failgo.sumat build time. Insteadapplywrites a project-local patched copy under.socket/go-patches/<module>@<version>/and a managedreplacedirective in the projectgo.mod, so the patch is project-scoped and the cache stays pristine for sibling projects.rollbackcleanly drops thereplacedirective + copy.apply --checkis a read-only, lock-free, offline auditor that verifies the committed redirects match the manifest, exiting non-zero on drift (for CI / GitHub-App use). -
Inline OpenVEX generation on
applyandscanvia--vex <path>. A single successfulapply/scancan now both patch and emit the OpenVEX 0.2.0 attestation, instead of requiring a separatesocket-patch vexstep. The--vex-product/--vex-no-verify/--vex-doc-id/--vex-compactflags mirror the standalonevexknobs (and reuse theSOCKET_VEX_*env vars). The document is always written to the given path (never stdout, so it never races--json), built from the post-run manifest and verified against on-disk state. JSON output gains a top-levelvexsummary ({ path, statements, format }). A requested-but-failed VEX makes the command exit non-zero even when the apply/scan itself succeeded, surfacing a stable error code in the envelope.
-
install.shcan install without reaching github.com. NewSOCKET_PATCH_BASE_URLpoints the archive downloads at any releases base that answers GitHub's two asset paths — notablyhttps://install.socket.dev/patch/SocketDev/socket-patch/releases, which relays them from the GitHub release, so one URL template covers either origin. A new release needs no publish for this: the origin resolves "latest" per request.socket-patch --updatecan use the same host today through theSOCKET_UPDATE_BASE_URLoverride it already has. Also new:SOCKET_PATCH_INSTALL_DIRto choose the install directory explicitly instead of taking/usr/local/binor~/.local/bin. The default download origin is still GitHub — seedocs/installer-hosting.md. -
The documented one-liner installs from
https://install.socket.dev/patch. The previous URL wasraw.githubusercontent.com, which asks users to trust a third-party CDN for a script they pipe into a shell and is the first URL a locked-down egress policy blocks. The hosted copy is byte-for-bytescripts/install.sh, with its SHA-256 published atinstall.socket.dev/patch.sha256; the GitHub raw URL keeps working and serves the same bytes. Binaries are still downloaded from the GitHub release and verified against itsSHA256SUMS— the trust model is unchanged, only the script's origin moved. New:docs/installer-hosting.md(how the copy is published), a CI step that runs the installer end to end instead of only linting it, and aninstaller-driftworkflow that checks the hosted copy against this repository weekly. -
Maven and NuGet promoted to fully available — the
SOCKET_EXPERIMENTAL_MAVEN/SOCKET_EXPERIMENTAL_NUGETruntime gates are retired. Every flow (scanin all modes,apply,get,rollback,vendor,repair,vex,setup) now discovers and patches installed Maven and NuGet packages unconditionally; the "N patch(es) skipped — support is experimental" warnings are gone, and the previously#[ignore]d maven/nuget dispatch e2e tests now gate CI. Setting the old env vars is harmless but does nothing. Behavior notes: a defaultscannow walks the local Maven repository (~/.m2/MAVEN_REPO_LOCAL) and the NuGet caches, andscan --prune/--syncnow judges maven/nuget manifest entries like any other ecosystem's (previously they were exempt from pruning while the gate was closed). The in-place sidecar caveat is unchanged and now documented per mode indocs/ecosystems.md: agent-mode patching leaves Maven's.jar.sha1/.jar.md5stale and NuGet's fixup deletes.nupkg.metadata+ advises on.nupkg.sha512; the vendored/hosted modes never touch the caches. -
Release workflow consolidated into a single
release.yml. One dispatch now publishes every package — crates.io, npm, PyPI, and RubyGems (both gems), all via OIDC trusted publishing — with the launcher-gem job gated on the GitHub release existing. The separaterelease-ecosystems.ymlworkflow is removed (itsrelease: publishedtrigger never fired: the release is created withGITHUB_TOKEN, which suppresses downstream workflow events). The CLI is distributed via GitHub releases, npm, PyPI, crates.io, and RubyGems only — the Composer/Packagist, Maven Central, and NuGet launcher channels drafted earlier in this cycle were dropped before ever shipping in a release. -
--api-url/--proxy-urlno longer carry clap-level defaults: with neither flag nor env var set they parse as unset and the documented default URLs are applied at API-client construction (after the socket-cli config layer). Observable behavior is unchanged unless a socket-cli login exists. -
All ecosystem feature flags removed — every ecosystem is always compiled in. The
cargo,golang,maven,composer,nuget, anddenoCargo features are gone from both crates; npm, PyPI, Ruby gems, Go, Cargo, NuGet, Maven, Composer, and Deno support is now unconditional. Builds that passed--features <eco>will get an "unknown feature" error and should simply drop the flag;--no-default-featuresno longer produces a minimal binary (there is nothing left to strip). TheSOCKET_EXPERIMENTAL_MAVEN/SOCKET_EXPERIMENTAL_NUGETruntime gates outlived this entry only briefly — they are retired in the same release (see the "promoted to fully available" entry under Changed). The only remaining features are the test-suite gatesdocker-e2eandsetup-e2eonsocket-patch-cli. (MAJOR for anyone scripting--features; no behavior change for default builds beyond composer/deno support now being present.) -
Token-less
scannow batch-queries the public proxy. Proxy-mode scans POST{proxy}/patch/batch(one request per--batch-sizechunk, mirroring the authenticated/v0/orgs/{slug}/patches/batchendpoint) instead of issuing oneGET /patch/by-package/:purlper package. The client transparently degrades to the legacy per-package GET path against proxies that predate the batch endpoint, and when the all-or-nothing batch validation rejects a chunk (e.g. a crawled PURL type the server doesn't recognize, such aspkg:jsr/…— per-package queries tolerate those individually, so one exotic package can't fail a whole scan). Rate limits and over-capacity 503s still surface instead of silently degrading. (MINOR)
-
bun 1.4 lockfiles are accepted again. bun 1.4.0 bumped
bun.locklockfileVersionto 2 while leaving the emitted grammar unchanged; the shared version gate refused everything but 1, so hosted and vendored modes refused every lock written by bun ≥ 1.4 (redirect_bun_lock_unsupported/vendor_lockfile_version_unsupported). The gate now accepts versions 1 and 2 and keeps failing closed on anything else; new shared golden fixturenpm/bun/lock-v2(the depscan TS twin needs the matching acceptance + fixture sync). -
gem: every coexisting installed copy is patched. Bundler's scoped
<engine>/<abi>/gemsand flatgems/stores can coexist under oneBUNDLE_PATHroot, each holding a real copy of the same gem@version; first-wins resolution patched one store and reported success while the other bundler loaded pristine (vulnerable) bytes.applynow fans out per copy (per-copyAppliedevents, each counted insummary.applied), bundle-path roots are crawled in bundler precedence order, and config-sourced roots are contained. Thegem_bundle_config_path_ignoredwarning also prints the skipped path verbatim instead of backslash-escaped. -
npm
@socketsecurity/socket-patch: the./schemaexport is now built at publish. The subpath pointed at a gitignoreddist/directory that nothing built during release, so it shipped broken; aprepackscript now compiles it as part ofnpm publish. -
Release workflow tag-guard and idempotency fixes. The tag-already-exists guard never fired (it ran
git rev-parsein a shallow, tagless checkout) — it is now a statelessgit ls-remotecheck that still permits same-commit retries; the GitHub-release step re-runs cleanly instead of hard-failing when the release already exists; and the cargo/PyPI/gem publish jobs skip already-published versions, so "Re-run failed jobs" can resume a partial release safely. -
NuGet hosted rewriter: creating a
packageSourceMappingfrom scratch now emits a catch-all for pre-existing sources.packageSourceMappingis exclusive — once ANY mapping exists, every package must match some source's pattern or restore hard-fails NU1100. A redirect into anuget.configwith no prior mapping previously routed only the patched id, breaking every OTHER package's restore; the rewriter now fans a<package pattern="*" />mapping out to each pre-existing package source (longest-prefix match still routes the patched id to the Socket source). Golden fixtures updated on both the Rust and TS sides. -
VEX now attests Go
replace-redirect patches.socket-patch vexpreviously verified golang patches against the pristine module cache instead of the patched.socket/go-patches/copy, so redirect-applied patches were silently omitted from the document (reportednot_applied, orpackage_not_foundon cache-less CI). Verification now follows the managedreplacedirective to the committed copy. -
repairon a hosted-only project is an informational no-op. Hosted (--mode hosted) mode leaves no local artifacts to repair — the lockfiles point atpatch.socket.devURLs, and there is no manifest or vendor ledger. A project whose only.socket/trace isredirect-state.json(no manifest, no vendor ledger, no vendored lockfile references) previously errored withmanifest_not_found(exit 1); it now exits 0 with aredirect_only_projectskip pointing atscan --mode hosted. Repair still errors on a bare directory with no traces at all.
A repo-wide correctness, security, and filesystem-safety hardening pass: every
source file in both crates was reviewed line by line, the bugs found were fixed,
and regression tests were added throughout (the lib + integration suites grow by
~10k lines of mostly tests). The audit harness used to drive the review lives in
scripts/study-crates.ts.
- Path-traversal in archive extraction.
read_archive_to_map(patch/package.rs) validated the raw tar entry path but returned thepackage/-stripped path, so an entry likepackage//etc/passwdpassed every check and then resolved to an absolute/etc/passwdthatPath::joinwrites outside the package tree. Validation now runs on the normalized path actually written to disk. - Unbounded preallocation from an untrusted delta header.
apply_diff(patch/diff.rs) reserved aVecsized from the bsdiff target-size header, which qbsdiff never validates — a tiny hostile delta could claim up toi64::MAXand abort the process. The hint is now clamped to 64 MiB. - Evidence-free VEX attestation.
verify_patch_record(vex/verify.rs) returnedappliedfor a patch touching zero files, producing anot_affectedstatement with no on-disk evidence; zero-file records are now omitted (no_files).
applycould not write into read-only directories (Go module cache marks dirs0o555); added aDirWriteGuardthat temporarily grants write on the parent dir around the CoW-break + atomic rename and restores its exact mode.applystripped setuid/setgid bits on every patched file becausechownran afterchmod; reordered to chown-before-chmod, plus a parent-dirfsyncso the rename survives a crash.- Non-atomic symlink break (
patch/cow.rs) removed the file before staging its replacement, destroying it with no rollback on a failed write; now rename-over the link, matching the hardlink path. Stage files are cleaned up on every error arm. rollbackused an unsafe in-place write; it now delegates to the hardenedapply_file_patch(atomic, CoW-safe, validate-before-write, permission restore). Also: a GC'd before-blob no longer shadows the already-original short-circuit, and new-file deletion works inside read-only directories.- Hash integrity:
compute_file_git_sha256(patch/file_hash.rs) opened and stat'd the path separately (TOCTOU) and never checked the target was a regular file (a directory hashed as the empty blob); now opens once, fstats the descriptor, and rejects non-regular files.compute_git_sha256_from_readernow errors when the streamed byte count disagrees with the declared size. - Sidecar writes in read-only caches: the cargo
.cargo-checksum.jsonrewrite and the NuGet.nupkg.metadatadelete used bare, non-atomic I/O that failedEACCESin the locked-down registry trees they exist to serve; both now go through the hardened write/DirWriteGuardpaths. - Blob cleanup (
utils/cleanup_blobs.rs) aborted the whole sweep on one dangling symlink and inflated the "checked" count with subdirs/dotfiles; now usessymlink_metadata, skips stat errors, and counts only real blobs. - Lock acquisition (
patch/apply_lock.rs) mapped everyflockerror toHeld(maskingENOLCK/EACCES/unsupported-FS and busy-waiting through the whole timeout) and overshot sub-100 ms waits; genuine faults now surface immediately and the sleep is clamped to the remaining budget.
- Composer: normalize the
v-prefixedinstalled.jsonversion against bare PURLs, tolerate a single malformed entry instead of dropping the file, and skip packages absent on disk. - Go: only skip
cache/at the module-cache root (not at any depth), decode/encode case-escaped versions (v1.0.0-RC1↔…-!r!c1), treatGOPATHas a path list, and reject malformed/emptymoduledirectives. - npm: follow symlinked directories during the global-fallback walk
(
DirEntry::metadata()doesn't follow links) and guard nested recursion so it doesn't descend through symlinked packages. - NuGet: lowercase the version directory (not just the id) when resolving the global packages folder, so prerelease-cased versions resolve.
- Python: the macOS framework
Versions/layout uses bare3.11dirs, and a package with missing/malformedMETADATAnow falls back to its<name>-<version>.dist-infodirectory name instead of vanishing. - Deno: correct the macOS cache path (
~/Library/Caches/deno), honorXDG_CACHE_HOMEon Linux, and treat an emptyDENO_DIRas unset. - Maven: strip XML comments before tag matching and handle self-closing / inline skip-sections so a commented or oddly-formatted POM can't leak a plugin's coordinates as the project's.
- Cargo: tolerate
[package]headers with comments/whitespace and split<name>-<version>dirs at the dotted version (handles numeric pre-releases). - Shared:
utils/fs::entry_is_dirnow follows symlinks, fixing symlinked package-dir discovery across every dir-walking crawler at once.
- API client: honor a
--proxy-urloverride on binary downloads (was re-derived from env), and make org selection, patch titles, and the individual-query batch capability flag deterministic / order-independent; hash comparison is now case-insensitive. - Version reporting:
USER_AGENTand telemetrycontext.versionwere hardcoded to1.0/1.0.0; both now derive fromCARGO_PKG_VERSION. applyno longer emits a spuriousFailedenvelope event for a release-variant whose first file isNotFound.- UTF-8 safety:
get/scan/removetruncated display strings with raw byte slices that panic on multi-byte API text; all use char-safe truncation. - Exit codes:
setupnow exits non-zero (notalready_configured) when apackage.jsonfails to parse, andrepairexits non-zero and fires failure telemetry on a partial download failure (also gates the offline dry-run "would download" event and threads throughbytes_freed). rollbackno longer miscounts zero-file records as already-original or double-counts no-ops in dry-run;unlockreportsreleasedfrom a pre-acquiresnapshot so a probe-created lock file isn't reported as removed.vexresolves qualified PyPI/Gem/Maven PURLs via the rollback-aware resolver so those patches are no longer dropped aspackage_not_found.package.jsonhandling: no longer panics on a non-object root or non-objectscripts, de-dups overlapping workspace patterns, handles bare*/**/deep globs, strips inline YAML comments, and preserves top-level key order (enabledserde_json'spreserve_order).- Smaller fixes: deterministic
listoutput ordering, case-insensitivefuzzy_matchtie-break,json_envelopestatus-invariant enforcement +oldUuidfield,lock_clisub-second timeout message, blob-fetcher all-skipped formatting, VEXStatement.timestampmade optional per OpenVEX 0.2.0, and VEX git-remoteurlparsing.
- Hundreds of regression tests added across the patch engine, crawlers, API
client, manifest,
package.json, VEX, and CLI command layers; the stalerepair/python_crawlere2e expectations were updated to the corrected contracts. Full suite green (--features cargo). - Added the
scripts/study-crates.tsper-file audit harness (with an example prompt config) used to drive this review.
-
Telemetry coverage for read-side + housekeeping + attestation commands.
scan,get,list,setup,repair,unlock, and the newvexcommand each emit apatch_<action>(and matching*_failed) event through the existing send path, joining the apply/remove/rollback trio that already shipped. Thescanevent carries per-tier counts (free_patches/paid_patches/can_access_paid), the ecosystems filter, and afallback_to_proxyflag;getcarriesuuid/tier/ecosystem/download_mode/fallback_to_proxy. -
scan+getautomatically fall back to the public proxy on 401/403 from the authenticated endpoint. A stale or revoked token no longer blocks access to free patches — the CLI logs a warning to stderr, swaps to the proxy, retries once, and tags the resulting telemetry event withfallback_to_proxy: true. The classifier is deliberately narrow: 404, 5xx, network, and rate-limit errors do NOT trigger fallback so backend issues stay visible.apply/remove/rollback/vexkeep their fail-loud semantics. -
SOCKET_OFFLINE(airgap mode) now disables telemetry universally.is_telemetry_disabled()honors the sameSOCKET_OFFLINE=1|truesignal--offlineuses for network suppression, so apply (and every future command) no longer attempts a 5-second telemetry POST againsthttps://api.socket.devwhen the operator explicitly requested airgap.
- New
tests/cli/telemetry_e2e.rsend-to-end behavioral coverage: apply/scan/get/list emit telemetry against a wiremock recorder;SOCKET_OFFLINE=1produces zero telemetry POSTs across all four; scan falls back on 401 + tags the resulting event; scan does NOT fall back on 500 (conservative classifier). - New
scan_invariantscases for the patch-management lifecycle: withdrawn patches keep their entry when the package is still installed but API is silent; entries for uninstalled packages get pruned;scanwithout--applyis read-only against the manifest and blobs even when an update is detected.
-
--offlinesemantics unified to strict airgap on every subcommand. Previously meant three different things acrossapply(strict airgap),repair(skip downloads / cleanup-only), androllback(fail when blobs missing). All three now mean the same thing: never contact the network, fail loudly when a required local source is missing. -
repair --download-modedefault changed fromfiletodiffto match every other subcommand. Users who need the legacy per-file blob behavior must now opt in with--download-mode file. -
repair --offlineis mutually exclusive with--download-only— passing both exits with code 2. -
Env vars renamed. The three remaining
SOCKET_PATCH_*env vars now use theSOCKET_*prefix:SOCKET_PATCH_PROXY_URL→SOCKET_PROXY_URLSOCKET_PATCH_DEBUG→SOCKET_DEBUGSOCKET_PATCH_TELEMETRY_DISABLED→SOCKET_TELEMETRY_DISABLED
The legacy names are still honored at runtime but emit a one-shot deprecation warning to stderr (the warning fires even under
--silentand--jsonbecause the transition signal must reach scripts and CI logs). Legacy names will be removed in v4.
- Shared
GlobalArgsclap struct#[command(flatten)]-ed into every subcommand. Every flag is now accepted on every subcommand (silently no-op'd where the subcommand doesn't consume it). Every flag has a matchingSOCKET_*env-var binding with precedenceCLI arg > env var > default. SeeCLI_CONTRACT.mdfor the full global-arguments table. applyandrepairaccept--api-url,--api-token,--orgvia the global flatten (previously env-var only — telemetry would silently fall back to the public proxy when the CLI was the only way to set these).- New global flags
--debugand--no-telemetry, promoted from env-only toggles. --proxy-url(env:SOCKET_PROXY_URL) as an explicit CLI knob for the public patch proxy.- New CI guard in the
Releaseworkflow: the workflow fails before tag creation ifCHANGELOG.mdlacks an entry for the version inCargo.toml. Blocks every downstream publish (cargo, npm, pypi).
- Garbage collection moved out of
apply. Usescan --prune,scan --sync, orrepair/gcinstead.applyis now strictly non-mutating against.socket/: when blobs need to be fetched they go to a temp overlay; the persistent cache is never written to. - Unified JSON envelope (
command/status/events/summary) forapply,list,remove,repair. Other subcommands keep their pre-v3 ad-hoc shapes for now; seeCLI_CONTRACT.mdfor migration status.
- Release workflow tolerates already-published npm packages so a partial publish can be retried without re-tagging.
- Pin Node
22.22.1in the release workflow to dodge a broken upstream npm.
- Harden core error handling, blob verification, and
--forcereporting. - Surface
find_by_purlserrors instead of silently swallowing them. - Add diagnostics to
applyfor silent no-op failures in CI. - Add explicit Node typings for TypeScript 6 compatibility in the npm wrapper.
- Simplify release to
workflow_dispatchonly (no bot commits). - Split release into PR-based version prep + auto-publish on dispatch.
- Prioritize
pnpm-workspace.yamldetection and restrictsetupto rootpackage.jsonfor pnpm monorepos. - Harden GitHub Actions workflows per
zizmoraudit. - Unflag Ruby gem (
gem) support and add e2e bundler tests. - Use
npx @socketsecurity/socket-patchfor the generated postinstall command.
- Full glibc/musl support across all Linux architectures (16 platform combinations now published per release).
- Interactive prompts and smart patch selection when multiple patches match a query.
- Ensure the binary has execute permission in the PyPI wrapper.
- Restore
binandoptionalDependenciesto the npm wrapperpackage.json.
- Expand ecosystem support: rough-in for composer, go, maven, nuget, ruby.
- Add a TypeScript schema library to the npm wrapper.
- Treat empty
SOCKET_API_TOKENas unset.
- Maintenance release.
- Maintenance release (version sync).
- Switch to per-platform
optionalDependenciesfor the npm package. - Add macOS global-package crawling fallbacks and pyenv support.
- Add support for more platforms; fix pypi and npm publish flows.
- Fix trusted publishing setup for npm and PyPI.
- Update PyPI publish action and add npm provenance permissions.
- Fix action image references in the publish workflow.
- Add
apply --force; rename--no-applyto--save-only(the old name remains as a hidden alias). - Cargo/Rust crate patching support behind a feature flag.
- Auto-resolve org slug from API token when
SOCKET_ORG_SLUGis unset.
- Fix publish workflow to checkout the bumped version.
- Pin GitHub Actions to full commit SHAs and wire up version-bump support in the publish workflow.