Skip to content

chore(release): bump version to 4.0.0 - #228

Merged
Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
release/v4.0.0
Aug 21, 2026
Merged

chore(release): bump version to 4.0.0#228
Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
release/v4.0.0

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Stamps 4.0.0 into every packaging site (scripts/version-sync.sh) and
rolls CHANGELOG.md's [Unreleased] notes into the ## [4.0.0] section.
CI's release-readiness job verifies the chores on this PR; the Release
workflow re-verifies them before anything publishes.

Also new in the CHANGELOG: an intro at the top of the 4.0.0 section
describing the three patch-application modes (agent / vendored / hosted)
and their v4.0 ecosystem coverage, and the (BREAKING — lands in v4.0)
heading qualifiers trimmed to (BREAKING) now that this IS v4.0.

Validation (local, on this exact tree)

  • cargo test -p socket-patch-core: green (exit 0).
  • cargo test -p socket-patch-cli: green except e2e_redirect_bun_build
    (2 failures) — pre-existing toolchain drift, not caused by this PR:
    bun 1.4.0 now writes bun.lock lockfileVersion: 2, and the hosted
    rewriter fail-closes on anything ≠ 1
    (redirect_bun_lock_unsupported, patch/redirect/mod.rs:2456).
    Reproduced standalone with bun install --save-text-lockfile on a
    scratch project. Follow-up: support lockfileVersion 2 (or pin bun < 1.4
    in the suite).
  • Docker e2e matrix (all 9 ecosystems + vendor capstones) and the live
    production hosted/vendored suites are running against this tree; results
    will be posted as a comment on this PR.

Release notes (rolled over from [Unreleased])

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): apply patches
    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 hooks setup
    configures. Requires socket-patch (and Socket API access, or pre-fetched
    blobs) on every machine that installs dependencies.

  • Vendored mode (vendor, or scan --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, so vendor --revert restores 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 a goproxy registry 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).

