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).
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.