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.
The Release workflow refuses to publish a version that does not appear
in this file — see .github/workflows/release.yml (version job).
-
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; in-place agent apply for both remains runtime-gated (SOCKET_EXPERIMENTAL_NUGET=1/SOCKET_EXPERIMENTAL_MAVEN=1— sidecar corruption risk) while the committable vendor path is safe. 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/design/golang-hosted-no-go.md; 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.
- 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)
-
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/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.