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.
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.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.- 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 asaction: "failed"witherrorCode/error, count it infailed, and count only patches that really applied inapplied. 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 lists every granted patch inredirect.patches[]as 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.
See ecosystem support and the compatibility guides for format boundaries, integrity limits, and required install commands.
-
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).
-
npm family.
- 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).
- Hosted scans pin npm aliases in a v2 lock's npm 6
-
Cargo. Hosted runs from a Cargo workspace member are refused (naming the root) instead of rewriting the member as a lockless project (#417).
-
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).
- 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).
-
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.
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.