Fix known vulnerabilities in the dependencies you already have — without waiting for an upstream release, and without a risky version bump.
Socket's security team backports minimal fixes to the exact versions of packages you
have installed. The socket-patch CLI finds which of your dependencies have a patch
available and applies it, verifying every changed file by hash. It works across npm,
PyPI, Cargo, Go, RubyGems, Maven, Composer, NuGet, and Deno, and it can persist the patches
whichever way fits your workflow: re-applied by the CLI, committed to your repo, or
pinned in your lockfile. When you're done, it can emit an OpenVEX
attestation so your vulnerability scanner stops flagging the
CVEs you've already fixed.
Contents: Installation · Five-minute tutorial · How it works · Common tasks · Command reference · OpenVEX · Scripting & CI/CD · Manifest format · Ecosystem support →
One-line install (macOS / Linux):
curl -fsSL https://raw.githubusercontent.com/SocketDev/socket-patch/main/scripts/install.sh | shDetects your platform (macOS/Linux, x64/ARM64), downloads the latest binary, and installs
to /usr/local/bin or ~/.local/bin. Use sudo sh instead of sh if /usr/local/bin
requires root.
On Windows, install via npm (below) or grab a prebuilt socket-patch-*-pc-windows-msvc.zip
from the latest release.
Or install through your package manager:
| Package manager | Command |
|---|---|
| npm | npm install -g @socketsecurity/socket-patch (or one-shot: npx @socketsecurity/socket-patch) |
| pip | pip install socket-patch |
| cargo | cargo install socket-patch-cli (builds from source with every ecosystem compiled in) |
| gem | gem install socket-patch |
| composer | composer require socketsecurity/socket-patch (run as vendor/bin/socket-patch) |
The gem and composer packages are thin launchers: on first run they download the prebuilt
binary for your platform from the matching GitHub release, verify its SHA-256, cache it,
and exec it. Set SOCKET_PATCH_BIN to an existing binary to skip the download.
Manual download
Download a prebuilt binary from the latest release:
# macOS (Apple Silicon)
curl -fsSL https://github.com/SocketDev/socket-patch/releases/latest/download/socket-patch-aarch64-apple-darwin.tar.gz | tar xz
# macOS (Intel)
curl -fsSL https://github.com/SocketDev/socket-patch/releases/latest/download/socket-patch-x86_64-apple-darwin.tar.gz | tar xz
# Linux (x86_64)
curl -fsSL https://github.com/SocketDev/socket-patch/releases/latest/download/socket-patch-x86_64-unknown-linux-musl.tar.gz | tar xz
# Linux (ARM64)
curl -fsSL https://github.com/SocketDev/socket-patch/releases/latest/download/socket-patch-aarch64-unknown-linux-musl.tar.gz | tar xzThe musl builds are fully static and run on any distro; glibc (-gnu) variants are also
on the releases page, alongside Windows (socket-patch-x86_64-pc-windows-msvc.zip) and
other targets.
Then move the binary onto your PATH:
sudo mv socket-patch /usr/local/bin/The full list of prebuilt targets (Windows, 32-bit ARM, i686, Android) is in docs/ecosystems.md.
No account or token is needed to follow along — without an API token socket-patch
talks to Socket's public patch proxy, which serves the free tier of patches anonymously.
(An API token unlocks your organization's patch tier; if you've already run
socket login with the separate Socket CLI,
socket-patch picks it up automatically — see
Configuration sources.)
1. Scan your project. From your project root, ask Socket which of your installed dependencies have patches available:
cd your-project
socket-patch scanscan crawls the installed packages it finds (node_modules/, virtualenvs, the cargo
registry cache, and so on), queries the patch database, prints each available patch with
its package, severity, and CVE/GHSA identifiers, and asks whether to apply. Say yes and
the vulnerable files are rewritten in place — each file is hash-verified before and after
the edit.
If it prints
No patches available for installed packages., none of your installed dependency versions currently has a Socket patch — the good outcome, with nothing to apply. (One exception: Maven and NuGet installed-package discovery is experimental and off by default —scansilently skips them unlessSOCKET_EXPERIMENTAL_MAVEN=1/SOCKET_EXPERIMENTAL_NUGET=1is set; see the mode × ecosystem matrix.) To walk the rest of the loop anyway, make a scratch project pinned to a version that has a free patch — at the time of writing,flatted@3.3.1:mkdir demo && cd demo && git init -q && npm init -y && npm install flatted@3.3.1 && socket-patch scan(The patch catalog changes over time; if that finds nothing, pick another patched version.)
2. See what you have. The applied patches are recorded in .socket/manifest.json:
socket-patch listFound 1 patch(es):
Package: pkg:npm/flatted@3.3.1
UUID: 5cac955f-eab1-4d29-8f4f-c408a6cc9647
...
Vulnerabilities (1):
- GHSA-25h7-pfq9-p65f (CVE-2026-32141)
Severity: HIGH
3. Make it stick. Patches applied in place don't survive a reinstall — the next
npm install (or pip install, bundle install, …) restores the vulnerable upstream
bytes. Commit the .socket/ directory and wire an install hook so patches re-apply
automatically:
socket-patch setup # e.g. adds a postinstall script for npm projects
echo '.socket/apply.lock' >> .gitignore # lock state, not part of the patch record
git add .gitignore .socket package.json # npm example — setup prints which files it changed
git commit -m "apply Socket security patches"From now on, every install — yours, your teammates', CI's — re-applies the patches. You
can also re-apply manually at any time with socket-patch apply (it's idempotent).
4. Undo, if you want. Remove a patch completely (restores the original files and deletes the manifest entry):
socket-patch remove "pkg:npm/flatted@3.3.1"That's the whole loop: scan → apply when prompted → setup → commit. This tutorial used the default agent mode, where the CLI re-applies patches after each install. There are two other ways to persist patches — committing the patched packages themselves (vendored) or pinning them in your lockfile (hosted) — and choosing between the three is the next section.
A patch is a minimal fix — usually the upstream security fix, backported — for one
exact published version of a package. Socket distributes it as per-file edits: for each
touched file, the hash of the expected original (beforeHash), the hash of the patched
result (afterHash), and the replacement content. By default, a file whose current
content matches neither the expected original nor the patched result is overwritten with
the full verified patched content plus a stderr warning (content_mismatch_overwritten);
pass --strict (a global option) to fail closed on mismatch instead,
or apply --force to skip pre-application hash verification entirely (see
apply). Either way the CLI verifies the result after writing. Patches are
looked up by package URL (PURL) — e.g.
pkg:npm/lodash@4.17.20 — so everything is keyed to exact versions.
Local state lives in .socket/ at your project root, and is designed to be
committed:
| Path | Contents |
|---|---|
.socket/manifest.json |
The record of downloaded patches: PURLs, file hashes, vulnerability metadata (format) |
.socket/blobs/ |
Patched file contents, named by git-sha256 hash |
.socket/vendor/ |
Vendored package artifacts and the vendor/redirect ledgers (only in vendored/hosted modes) |
Mutating commands also leave a
.socket/apply.lockfile there between runs. It is lock state, not part of the patch record — add it to your.gitignore(repairdeletes it).
The same patched bytes can reach your build three different ways. The modes differ in
where the patch lives and what must happen at install time; pick one per project
(scan --mode <name> drives exactly one mode per run).
| Mode | Where the patch lives | Install-time requirement | Trade-off |
|---|---|---|---|
agent — scan --mode agent (or apply) |
.socket/ manifest + blobs, committed; the CLI re-applies after each install |
The socket-patch CLI must run (install hook via setup, or an apply step in CI) |
Small repo footprint (per-file blobs, not whole packages); no lockfile edits; the only mode that needs CI / install-hook changes |
vendored — scan --mode vendored (or vendor) |
Patched packages committed under .socket/vendor/; the lockfile is rewired to consume them |
None — the package manager installs the committed bytes | Fully airgapped and hermetic, at the cost of repo size |
hosted — scan --mode hosted |
No patched bytes in your repo: the lockfile is rewritten so only the patched dependencies resolve to Socket-hosted, integrity-pinned packages on patch.socket.dev; the edits + patch records are ledgered in .socket/vendor/redirect-state.json (commit it — vex reads it, and it records the pre-redirect originals a future revert feature will need; hosted has no CLI revert yet, see Undo things) |
Installs must be able to reach patch.socket.dev (no CLI, no install hook) |
Smallest possible diff (lockfile + ledger); not for airgapped installs |
Every mode pins the patched bytes: in agent mode the CLI verifies every file on each apply; vendored and hosted modes lean on your package manager's own lockfile integrity checks (sha512 / sha256 / contentHash / CHECKSUMS) where the ecosystem enforces them — hosted Maven, which has no lockfile, gets a fail-closed version-suffixing scheme instead. A few combinations have weaker install-time pins (vendored Maven, NuGet without a lockfile, Go's directory replaces, pipenv's Pipfile.lock) — there the committed bytes are the protection; see the per-ecosystem caveats.
Choosing: agent is the original method and remains fully supported, but it is the
only mode that requires CI / install-hook modification — new projects should prefer
hosted or vendored. Pick vendored if your builds are airgapped or you don't want an
infrastructure dependency; pick hosted if you want the smallest diff and your installs
can reach patch.socket.dev. (Hosted is the planned default for GitHub-app patch PRs —
it keeps the PR diff small.)
Mode support varies by ecosystem — e.g. Go can't do hosted, Rush monorepos can't do vendored. See the full mode × ecosystem matrix for details and per-ecosystem caveats.
socket-patch scan # interactive: prompts before applying
socket-patch scan --json --mode agent --yes # non-interactive (CI, scripts)socket-patch get CVE-2024-12345
socket-patch get GHSA-xxxx-yyyy-zzzz
socket-patch get lodash # fuzzy-matches installed packages
socket-patch get "pkg:npm/lodash@4.17.20"socket-patch <uuid> with a bare patch UUID is a shortcut for get <uuid>.
socket-patch setup # wire install hooks (npm postinstall, Python .pth, …)
socket-patch setup --check # CI gate: exit non-zero if hooks are missing or a patch driftedSee setup for what gets wired per ecosystem — and which ecosystems (Cargo,
Go, Maven, NuGet, Deno) have no hook and are patched on demand instead.
# Vendored: commit the patched packages themselves (airgap-friendly)
socket-patch scan --json --mode vendored --yes
echo '.socket/apply.lock' >> .gitignore
git add .gitignore .socket package-lock.json # your lockfile may differ
# Hosted: smallest diff — patched deps resolve from patch.socket.dev
socket-patch scan --json --mode hosted --yes
git add .socket/vendor/redirect-state.json package-lock.jsonNo setup hook or CI apply step is needed — the package manager installs the patched
bytes. See Three patch modes to choose, and the
mode × ecosystem matrix for what your
ecosystem supports.
One command discovers, applies, and garbage-collects in a single pass:
socket-patch scan --json --mode agent --prune --yesThe working-tree changes (the .socket/ directory — plus lockfile edits if your bot
runs --mode vendored or --mode hosted) are what your PR tooling commits — e.g.
peter-evans/create-pull-request picks them up automatically; use the JSON summary for
the PR title/body. See Scripting & CI/CD, including how to supply
SOCKET_API_TOKEN for org-tier patches.
socket-patch vex --output socket.vex.json
grype <image-or-dir> --vex socket.vex.json # or trivy image --vex ...The OpenVEX document marks each patched CVE not_affected, so scanners stop flagging
vulnerabilities you've already remediated. You can also emit it inline from apply /
scan / vendor with --vex <path>. Details in OpenVEX
attestations.
Vendored mode needs no Socket infrastructure and no socket-patch binary at install
time — the patched packages install from the committed bytes (other, unvendored
dependencies still resolve from your registry or mirror as usual). Agent mode works
offline once the blobs are committed:
socket-patch apply --offline # strict airgap: fails loudly if anything needs the networkscan and get inherently need the network and refuse to run with --offline.
Five commands clean up different layers — the first three undo, the last two reconcile and repair; pick by what you want back:
| Command | What it does |
|---|---|
rollback |
Restores the original file bytes but keeps the manifest entry — the next apply re-applies the patch |
remove |
Everything rollback does, plus it deletes the manifest entry and reverts any vendoring — permanent, the patch is fully gone in one command |
vendor --revert |
Un-vendors wholesale: restores the recorded original lockfile fragments byte-for-byte and removes the .socket/vendor/ artifacts — works without a manifest |
scan --prune |
Reconciles, doesn't reverse: drops manifest entries for packages that have left the project and garbage-collects orphan blob/diff/archive files — installed patches stay |
repair (alias gc) |
Restores health, not originals: re-downloads missing blobs, rebuilds missing/corrupt vendored artifacts, cleans up unused ones, and removes the leftover apply.lock file (housekeeping — mutating commands leave it behind after every run) |
And setup --remove reverts the install hooks that setup added.
Hosted mode has no CLI revert yet:
scan --mode hostedmakes plain lockfile / registry-config edits, so undo them with your version control (e.g.git checkout -- <lockfile>) and delete the.socket/vendor/redirect-state.jsonledger — once you've reverted by hand, its recorded original fragments are stale, and a leftover ledger would still letvexattest the removed redirects.
| Command | What it does |
|---|---|
scan |
Scan installed packages for available security patches |
apply |
Apply security patches from the local manifest |
vex |
Generate an OpenVEX attestation for the applied patches |
vendor |
Eject patched dependencies into committable .socket/vendor/ |
setup |
Wire install hooks so patches re-apply automatically |
rollback |
Restore original files (keeps the manifest) |
get |
Fetch and apply a patch by UUID / CVE / GHSA / PURL / name (alias: download) |
list |
List all patches in the local manifest |
remove |
Remove a patch: roll back files + delete the manifest entry |
repair |
Download missing blobs, clean up unused ones, tidy lock state (alias: gc) |
These flags are accepted by every subcommand and go after the command name —
socket-patch <command> --json --cwd ./app works uniformly (socket-patch --json <command> is a parse error). A command silently ignores any global flag it doesn't use
(e.g. list --global parses fine and the flag is a no-op).
Each flag has a matching SOCKET_* environment variable, listed in the table;
command-specific flags list theirs in each command's own table. **Precedence is CLI arg
env var > default** — with one extra fallback layer for the three authentication settings, described in Configuration sources below.
| Flag | Env var | Description |
|---|---|---|
--cwd <dir> |
SOCKET_CWD |
Working directory (default: .). The manifest path is resolved relative to this. |
--manifest-path <path> |
SOCKET_MANIFEST_PATH |
Path to the patch manifest, resolved relative to --cwd (default: .socket/manifest.json). |
--api-url <url> |
SOCKET_API_URL |
Socket API URL for the authenticated endpoint (default: https://api.socket.dev). |
--api-token <token> |
SOCKET_API_TOKEN |
Socket API token — optional. When no token resolves from any source, the anonymous public patch proxy is used (free patches). See Configuration sources for how to obtain and persist one. |
-o, --org <slug> |
SOCKET_ORG_SLUG |
Organization slug. Auto-resolved when omitted and a token is set. |
--proxy-url <url> |
SOCKET_PROXY_URL |
Public proxy URL used when no API token is set (default: https://patches-api.socket.dev). |
-e, --ecosystems <list> |
SOCKET_ECOSYSTEMS |
Restrict to specific ecosystems (comma-separated, e.g. npm,pypi). Unknown names are rejected. |
--download-mode <mode> |
SOCKET_DOWNLOAD_MODE |
Artifact to fetch when local files are missing: diff (default, smallest delta), package (full per-package tarball), or file (legacy per-file blobs). |
--vendor-source <mode> |
SOCKET_VENDOR_SOURCE |
How vendor acquires the installable artifact: auto (default — download the prebuilt package from patch.socket.dev, fall back to a local build on any miss), service (require the service, fail-closed), or build (always build locally). Covers npm, pypi, cargo, golang, composer, gem, nuget, and maven. |
--vendor-url <url> |
SOCKET_VENDOR_URL |
Base host for the vendoring service's package-reference request (default: the active --api-url/--proxy-url base). Point at staging / local dev for testing. |
--patch-server-url <url> |
SOCKET_PATCH_SERVER_URL |
Override the host of the prebuilt-archive download URL the service returns (default: as returned). Mainly for local-dev / testing. |
--offline |
SOCKET_OFFLINE |
Strict airgap: never contact the network. Operations that need remote data fail loudly. |
--strict |
SOCKET_STRICT |
Fail-closed on before-hash mismatches instead of the default warn-and-overwrite: a file whose current content matches neither beforeHash nor afterHash aborts that package's apply. Overridden by --force. |
-g, --global |
SOCKET_GLOBAL |
Operate on globally-installed packages. |
--global-prefix <path> |
SOCKET_GLOBAL_PREFIX |
Override the path used to discover globally-installed packages. |
-j, --json |
SOCKET_JSON |
Emit machine-readable JSON output. Every JSON response includes a "status" field — camelCase on the envelope commands ("success", "error", "noManifest", "partialFailure", "paidRequired", "notFound"; apply/list/repair/remove/vendor), snake_case on the legacy shapes ("partial_failure", "not_found"; get/scan/rollback/setup). See CLI_CONTRACT.md for the exact shapes. |
-v, --verbose |
SOCKET_VERBOSE |
Show extra detail in human-readable output. |
-s, --silent |
SOCKET_SILENT |
Suppress non-error output. |
--dry-run |
SOCKET_DRY_RUN |
Preview the operation without making any mutations. |
-y, --yes |
SOCKET_YES |
Skip interactive confirmation prompts. |
--lock-timeout <secs> |
SOCKET_LOCK_TIMEOUT |
Seconds to wait for .socket/apply.lock before giving up. 0/unset = a single non-blocking try; a positive value retries with backoff. Only meaningful for mutating commands (apply, rollback, repair, remove). |
--debug |
SOCKET_DEBUG |
Emit verbose debug logs to stderr. |
--no-telemetry |
SOCKET_TELEMETRY_DISABLED |
Disable anonymous usage telemetry. |
For the three authentication settings, the Socket CLI's
persisted login sits between the env var and the built-in default — run socket login
(or socket config set apiToken / defaultOrg) once and socket-patch picks it up
too. The SOCKET_CLI_* env vars the JS CLI reads are honored as peer aliases as well,
so one export configures both tools. To set a token directly instead, create one in the
Socket dashboard under your organization's API tokens settings and
use the raw token (sktsec_<...>_api) shown at generation time, not the
sha512-... display hash. Resolution is per key, and an empty value means "unset" at
every layer:
--api-token / --org / --api-url
1. CLI flag
2. Env var SOCKET_API_TOKEN / SOCKET_ORG_SLUG / SOCKET_API_URL — or the
SOCKET_CLI_* peer aliases (SOCKET_CLI_API_TOKEN /
SOCKET_CLI_ORG_SLUG / SOCKET_CLI_API_BASE_URL); the
canonical name wins when both are set
3. socket-cli config <data dir>/socket/settings/config.json — read-only
(Linux: $XDG_DATA_HOME, else ~/.local/share;
macOS: $XDG_DATA_HOME, else ~/Library/Application Support,
then legacy ~/.local/share; Windows: %LOCALAPPDATA%)
4. Built-in default no token → public proxy; org → auto-resolve;
url → https://api.socket.dev
Two env-only toggles adjust this. SOCKET_NO_API_TOKEN=1 ignores ambient tokens (env +
config; an explicit --api-token still wins) — useful to force the anonymous public
proxy in CI or a test run. SOCKET_NO_CONFIG=1 disables the config-file layer entirely.
socket-patch never writes the config file, and a corrupt one only produces a stderr
warning — it never breaks a command or pollutes --json output. socket-patch does
not read .env files or any per-repository config for endpoints or credentials: a
cloned repo must never be able to redirect where patches come from or spend your token.
(Full rationale: docs/design/configuration.md.)
The sections below list only each command's command-specific flags.
Scan installed packages for available security patches — and, with --mode, act on what
it finds. scan is the entry point for all three patch modes:
--mode agentdownloads and applies the selected patches in place;--mode vendoreddiscovers, downloads, and builds + wires the committable.socket/vendor/artifacts in one pass (re-vendoring automatically when a newer patch is selected);--mode hostedrewrites lockfiles / registry configs so only the patched dependencies resolve to Socket-hosted packages.
Without a mode, interactive scan prompts before applying, and scan --json is
read-only (discovery plus an updates[] array; no mutation).
scan --mode agent --prune is the single command bots need for full auto-update: it
discovers patches, applies them, and garbage-collects orphan blob files plus manifest
entries for uninstalled packages — all in one invocation.
Usage:
socket-patch scan [options]Command-specific options (plus all Global options):
| Flag | Env var | Description |
|---|---|---|
--mode <hosted|vendored|agent> |
— | Selects one of the three patch modes, summarized above. Combining --mode with a legacy boolean flag of a different mode is an error (exit 2); the same mode spelled both ways is accepted. |
--prune |
— | Garbage-collect after the scan: remove manifest entries for packages no longer present in the crawl (installed trees + lockfiles — a wiped node_modules alone doesn't prune lockfile-listed entries) and delete orphan blob/diff/package-archive files. Off by default. Vendored packages are exempt from the crawl-based prune (an absent installed copy is their normal state), but a vendored entry whose dependency has left the lockfile is reverted and its manifest entry dropped. Orthogonal to --mode — combines with any mode. |
--detached |
— | With --mode vendored: skip all .socket/manifest.json writes — the vendor ledger embeds the patch records instead. For projects that want the vendored patches only in the lockfile + .socket/vendor/. Detached patches are invisible to apply/rollback/repair; undo them with remove <purl> or vendor --revert. |
--batch-size <n> |
SOCKET_BATCH_SIZE |
Packages per API request (default: 100). |
--all-releases |
SOCKET_ALL_RELEASES |
Store patches for every release/distribution variant, not just the installed one — PyPI wheel/sdist, RubyGems platform, Maven classifier. Makes the manifest portable across environments (e.g. cross-platform CI caches). |
--vex <path> |
SOCKET_VEX |
On a successful scan, also write an OpenVEX 0.2.0 document to this path. See Inline VEX generation. |
--vex-product, --vex-no-verify, --vex-doc-id, --vex-compact |
SOCKET_VEX_* |
Passthrough to the embedded VEX builder; mirror the standalone vex knobs. Inert unless --vex is set. |
Deprecated boolean spellings of
--moderemain supported for back-compat:--apply(==--mode agent) and--vendor(==--mode vendored); prefer--mode.--syncis not deprecated — it is convenience sugar for--mode agent+--prune, the single-flag bot invocation (scan --json --sync --yes).
Use
--dry-runto preview what any moded run (with or without--prune) would do without mutating disk.
Examples:
# Scan local project (interactive prompt to apply)
socket-patch scan
# Scan with JSON output (discover + updates, no mutation)
socket-patch scan --json
# Agent mode: discover + apply patches in place (non-interactive)
socket-patch scan --json --mode agent --yes
# Auto-update bot: discover, apply, garbage-collect — all in one
socket-patch scan --json --mode agent --prune --yes
# Preview an agent-mode + prune run without mutating disk
socket-patch scan --json --mode agent --prune --yes --dry-run
# Scan only npm packages
socket-patch scan --ecosystems npm
# Scan global packages
socket-patch scan -g
# Agent mode + emit an OpenVEX attestation in one pass
socket-patch scan --json --mode agent --prune --yes --vex socket.vex.json
# Vendored mode: build + commit every patched dependency (see the vendor
# command). Works on a completely fresh clone: dependencies listed in the
# lockfile but not yet installed are fetched pristine from their registry and
# integrity-verified against the lockfile before vendoring.
socket-patch scan --json --mode vendored --yes
# Same, but keep the manifest out of it entirely
socket-patch scan --json --mode vendored --detached --yes
# Preview a vendored run (would_vendor / would_revendor / already_vendored)
socket-patch scan --json --mode vendored --yes --dry-run
# Hosted mode: rewrite lockfiles so patched deps resolve to Socket-hosted
# integrity-pinned packages — no artifact bytes in the repo, no CI changes.
socket-patch scan --json --mode hosted --yesAlready-vendored packages are skipped by plain
--mode agent(the committed artifact is the patch); a newer available patch still appears in the JSONupdates[]array — re-runscan --mode vendoredto take it.
Apply security patches from the local manifest. Idempotent — safe to run from install hooks and CI on every build.
Usage:
socket-patch apply [options]Command-specific options (plus all Global options):
| Flag | Env var | Description |
|---|---|---|
-f, --force |
SOCKET_FORCE |
Skip pre-application hash verification (apply even if package version differs). |
--check |
— | Read-only audit that the committed Go replace-redirects match the manifest (for CI / GitHub-App auditing) — Go only, since cargo patches in place and has no redirect to audit. Lock-free, crawl-free, and offline-safe: exits 0 in sync, 1 on drift. Vendored modules are excluded from the audit. |
--vex <path> |
SOCKET_VEX |
On a successful apply, also write an OpenVEX 0.2.0 document to this path. See Inline VEX generation. |
--vex-product, --vex-no-verify, --vex-doc-id, --vex-compact |
SOCKET_VEX_* |
Passthrough to the embedded VEX builder; mirror the standalone vex knobs. Inert unless --vex is set. |
Examples:
# Apply patches
socket-patch apply
# Dry run
socket-patch apply --dry-run
# Apply only npm patches
socket-patch apply --ecosystems npm
# Apply in offline mode
socket-patch apply --offline
# JSON output for CI/CD
socket-patch apply --json
# Apply and emit an OpenVEX attestation in one step
socket-patch apply --vex socket.vex.jsonPackages managed by
vendorare skipped (skipped/vendoredin JSON): the committed vendored artifact is the patch, so there is nothing forapplyto do — even when the installed tree (e.g.node_modules/) is absent.
Generate an OpenVEX 0.2.0 attestation describing the vulnerabilities that the applied patches have mitigated. See OpenVEX attestations below for the full workflow.
Usage:
socket-patch vex [options]Command-specific options (plus all Global options):
| Flag | Env var | Description |
|---|---|---|
-O, --output <path> |
SOCKET_VEX_OUTPUT |
Write the VEX document to this path instead of stdout. Required when combined with --json. |
--product <id> |
SOCKET_VEX_PRODUCT |
Override the auto-detected top-level product PURL/identifier. |
--no-verify |
SOCKET_VEX_NO_VERIFY |
Skip the on-disk file-hash check and trust the manifest — useful on a build machine that doesn't have the patched files laid out. |
--doc-id <id> |
SOCKET_VEX_DOC_ID |
Override the document @id. Default is a random urn:uuid:<v4> regenerated each run; pin this for a reproducible identifier. |
--compact |
SOCKET_VEX_COMPACT |
Emit compact JSON instead of pretty-printed. |
Examples:
# Print a VEX document to stdout (human-readable status goes to stderr)
socket-patch vex
# Write the document to a file
socket-patch vex --output socket.vex.json
# CI shape: VEX doc to file, machine-readable envelope to stdout
socket-patch vex --json --output socket.vex.json
# Generate on a build box without verifying on-disk files
socket-patch vex --no-verify --output socket.vex.jsonapply's committable sibling — the standalone command behind
vendored mode (scan --mode vendored runs discovery + this engine
in one pass). Instead of patching installed packages in place (machine-local state),
vendor ejects each patched package into .socket/vendor/<ecosystem>/<patch-uuid>/… and
rewires your lockfile so the project consumes the vendored copy. Commit .socket/ — the
vendored artifacts plus the manifest that vex, list, and
repair read — along with the lockfile edits, and every fresh checkout
builds with the patched dependency: no socket-patch binary, no Socket API access, no
install hook required on the consuming machine.
Vendoring is per-patch: only dependencies with a Socket patch are vendored. For the lockfile flavors each ecosystem supports, see the mode × ecosystem matrix.
Usage:
socket-patch vendor [options]Command-specific options (plus all Global options):
| Flag | Env var | Description |
|---|---|---|
-f, --force |
SOCKET_FORCE |
Tolerate missing patch-target files in the staged copy (skipped instead of failing the vendor) and bypass the variant probe for multi-release ecosystems. A plain before-hash mismatch doesn't need this: vendor staging always overwrites mismatched content with the verified patched bytes (surfaced as a vendor_content_mismatch_overwritten warning). |
--revert |
SOCKET_VENDOR_REVERT |
Undo vendoring: restore the recorded original lockfile fragments byte-for-byte and remove the .socket/vendor/ artifacts. Works without a manifest. |
--vex <path> |
SOCKET_VEX |
On a successful vendor, also write an OpenVEX 0.2.0 document to this path. |
--vex-product, --vex-no-verify, --vex-doc-id, --vex-compact |
SOCKET_VEX_* |
Passthrough to the embedded VEX builder. Inert unless --vex is set. |
How it interacts with the rest of the CLI — once a package is vendored, vendor owns
it:
applyandrollbackskip vendored packages (they never touch a vendor-owned tree or lockfile entry).removereverts the vendoring as part of removing the patch — lockfile restored, artifact deleted — so one command fully undoes it.scanskips downloading/applying patches for vendored packages, and--pruneexempts them from its crawl-based prune (though a vendored entry whose dependency has left the lockfile is reverted and dropped); newer patches show up inupdates[]as the signal to re-runscan --mode vendored.vexattests vendored patches by verifying the committed artifact (marked(vendored)in the impact statement) — nosetupinstall hook needed.- Re-running
vendoris idempotent; patches dropped from the manifest are auto-reverted on the next run.
Examples:
# Vendor every patched dependency listed in the manifest
socket-patch vendor
# Preview without writing anything
socket-patch vendor --dry-run
# Then make it stick: commit .socket/ (vendor artifacts + manifest) and the lockfile
# (gitignore .socket/apply.lock — see "How Socket Patch works")
git add .socket package-lock.json && git commit -m "vendor Socket patches"
# Undo everything (restores the original lockfile byte-for-byte)
socket-patch vendor --revert
# JSON output for scripting
socket-patch vendor --jsonPrefer one command?
scan --mode vendoreddiscovers, downloads, and vendors in a single pass.
Configure your project so patches are re-applied automatically after install — no
manual socket-patch apply step in CI. setup is a one-time operation: run it, commit
the change together with your .socket/ patches, and every later install handles the
rest. It is strictly opt-in — nothing is hooked unless you run setup and commit the
result.
What gets wired, per ecosystem:
- npm / yarn / pnpm / bun — writes
postinstallanddependenciesscripts intopackage.jsonso any install — includingnpm install <pkg>— re-applies patches (pnpm: root package only). - Python (pip / uv / poetry / pdm / hatch) — Python has no universal post-install
hook, so
setupinstead adds asocket-patch[hook]dependency to your manifest (pyproject.toml/requirements.txt; for classic Poetry, the equivalentsocket-patch = { extras = ["hook"] }). Installing it lays down a startup.pth(shipped by the smallsocket-patch-hookwheel) that re-applies your committed.socket/patches the next time the interpreter runs. It is package-manager-agnostic (it rides the interpreter, not any one installer) and fail-open — a hook error can never break interpreter startup. Details below. - RubyGems (Bundler) — adds a managed
plugin "socket-patch"block to theGemfileand generates an in-tree Bundler plugin under.socket/bundler-plugin/. It re-applies patches on everybundle install(cached and fresh). (Requires thesocket-patchCLI onPATH.) - Composer (PHP) — appends
socket-patch applytocomposer.json'spost-install-cmd/post-update-cmdscript events, so patches re-apply on everycomposer install/composer update. (Requires thesocket-patchCLI onPATH.) - Cargo & Go — apply-only, no
setuphook. A one-click auto-repatch-on-build isn't possible for these, sosetupskips them. Patch withsocket-patch applydirectly: cargo patches the crate in place (invendor/or the registry cache, rewriting.cargo-checksum.jsonsocargo buildaccepts it) — note that a non-vendored crate patches the shared$CARGO_HOME/registrycache, which affects every project on the machine and is silently reset bycargo cleanor a cache prune; vendor the dependency (--mode vendored) for a project-local, committable patch. go writes a project-local patched copy under.socket/go-patches/plus ago.modreplacedirective (the module cache isgo.sum-verified, so in-place patching can't build); commitgo.mod+.socket/go-patches/so a clone builds the patched bytes. To havevexstill attest these hand-applied patches, add asetup.manualarray to.socket/manifest.jsonby hand (there is no CLI flag for it yet):"setup": { "manual": ["cargo", "golang"] }. - Maven / NuGet / Deno — also apply-only: no native install hook exists to wire, so
setupreportsno_files; patch them on demand withsocket-patch apply, and declare them insetup.manual(the same hand-edit as the Cargo & Go note above, e.g."setup": { "manual": ["deno"] }) sovexstill attests the hand-applied patches — this matters most for Deno, which has no vendored or hosted alternative. For Maven and NuGet, discovery of installed packages is experimental and off by default (opt in withSOCKET_EXPERIMENTAL_MAVEN=1/SOCKET_EXPERIMENTAL_NUGET=1), and in-place patching corrupts their cache checksum sidecars — prefer--mode vendoredor--mode hosted; see ecosystems.md.
Usage:
socket-patch setup # configure (interactive)
socket-patch setup --check # verify configured; non-zero exit if not (CI gate)
socket-patch setup --remove # revert what setup addedCommand-specific options (plus all Global options — --dry-run,
--yes, --json, --cwd are the most relevant):
| Flag | Env var | Description |
|---|---|---|
--check |
— | Read-only verification that every manifest is configured and every installed patch is still applied on disk (each file matches its recorded afterHash); exits non-zero if any manifest still needs setup or a patch has drifted. Never writes (safe in CI). Conflicts with --remove. |
--remove |
— | Revert every install hook setup added (npm package.json scripts, the Python socket-patch[hook] dependency, the gem Bundler plugin wiring, and the Composer post-install-cmd/post-update-cmd script entries). |
--exclude <paths> |
SOCKET_SETUP_EXCLUDE |
Workspace-member path(s) to exclude from setup (comma-separated, relative to the repo root). The exclusion is persisted in .socket/manifest.json, so setup --check and a fresh clone honor it without re-passing the flag. |
The Python hook is designed to be easy to skip or remove:
- Per interpreter / CI step: set
SOCKET_PATCH_HOOK=off(orSOCKET_NO_HOOK=1). This is checked before any hook code runs, so it fully bypasses the hook for that process. - Remove from a project:
socket-patch setup --remove, thenpip uninstall socket-patch-hook. - Never opted in: if you don't run
setup, there is no hook — it is opt-in by design.
On interpreter startup, only when the set of installed packages changed, the hook runs
socket-patch apply --offline --ecosystems pypi for the project that owns the current
virtualenv, re-applying only the patches committed in that project's .socket/.
Specifically:
- It is anchored to the virtualenv it is installed in (not the working directory), so
a
pythonstarted from an unrelated directory cannot pull in a foreign.socket/manifest.json. - It verifies each file's hash before patching and never writes outside the installed package directory (path-escaping manifest keys are refused).
- It prefers the binary shipped in the installed
socket-patchpackage overPATH, so a binary planted earlier onPATHcannot shadow it;PATHis consulted only as a fallback when that package isn't installed. - It runs offline (no network at startup) and is fail-open (any error is swallowed; it can never abort the interpreter).
Examples:
# Interactive setup (all detected ecosystems, auto-detected)
socket-patch setup
# Non-interactive
socket-patch setup -y
# Preview changes
socket-patch setup --dry-run
# Verify configuration in CI (exits non-zero if not set up or a patch has drifted)
socket-patch setup --check
# JSON output for scripting
socket-patch setup --json -yRoll back patches to restore the original files. If no identifier is given, all patches
are rolled back. The manifest entries are kept, so a later apply re-applies the patches
— use remove to delete a patch permanently.
Packages managed by vendor are excluded — their patch lives in the committed
artifact, not the installed tree — and are listed in the JSON output's vendored array
(use remove or vendor --revert to undo them).
Usage:
socket-patch rollback [identifier] [options]Arguments:
identifier— package PURL or patch UUID to roll back. Omit to roll back all patches.
Command-specific options (plus all Global options):
| Flag | Env var | Description |
|---|---|---|
--one-off |
SOCKET_ONE_OFF |
Reserved: rollback by fetching original (beforeHash) files from the API, no manifest required. Not yet implemented — the command currently errors up front. |
Examples:
# Rollback all patches
socket-patch rollback
# Rollback a specific package
socket-patch rollback "pkg:npm/lodash@4.17.20"
# Rollback by UUID
socket-patch rollback 550e8400-e29b-41d4-a716-446655440000
# Dry run
socket-patch rollback --dry-run
# JSON output
socket-patch rollback --jsonGet a security patch from the Socket API and apply it. Accepts a UUID, CVE ID, GHSA ID, PURL, or package name. The identifier type is auto-detected but can be forced with a flag.
Alias: download. And as a shortcut, socket-patch <uuid> with a bare patch UUID is
rewritten to socket-patch get <uuid>.
Usage:
socket-patch get <identifier> [options]Arguments:
identifier— patch UUID, CVE ID, GHSA ID, package PURL, or package name. Type is auto-detected; force it with--id/--cve/--ghsa/--package.
Command-specific options (plus all Global options):
| Flag | Env var | Description |
|---|---|---|
--id |
— | Force identifier to be treated as a UUID. |
--cve |
— | Force identifier to be treated as a CVE ID. |
--ghsa |
— | Force identifier to be treated as a GHSA ID. |
-p, --package |
— | Force identifier to be treated as a package name. |
--save-only |
SOCKET_SAVE_ONLY |
Download the patch without applying it (alias: --no-apply). |
--one-off |
SOCKET_ONE_OFF |
Reserved: apply the patch immediately without saving to the .socket folder. Not yet implemented — the command currently errors up front. |
--all-releases |
SOCKET_ALL_RELEASES |
Download patches for every release/distribution variant of a matched package (PyPI wheel/sdist, RubyGems platform, Maven classifier), not just the installed one. |
Authenticated lookups run against an org. The slug is auto-resolved from your token when omitted; pass
--org <slug>(or setSOCKET_ORG_SLUG) to pick one explicitly — useful when the token belongs to multiple orgs.
Examples:
# Get patch by UUID
socket-patch get 550e8400-e29b-41d4-a716-446655440000
# Get patch by CVE
socket-patch get CVE-2024-12345
# Get patch by GHSA
socket-patch get GHSA-xxxx-yyyy-zzzz
# Get patch by package name (fuzzy matches installed packages)
socket-patch get lodash
# Download only, don't apply
socket-patch get CVE-2024-12345 --save-only
# Apply to global packages
socket-patch get lodash -g
# JSON output for scripting
socket-patch get CVE-2024-12345 --json -yList all patches in the local manifest.
Usage:
socket-patch list [options]No command-specific options — see Global options (--json,
--manifest-path, --cwd are the relevant ones).
Examples:
# List patches
socket-patch list
# JSON output
socket-patch list --jsonSample output:
Found 1 patch(es):
Package: pkg:npm/flatted@3.3.1
UUID: 5cac955f-eab1-4d29-8f4f-c408a6cc9647
Tier: free
License: MIT
Exported: Wed, 18 Mar 2026 22:53:26 GMT
Vulnerabilities (1):
- GHSA-25h7-pfq9-p65f (CVE-2026-32141)
Severity: HIGH
Summary: flatted vulnerable to unbounded recursion DoS in parse() revive phase
Files patched (6):
- package/cjs/index.js
- package/es.js
...
Remove a patch from the manifest (rolls back files first by default). If the package is
vendored, remove also reverts the vendoring — the lockfile is restored
byte-for-byte and the .socket/vendor/ artifact is deleted — so the patch is fully gone
in one command. Detached-vendored patches (from scan --mode vendored --detached) are
removable by PURL or UUID too, even though they have no manifest entry.
Usage:
socket-patch remove <identifier> [options]Arguments:
identifier— package PURL (e.g.pkg:npm/package@version) or patch UUID.
Command-specific options (plus all Global options):
| Flag | Env var | Description |
|---|---|---|
--skip-rollback |
SOCKET_SKIP_ROLLBACK |
Only update the manifest, do not restore original files (for vendored packages this also leaves the vendor wiring + artifact in place). |
Examples:
# Remove by PURL
socket-patch remove "pkg:npm/lodash@4.17.20"
# Remove by UUID
socket-patch remove 550e8400-e29b-41d4-a716-446655440000
# Remove without rolling back files
socket-patch remove "pkg:npm/lodash@4.17.20" --skip-rollback
# JSON output
socket-patch remove "pkg:npm/lodash@4.17.20" --jsonDownload missing blobs, clean up unused blobs, and reset the advisory lock state.
Alias: gc
repair cleans up the .socket/ directory without running a scan — useful when you've
manually adjusted the manifest, recovered from a partial-failure state, or just want to
free space. It also rebuilds missing or corrupt vendored artifacts. For the combined
workflow (discover + apply + GC in one pass), use
scan --json --mode agent --prune --yes instead.
As its final step, repair removes the leftover .socket/apply.lock file that mutating
commands retain between runs (skipped under --dry-run). A leftover file from a crashed
run never blocks anything — the OS releases a dead process's lock automatically — so this
is pure housekeeping. If another socket-patch process is actively running, repair
refuses up front with lock_held (exit 1); it never steals a live lock — wait for the
other process to finish, or budget a wait with --lock-timeout.
Usage:
socket-patch repair [options]Command-specific options (plus all Global options):
| Flag | Env var | Description |
|---|---|---|
--download-only |
SOCKET_DOWNLOAD_ONLY |
Only download missing artifacts, do not clean up (incompatible with --offline). |
Examples:
# Full repair (download missing + clean up unused)
socket-patch repair
# Cleanup only — missing blobs are warned about and skipped, never downloaded
socket-patch repair --offline
# Download missing blobs only
socket-patch repair --download-only
# JSON output for scripting
socket-patch repair --jsonsocket-patch vex turns your local manifest into a machine-readable statement of which
known vulnerabilities no longer affect your build because a Socket patch has been applied.
This lets vulnerability scanners stop flagging CVEs that you've already remediated in
place — without bumping the package version.
How it works
- Reads
.socket/manifest.jsonand, unless--no-verifyis passed, re-checks each patched file's hash on disk so the attestation only covers patches that are actually applied. Vendored patches are verified against the committed artifact instead of the installed tree (their impact statement carries a(vendored)marker), and need nosetupinstall hook to be attested. Detached-vendored patches (scan --mode vendored --detached) attest from the vendor ledger's embedded records, and hosted-mode patches attest from the redirect ledger (.socket/vendor/redirect-state.json, marker(redirected)— hash-verified against the installed tree post-install), sovexworks even with no manifest file at all. - Auto-detects the top-level product identifier (override with
--product), probing in order:.git/config[remote "origin"]→pkg:github/<owner>/<repo>(similar for GitLab/Bitbucket; raw URL otherwise)package.json→pkg:npm/<name>@<version>pyproject.toml→pkg:pypi/<name>@<version>Cargo.toml→pkg:cargo/<name>@<version>
- Emits an OpenVEX 0.2.0 document whose statements mark each mitigated vulnerability as
not_affected(justification: the patch is present), suitable for piping intovexctl, Grype, Trivy, and similar tools.
Provenance markers
Each statement's impact string records how the patch is persisted — one marker per patch mode:
| Impact statement | Mode | What the evidence is | What a consumer should do |
|---|---|---|---|
Patched via Socket patch <uuid> |
agent | The installed tree: every patched file's hash was verified against the manifest's afterHash |
Trust the statement as long as the agent install hook (or a CI apply) keeps re-applying; ecosystems without a hook must be declared in setup.manual |
Patched via Socket patch <uuid> (vendored) |
vendored | The committed .socket/vendor/ artifact was hash-verified — no install hook needed; the lockfile wiring is the persistence mechanism |
Trust it on any checkout; the committed bytes are the patch |
Patched via Socket patch <uuid> (redirected) |
hosted | The lockfile's integrity pin points at the Socket-hosted patched package. When emitted in-run by scan --mode hosted --vex, the statement is attested from the redirect ledger without hash verification (the bytes are fetched at install time — the JSON vex summary carries verified: false) |
Ensure installs still resolve from patch.socket.dev (the lockfile edit is intact), and run socket-patch vex after installing — it re-reads the ledger and hash-verifies the redirected patches against the installed tree |
The markers are stable strings (see CLI_CONTRACT.md); scanners and policy engines may match on them.
Output channels
| Invocation | VEX document | Status / summary |
|---|---|---|
default (no --output, no --json) |
stdout | one-line summary (stderr) |
--output <path> |
the file | one-line summary (stdout) |
--json --output <path> |
the file | machine-readable envelope on stdout (the CI shape) |
--json requires --output, since the VEX document is itself JSON and would otherwise
collide with the envelope on stdout.
Using it with a scanner
# Generate the attestation as part of CI, then hand it to a scanner
socket-patch vex --output socket.vex.json
# Suppress already-patched findings in Grype
grype <image-or-dir> --vex socket.vex.json
# Or with Trivy
trivy image --vex socket.vex.json <image>Apply patches first (in any mode) — vex errors with no_patches when there is nothing
to attest (an empty manifest, no detached-vendored patches, and no hosted redirect
records).
You don't need a separate vex invocation: pass --vex <path> to apply, scan, or
vendor and the same OpenVEX document is generated as a side-effect of a successful run.
# Patch and attest in one step
socket-patch apply --vex socket.vex.json
# Discover, apply, prune, and attest — the full auto-update-bot pass
socket-patch scan --json --mode agent --prune --yes --vex socket.vex.json
# Vendor and attest — works manifest-less with --detached too
socket-patch scan --json --mode vendored --yes --vex socket.vex.jsonThe --vex-product, --vex-no-verify, --vex-doc-id, and --vex-compact flags mirror
the standalone command's --product / --no-verify / --doc-id / --compact knobs.
Contract:
- The document is always written to the file (never stdout), so it never collides
with the command's own
--jsonoutput. JSON mode adds a top-levelvexsummary —{ path, statements, format }— to the envelope (apply) / result (scan). - It's built from the manifest as it stands after the run (including any
--mode agentwrites, with or without--prune) and verified against on-disk state unless--vex-no-verifyis set. Generated for real applies,--dry-run, and read-only scans alike. - Fail-the-command: if
--vexwas requested but generation fails (no detectable product, empty/missing manifest, nothing verified, unwritable path), the command exits non-zero even when the apply/scan itself succeeded, with a stable error code in the JSON output.
All commands support --json for machine-readable output. JSON responses always include
a "status" field for easy error detection.
Authentication in CI: a runner has no socket login state — if your organization
has org-tier patches, provide the token as a CI secret via SOCKET_API_TOKEN (without
it, runs silently fall back to the anonymous public proxy and see free patches only, and
paid-tier blob downloads report paidRequired). To deliberately pin a run to the
anonymous free tier, set SOCKET_NO_API_TOKEN=1. See
Configuration sources.
# Check for available patches in CI (read-only)
result=$(socket-patch scan --json --ecosystems npm)
patches=$(echo "$result" | jq '.totalPatches')
# Auto-update bot: discover, apply, and garbage-collect in one pass
socket-patch scan --json --mode agent --prune --yes | jq '{
applied: [.apply.patches[]? | select(.action == "added" or .action == "updated") | .purl],
pruned: (.gc.prunedManifestEntries // []),
bytes_freed: (.gc.bytesFreed // 0)
}'
# The PR action (e.g. peter-evans/create-pull-request) commits the working-tree
# changes; use this summary as the PR body.
# Apply patches and check result
socket-patch apply --json | jq '.status'
# "success", "partialFailure", "noManifest", or "error"When stdin is not a TTY (e.g. in CI pipelines), interactive prompts auto-proceed instead of blocking. Progress indicators and ANSI colors are automatically suppressed when output is piped.
The exact JSON shapes, exit codes, and stability guarantees are specified in CLI_CONTRACT.md.
Downloaded patches are stored in .socket/manifest.json:
{
"patches": {
"pkg:npm/package-name@1.0.0": {
"uuid": "unique-patch-id",
"exportedAt": "2024-01-01T00:00:00Z",
"files": {
"path/to/file.js": {
"beforeHash": "git-sha256-before",
"afterHash": "git-sha256-after"
}
},
"vulnerabilities": {
"GHSA-xxxx-xxxx-xxxx": {
"cves": ["CVE-2024-12345"],
"summary": "Vulnerability summary",
"severity": "high",
"description": "Detailed description"
}
},
"description": "Patch description",
"license": "MIT",
"tier": "free"
}
}
}Patched file contents are in .socket/blobs/ (named by git SHA256 hash).
The manifest may also carry an optional top-level "setup" key persisting setup state —
"setup": { "manual": ["cargo"], "exclude": ["packages/legacy"] } — where manual
lists ecosystems you patch by hand so vex still attests them (see
setup), and exclude lists workspace members excluded from setup (written
by setup --exclude).
- Ecosystem & platform support — the full mode × ecosystem matrix, per-ecosystem caveats (Maven, NuGet, Rush monorepos, Go), and supported platforms.
- CLI contract — the machine-readable surface: exact JSON shapes, exit codes, flag/env bindings, and the semver policy that governs them.
- Design notes — e.g. the configuration model and why hosted mode is impossible for Go.
- Changelog