Removed (BREAKING)

  • The unlock subcommand. Folded into repair, which now deletes the
    leftover <.socket>/apply.lock file as its final housekeeping step (skipped
    under --dry-run, refused with lock_held while 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 — so unlock's inspect
    path had no recovery scenario, and its --release file deletion is now
    automatic. Migration: unlock --releaserepair; the probe-style
    "is anything holding the lock?" check → run the mutating command (optionally
    with --lock-timeout) and branch on errorCode: lock_held.
    SOCKET_UNLOCK_RELEASE is gone with the subcommand, and the
    patch_unlocked / patch_unlock_failed telemetry events are retired.
  • The global --break-lock flag and SOCKET_BREAK_LOCK env 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 a
    lock_broken audit event for a reclaim that plain acquisition performs
    anyway. The lock_broken warning event and rollback's warnings[]
    lock_broken entry are no longer emitted (warnings stays present, now
    always empty). The lock_held stderr hint now advises waiting /
    --lock-timeout instead of pointing at the removed commands.

Changed (BREAKING)

  • --help command order is now workflow-first: scan, apply, vex,
    vendor, setup, then rollback, get, list, remove, repair.

Added

  • Hosted mode for Go (free tier). scan --mode hosted now redirects
    golang dependencies when the reference carries a goproxy registry
    override: a fork-style
    replace <mod> <ver> => patch.socket.dev/gopatch/<uuid> <ver>-socketpatch.<n>
    in go.mod plus the socket module's two h1: lines in go.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 from go.sum, so the committed pair is the whole redirect —
    validated end-to-end in e2e_golang_hosted_build.rs (fresh caches, bogus
    GOSUMDB tripwire, go mod tidy byte-level no-op, tampered-hash
    SECURITY ERROR). Fails closed (per-dep redirect_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 historical redirect_golang_unsupported
    warning (paid tier stays vendored — see docs/design/golang-hosted.md).
    Wire schema gains integrity.goModH1 and
    registryOverride.identifiers.goModuleVersion (additive). Requires
    server-side publication of the grant-free gopatch artifact 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> --pr performs the whole bump chore —
    stamps every packaging site via version-sync.sh, rolls [Unreleased]
    into a dated ## [X.Y.Z] CHANGELOG section, and opens the release/vX.Y.Z
    PR (also dispatchable from the Actions tab as the Version Bump
    workflow). A new release-readiness CI job runs scripts/release-lint.sh
    on 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. The Release workflow's version job 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 published
    SHA256SUMS before extraction, sanity-execs the staged binary, and
    atomically swaps it in place (Windows uses the rename-dance via
    self-replace; a setuid/setgid install is refused). --update 3.4.0
    (or SOCKET_PATCH_VERSION) pins a version, up or down; bare --update
    never downgrades; --force reinstalls. --dry-run is a check-only
    probe (zero downloads, updateAvailable in the --json details).
    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; --force
    overrides. --offline refuses up front and --force cannot 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 with SOCKET_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 --update refreshes the notice's cache.

  • shellcheck scripts/install.sh in CI (and a fix for the SC2144
    glob-with--e musl-loader probe it found).

  • socket login now 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 for apiToken,
    defaultOrg, and apiBaseUrl: precedence per key is CLI flag > env var

    socket-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 canonical SOCKET_* names win. Two new
    env-only toggles: SOCKET_NO_API_TOKEN ignores ambient tokens (env +
    config; an explicit --api-token still authenticates) and
    SOCKET_NO_CONFIG disables the config layer. A corrupt config file
    warns once on stderr and is ignored; --json stdout is unaffected. The
    telemetry endpoint now resolves the API base through the same chain as
    client construction, so a config-supplied apiBaseUrl applies to both.
    Design notes: docs/design/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), scan rewrites 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 rewrites
    package-lock.json/npm-shrinkwrap.json resolved+integrity (v2 legacy
    dependencies mirror included), pnpm-lock.yaml inline resolutions, and
    yarn classic resolved/integrity blocks; pypi rewrites requirements.txt
    pins to name @ <url> --hash=sha256:… (pip-compile continuation lines are
    refused rather than corrupted) and uv.lock wheel entries; cargo defines a
    per-patch sparse registry in .cargo/config.toml plus Cargo.toml
    registry = keys and Cargo.lock source/checksum surgery; composer
    rewrites the lock entry's dist url/shasum; nuget adds a nuget.config
    source + packageSourceMapping and repins packages.lock.json
    contentHash; gem adds a per-dep source block + a CHECKSUMS pin
    (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 under
    tests/fixtures/redirect/. JSON output gains a redirect sub-object with
    mode: "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 --mode with a boolean of a DIFFERENT mode is a
    usage error (exit 2); the same mode spelled both ways is accepted, and
    --detached now requires vendored mode in either spelling.

  • VEX support for hosted mode: the (redirected) provenance marker + the
    redirect ledger.
    scan --mode hosted persists 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-run scan --mode hosted --vex
    attests confirmed redirects from the ledger WITHOUT hash verification (the
    bytes are fetched at install time; the JSON vex summary carries
    verified: false), while a post-install socket-patch vex reads the ledger
    back and hash-verifies the redirected patches against the installed tree.
    A confirmed redirect whose record fetch failed surfaces a
    record_fetch_failed warning (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 a nuget.config source +
    packageSourceMapping and a packages.lock.json contentHash repin —
    dotnet restore --locked-mode then fails NU1403 on tamper. Maven: the uuid
    dir is a committed maven2 file:// repository (rebuilt .jar + the
    verbatim upstream pom so transitives survive + .sha1 sidecars), wired via
    a pom.xml <repository> with checksumPolicy=fail; multi-module
    aggregator poms (vendor_maven_multimodule_unsupported) and gradle-only
    projects (vendor_gradle_unsupported) are refused fail-closed, and the
    always-on vendor_maven_local_cache_shadow advisory carries the
    mvn dependency:purge-local-repository one-liner (a warm ~/.m2 copy
    silently shadows any repository). Both are proven by docker capstones
    against the real .NET SDK / Apache Maven (cold-cache, --network none,
    RED + TAMPER probes). nuget and maven are now DEFAULT compile features;
    the SOCKET_EXPERIMENTAL_NUGET / SOCKET_EXPERIMENTAL_MAVEN runtime
    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 covers nuget and maven dirs,
    and --vendor-source prebuilt 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 injected
    socket-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.config resolver args (originAware=false,
    failIfMissing=false) + .mvn/checksums/checksums.sha256 entries 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 a redirect_maven_same_gav_fallback warning (NOT fail-closed:
    a Socket-repo failure falls back to the unpatched artifact). Gradle build
    scripts are never edited: a present build.gradle* / settings.gradle*
    emits a paste-able exclusiveContent snippet 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 and checksum: becomes the precomputed yarnBerry10c0 cache-zip
    sha512 — leaving the descriptor key and package.json untouched, so yarn install --immutable --check-cache passes and tamper fails YN0018. Whole-file
    gates refuse a cacheKey ≠ 10c0 or a .yarnrc.yml compressionLevel ≠ 0
    (redirect_yarn_berry_cache_unsupported) — no offline-reproducible checksum.
    Validated e2e against real corepack yarn@4.12.0 on the node-modules linker;
    PnP is not exercised for hosted (the lock rewrite fires, but PnP's
    .yarn/cache resolution is untested). bun (text bun.lock v1): 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-lockfile then installs the
    hosted bytes and tamper fails the integrity check. A binary bun.lockb with
    no text lock is auto-migrated first via the user's own bun install --save-text-lockfile --frozen-lockfile --lockfile-only (deletes bun.lockb,
    recorded as a removed ledger edit, offline, fails closed;
    redirect_bun_lockb_would_migrate on --dry-run,
    redirect_bun_lockb_unsupported if 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 at
    common/config/rush/pnpm-lock.yaml (plus one per subspace under
    common/config/subspaces/<name>/). scan --mode hosted discovers those
    locks when rush.json is present and repoints them in place (the pnpm
    rewriter is now basename-generalized, so nested locks rewrite path-generically).
    Editing a Rush lock outside rush update desyncs the pnpmShrinkwrapHash in
    common/config/rush/repo-state.json, so a redirect_rush_repo_state_stale
    warning fires when a lock was touched and that file exists — rush install
    fails under preventManualShrinkwrapChanges until rush update refreshes 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)
    because rush install copies the lock into common/temp, so vendor's
    relative file: specs can't survive — the refusal routes to hosted mode.

  • pnpm hosted rewriter generalized to nested lockfiles. The
    pnpm-lock.yaml rewriter now matches any pnpm-lock.yaml at 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 GOPROXY chain would leak
    licensed bytes / tokened URLs to the public mirror. The full analysis lives
    in docs/design/golang-hosted-no-go.md; both the CLI rewriter and the
    depscan backend twin emit redirect_golang_unsupported naming 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.

  • vendor now supports every major npm and pypi package manager. The npm
    ecosystem gained four lockfile flavors beyond package-lock.json — yarn
    classic (yarn.lock v1), yarn berry with the node-modules linker
    (resolutions + a cache-zip 10c0 checksum reproduced offline from the
    vendored tarball), pnpm (pnpm.overrides + pnpm-lock.yaml surgery, 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 binary
    bun.lockb are 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 optional CHECKSUMS section; composer's
    dist.reference carries the patch UUID into installed.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/--revert accept empty env vars
    (SOCKET_FORCE=) as false, matching the global-flag contract.

  • New vendor subcommand: committable vendoring of patched dependencies.
    Where apply patches installed packages in place (machine-local state),
    socket-patch vendor 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. Per ecosystem
    (each mechanism validated against the real package manager): npm rewrites
    package-lock.json only (deterministic patched tarball, recomputed
    integrity, npm ci-verified); cargo writes a [patch.crates-io] entry in
    .cargo/config.toml plus surgical Cargo.lock edits so cargo build --locked --offline works; golang reuses the replace-directive engine
    pointed at the vendor tree; composer rewrites the lock entry to a
    dist: path copy; gem edits the Gemfile + Gemfile.lock pair in bundler's
    canonical form; pypi rebuilds a valid wheel (regenerated RECORD) wired
    through uv's pyproject.toml/uv.lock pair (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.json ledger records the verbatim original
    lockfile fragments, and vendor --revert restores them byte-exactly.
    vendor --vex mirrors apply --vex; VEX generation attests vendored
    patches by hashing the committed artifacts, and apply yields ownership of
    vendored packages (vendored skip reason).

  • Cargo support (cargo is now a default feature). apply patches a Rust
    dependency in place wherever the crawler finds it — the project vendor/
    directory or the shared $CARGO_HOME registry cache — rewriting the crate's
    .cargo-checksum.json sidecar so cargo build accepts the modified files.
    rollback restores the original bytes from the beforeHash blobs, like
    npm/PyPI/gem. cargo ships on by default (alongside the always-on npm + PyPI

    • Ruby gems support), so released binaries and a plain cargo install socket-patch-cli patch Rust dependencies out of the box;
      maven/composer/nuget/deno remain opt-in.
  • 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 fail go.sum at build time. Instead apply writes a
    project-local patched copy under .socket/go-patches/<module>@<version>/
    and a managed replace directive in the project go.mod, so the patch is
    project-scoped and the cache stays pristine for sibling projects. rollback
    cleanly drops the replace directive + copy. apply --check is 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 apply and scan via --vex <path>. A
    single successful apply/scan can now both patch and emit the OpenVEX
    0.2.0 attestation, instead of requiring a separate socket-patch vex step.
    The --vex-product / --vex-no-verify / --vex-doc-id / --vex-compact
    flags mirror the standalone vex knobs (and reuse the SOCKET_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-level vex summary
    ({ 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.

Changed

  • install.sh can install without reaching github.com. New
    SOCKET_PATCH_BASE_URL points the archive downloads at any releases base that
    answers GitHub's two asset paths — notably
    https://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 --update can use the same host today through the
    SOCKET_UPDATE_BASE_URL override it already has. Also new:
    SOCKET_PATCH_INSTALL_DIR to choose the install directory explicitly instead
    of taking /usr/local/bin or ~/.local/bin. The default download origin is
    still GitHub — see docs/installer-hosting.md.

  • The documented one-liner installs from https://install.socket.dev/patch.
    The previous URL was raw.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-byte
    scripts/install.sh, with its SHA-256 published at
    install.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 its SHA256SUMS — 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 an installer-drift workflow that checks the hosted copy
    against this repository weekly.

  • Maven and NuGet promoted to fully available — the
    SOCKET_EXPERIMENTAL_MAVEN / SOCKET_EXPERIMENTAL_NUGET runtime gates
    are retired.
    Every flow (scan in 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 default scan now walks the local Maven repository (~/.m2 /
    MAVEN_REPO_LOCAL) and the NuGet caches, and scan --prune/--sync
    now 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 in
    docs/ecosystems.md: agent-mode patching leaves Maven's
    .jar.sha1/.jar.md5 stale 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 separate
    release-ecosystems.yml workflow is removed (its release: published
    trigger never fired: the release is created with GITHUB_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-url no 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, and deno Cargo
    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-features no longer produces a minimal binary
    (there is nothing left to strip). The SOCKET_EXPERIMENTAL_MAVEN /
    SOCKET_EXPERIMENTAL_NUGET runtime 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 gates docker-e2e and setup-e2e on socket-patch-cli. (MAJOR
    for anyone scripting --features; no behavior change for default builds
    beyond composer/deno support now being present.)

  • Token-less scan now batch-queries the public proxy. Proxy-mode scans
    POST {proxy}/patch/batch (one request per --batch-size chunk, mirroring
    the authenticated /v0/orgs/{slug}/patches/batch endpoint) instead of
    issuing one GET /patch/by-package/:purl per 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 as pkg: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)

Fixed

  • npm @socketsecurity/socket-patch: the ./schema export is now built
    at publish.
    The subpath pointed at a gitignored dist/ directory that
    nothing built during release, so it shipped broken; a prepack script
    now compiles it as part of npm publish.

  • Release workflow tag-guard and idempotency fixes. The
    tag-already-exists guard never fired (it ran git rev-parse in a
    shallow, tagless checkout) — it is now a stateless git ls-remote check
    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 packageSourceMapping from scratch now
    emits a catch-all for pre-existing sources.
    packageSourceMapping is
    exclusive — once ANY mapping exists, every package must match some source's
    pattern or restore hard-fails NU1100. A redirect into a nuget.config with
    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 vex
    previously 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 (reported not_applied,
    or package_not_found on cache-less CI). Verification now follows the
    managed replace directive to the committed copy.

  • repair on a hosted-only project is an informational no-op. Hosted
    (--mode hosted) mode leaves no local artifacts to repair — the lockfiles
    point at patch.socket.dev URLs, and there is no manifest or vendor ledger.
    A project whose only .socket/ trace is redirect-state.json (no manifest,
    no vendor ledger, no vendored lockfile references) previously errored with
    manifest_not_found (exit 1); it now exits 0 with a redirect_only_project
    skip pointing at scan --mode hosted. Repair still errors on a bare
    directory with no traces at all.

After merging

  1. Dispatch the Release workflow on the default branch (optionally with
    dry-run: true first). It builds all targets, tags v4.0.0, creates
    the GitHub release, and publishes every ecosystem package.
  2. Approve the staged npm versions with 2FA — platform packages first, then
    @socketsecurity/socket-patch (link in the run's step summary).
  3. On a partial failure: fix the cause and use "Re-run failed jobs" on the
    same run — every publish job is idempotent.

See docs/releasing.md for the full playbook.

🤖 Generated with Claude Code


Note

Medium Risk
Mechanical version/changelog stamp with no runtime logic changes, but it is a major-version publish gate that will ship breaking CLI removals once the Release workflow runs.

Overview
Bumps the workspace from 3.3.0 to 4.0.0 in Cargo, npm platform packages, PyPI, and both Ruby gems, and rolls [Unreleased] into ## [4.0.0] — 2026-08-20.

The new changelog section opens with a three-modes overview (agent / vendored / hosted) and drops the “lands in v4.0” heading qualifiers now that this is v4.0. No product code changes in this PR.

Reviewed by Cursor Bugbot for commit 90a6146. Configure here.

Bump 3.3.0 -> 4.0.0 (major: removes the unlock subcommand and the
--break-lock flag, reorders --help). Synced via scripts/bump-version.sh
across Cargo.toml (workspace version + core dep pin), Cargo.lock, all
npm packages (main + platform shims + lockfile), both pypi pyprojects,
and both gemspecs + the launcher VERSION constant.

Rolls CHANGELOG [Unreleased] into ## [4.0.0] and adds an intro
describing the three patch-application modes (agent / vendored /
hosted) with their v4.0 ecosystem coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@socket-security

socket-security Bot commented Aug 21, 2026

Copy link
Copy Markdown

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Full e2e results (local, this tree, 4.0.0 binary)

Leg Result
Host suite socket-patch-core ✅ green
Host suite socket-patch-cli (~3.9k tests) ✅ green except e2e_redirect_bun_build (pre-existing, see below)
Host --ignored e2e (maven, nuget, safety_cargo_build, safety_pnpm, redirect_npm_build, redirect_rush_sim, vendor_gem_build) ✅ all green
Docker e2e matrix — all 9 ecosystems (npm, pypi, gem, cargo, golang, maven, composer, nuget, deno), images rebuilt with the 4.0.0 binary ✅ all green
Docker vendor capstones (composer, gem, maven, nuget, pypi_pm) + harness selftest ✅ all green
e2e_hosted_production (live, real registries + patch.socket.dev, 3 attempts) ⚠️ 14/16 — bun + gem legs (below)
e2e_vendored_production (live, 3 attempts) ⚠️ 11/13 — bun + gem legs (below)

Skipped on host (no toolchain; covered by the Docker matrix): e2e_composer / e2e_vendor_composer_build (no composer), deno host legs. e2e_cargo/e2e_golang select 0 tests under --ignored (their tests ran in the default host suite).

Pre-existing failures — NOT from this PR (reproduce identically on the unbumped tree's code paths)

  1. bun 1.4 writes bun.lock lockfileVersion: 2; the hosted rewriter fail-closes on ≠ 1 (redirect_bun_lock_unsupported, socket-patch-core/src/patch/redirect/mod.rs:2456). Fails e2e_redirect_bun_build (2 tests) and the bun legs of both production suites on any machine with bun ≥ 1.4. Reproduced standalone: a scratch bun install --save-text-lockfile emits "lockfileVersion": 2. Follow-up: teach the rewriter (and its depscan TS twin) lockfileVersion 2, or pin bun < 1.4 in the suites.
  2. Production gem catalog drift: activestorage@6.0.3 now resolves to patch c1a1cd3c-b670-4e44-b4fa-1a63ecd42db6, not in the suites' pinned any-of sets (15e960b5-…, 6c4141c5-…). The redirect/vendor itself worked (correct patch.socket.dev source block wired); the pins are stale. Follow-up per the tests' own guidance: verify the new patch and extend GEM_UUIDS in both production suites.

🤖 Generated with Claude Code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#222/#224/#226 in the 4.0.0 changelog

Production published two more free activestorage@6.0.3 patches on
2026-08-20 — eeb6bf9f (GHSA-9xrj-h377-fr87 / CVE-2026-33195, disk_service)
at 16:14Z and c1a1cd3c (GHSA-r4mg-4433-c7g3 / CVE-2025-24293,
image_processing_transformer) at 20:31Z — and the server-ranked resolver
now wires the newest, so the required hosted-e2e check went red on the
gem leg (this PR's run 32436943099, and every push until the pins learn
the new UUIDs). Both patches' /patch/view blobs live-verified: every
touched file carries the Socket Community Patch header.

- hosted suite: append both UUIDs to the GEM_UUIDS any-of set (the leg
  already content-verifies the wired patch against its /patch/view
  manifest, so the pin is the only per-patch knowledge it needs).
- vendored suite: the single GEM_UUID pin also hardcoded the marker FILE
  (s3_service.rb, specific to the 15e960b5 patch). Replace it with a
  GEM_PATCHES (uuid, marker-file) table; the leg now asserts pristine
  across every candidate file up front, resolves the wired uuid from the
  download envelope, and probes that patch's own file + vendor path.
- docs/testing catalogs updated to match.

Also add the 4.0.0 CHANGELOG entries for the three user-facing changes
merged from main without notes: get --mode hosted|vendored (#226), the
bun 1.4 lockfileVersion-2 acceptance (#224), and the gem multi-copy /
bundle-path-precedence apply fix (#222).

Validated live against production: e2e_hosted_production 16/16,
e2e_vendored_production 13/13, e2e_redirect_bun_build 9/9.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

CI red diagnosed + fixed; main merged in

What broke: the required hosted-e2e check failed on gem_bundler_hosted_install_proof (all 3 attempts). Production published two more free activestorage@6.0.3 patches on 2026-08-20 — eeb6bf9f… (GHSA-9xrj-h377-fr87 / CVE-2026-33195) and c1a1cd3c… (GHSA-r4mg-4433-c7g3 / CVE-2025-24293) — and the server-ranked resolver now wires the newest, which wasn't in the suites' pinned any-of sets. Test-catalog drift, not a CLI regression (the redirect itself wired correctly).

Fixed in b5d4808 (after live-verifying both new patches' /patch/view blobs carry the Socket Community Patch marker):

  • hosted suite: both UUIDs appended to GEM_UUIDS;
  • vendored suite: the single-UUID pin (which also hardcoded the s3_service.rb marker file, specific to the old patch) generalized to a GEM_PATCHES (uuid → marker-file) table — the leg now resolves the wired patch from the download envelope and probes that patch's own file;
  • docs/testing/* catalogs updated.

Merged main in 90a6146 (#222, #224, #226, #227) — this also cured the bun failures from the earlier local e2e report: #224 (bun 1.4 lockfileVersion: 2 acceptance) landed on main after this branch was cut. Added the missing 4.0.0 CHANGELOG entries for the three user-facing changes that merged without notes (#222, #224, #226).

Re-validated live against production on this exact tree: e2e_hosted_production 16/16, e2e_vendored_production 13/13, e2e_redirect_bun_build 9/9.

🤖 Generated with Claude Code

@socket-security-staging

Copy link
Copy Markdown

@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit 96df6ae into main Aug 21, 2026
62 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the release/v4.0.0 branch August 21, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants