Skip to content

Commit 780d76e

Browse files
committed
docs: manifest-less VEX, npm 12 allow-remote and the v5 changelog
- CHANGELOG [Unreleased] (both entries are v5 MAJOR): the semver note now lists #247's changes and `vex` refusing stale ledger records and corrupt vendor ledgers; Changed (BREAKING) gains the ledger-liveness / `vendor_ledger_corrupt` entry (and #247's Fixed bullet that had `vex` disclose an unreadable ledger now points at it); Added gains the npm 12 `allow-remote` auto-config, manifest-less VEX, standalone `vendor` record embedding and the new product probes; Fixed gains the composer / gem / cargo v1 / yarn 4.0 / npm dual-lock rewriter fixes after #247's own entries. - CLI_CONTRACT.md: new "Manifest-less VEX (lockfile discovery)" section (inputs, per-ecosystem recognition table, record resolution, verification basis, liveness gates, run warnings), "Patch hosts", the embedded-VEX no-manifest rules, `manifest_not_found` for `vex`, the vendor ledger's `record` semantics (vendored-mode `detached` entries plus the standalone `vendor` fallback copy; the reconcile exemption keys on `detached`), and the new rollback warning code. - README: "No manifest needed for hosted and vendored patches", the rewritten `vex` how-it-works steps and product probes, "npm compatibility (hosted mode and npm 12)", the hosted `.npmrc` commit hint, and the ledger-liveness note under "Undo things". - docs/testing: npm-compatibility.md (npm 6–12, new), uv and bun tables, ecosystems.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 75c80e8 commit 780d76e

7 files changed

Lines changed: 604 additions & 61 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 185 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ into the new version's section — see docs/releasing.md.
1919

2020
> **Semver note:** this entry changes `rollback`'s default behavior, narrows
2121
> the meaning of its existing `vendored: []` JSON key, makes vendored mode
22-
> manifest-free, and turns a plain non-TTY `scan` report-only — all MAJOR
23-
> per CLI_CONTRACT.md's semver policy — so it ships as the next major
22+
> manifest-free, turns a plain non-TTY `scan` report-only, and makes `vex`
23+
> refuse to attest stale ledger records and corrupt vendor ledgers — all
24+
> MAJOR per CLI_CONTRACT.md's semver policy — so it ships as the next major
2425
> release (v5.0).
2526
2627
### Changed (BREAKING)
@@ -129,9 +130,165 @@ into the new version's section — see docs/releasing.md.
129130
`[patch.crates-io]` entries now refuse as `user_authored_patch_entry`) and
130131
the `pypi_pipenv_invalid_wheel` refusal code (the Pipenv backend takes the
131132
resolved version instead of parsing the wheel filename).
133+
- **`vex` attests a ledger record only while a lockfile still wires it —
134+
even under `--no-verify`.** A vendor-ledger entry whose artifact no
135+
lockfile/config references any more is omitted as `vendor_unwired`, and a
136+
redirect-ledger record whose hosted patch no lockfile references as
137+
`redirect_unwired` (a manifest-owned purl falls back to agent-mode
138+
verification instead). Previously a reverted lockfile plus a leftover
139+
`.socket/vendor/state.json` / `redirect-state.json` kept attesting, and
140+
`--no-verify` / `--vex-no-verify` attested every ledger record outright;
141+
those flags now skip only the hashing, never the wiring, record-match and
142+
conflict gates. A malformed or unreadable `.socket/vendor/state.json` is
143+
now the hard error `vendor_ledger_corrupt` (exit 2 standalone, the host
144+
command fails under `--vex`), mirroring `redirect_ledger_corrupt`, instead
145+
of a warning after which vendored patches silently lost their
146+
committed-artifact verification and detached records.
147+
Lockfiles that wire one package to different patches attest none of them
148+
(`wiring_conflict`), and when the lockfile wires a package to patch U, a
149+
manifest or ledger record for it under another uuid is superseded.
132150

133151
### Added
134152

153+
- **Hosted npm redirects configure npm 12's `allow-remote` for you.** npm 12
154+
defaults to `allow-remote=none` and refuses (EALLOWREMOTE) a lock that
155+
resolves patched packages from the Socket patch host. When `scan --mode
156+
hosted` / `get --mode hosted` leaves a root `package-lock.json` /
157+
`npm-shrinkwrap.json` redirected, it now writes `allow-remote=all` to the
158+
project `.npmrc` — creating the file, or appending one line with the BOM,
159+
CRLF and every other byte preserved — so a plain `npm ci` installs the
160+
patched bytes on npm 12 (verified on npm 10.9.9, 11.20.0 and 12.1.0). The
161+
edit is ledger-recorded (`redirect_npmrc_allow_remote`, `created` / `added`)
162+
and `rollback`, `remove`, scoped unwinds and the hosted → vendored takeover
163+
remove exactly what was added once no package-lock entry needs it (a
164+
modified created file keeps its other lines:
165+
`redirect_npmrc_allow_remote_modified`, reported by `rollback`, `remove`,
166+
`vendor` and the vendored reconcile). An explicit user
167+
`allow-remote=none` / `root` is respected — in the project `.npmrc`, in the
168+
user / global / builtin npm config (a committed project line would
169+
silently override that machine policy), or in an `npm_config_allow_remote`
170+
environment variable (which beats every `.npmrc`) — and the warning names
171+
where it was found. A symlinked, unreadable or bare-CR `.npmrc` is left
172+
alone (and a symlinked one refuses an unwind before anything is written),
173+
`--dry-run` writes nothing but previews the write — also for a vendored →
174+
hosted takeover — and
175+
`--no-npm-allow-remote-config` / `SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG` opts
176+
out. `redirect_npm_allow_remote` now fires on EVERY hosted npm run —
177+
including when `.npmrc` already allows it — and always states the
178+
tradeoff: `allow-remote=all` admits any url-resolved dependency, not just
179+
Socket's, while the sha512 integrity pins stay enforced. The `.npmrc`
180+
sniff now follows npm 12's measured grammar: only the exact key
181+
`allow-remote` counts (an `allow_remote` / `ALLOW-REMOTE` line, which npm
182+
ignores, previously silenced the warning), lines split on a bare `\r` as
183+
well as `\n`, a `[section]` header only counts on the untrimmed line (as
184+
in npm's `ini`), section bodies are not top-level (a section-scoped copy of
185+
the line never makes the unwind ambiguous), and the value is
186+
case-sensitive. Mode-preserving atomic writes now create their stage file
187+
with the destination's permission bits, so a 0600 token-bearing `.npmrc`
188+
is never staged world-readable. Vendored mode is unaffected
189+
(npm gates `file:` tarballs by `allow-file`, default `all`).
190+
191+
- **Manifest-less VEX: `vex` attests hosted and vendored patches straight
192+
from the lockfiles.** `socket-patch vex`, and `apply` / `scan` / `vendor
193+
--vex`, no longer need `.socket/manifest.json`, or the `.socket/vendor`
194+
ledgers, to attest hosted and vendored patches. That covers a
195+
depscan-opened PR, a clone that never committed its ledgers, and a lock-only
196+
CI checkout. New read-only discovery (`socket-patch-core` `vex::discover`)
197+
reads every supported root lockfile and config:
198+
- npm: package-lock / shrinkwrap;
199+
- pnpm: every lock generation, plus Rush locks;
200+
- yarn classic and berry;
201+
- `bun.lock` / `bun.lockb`;
202+
- cargo: `Cargo.lock` + `Cargo.toml` + cargo config;
203+
- Go: `go.mod` / `go.work` + sums;
204+
- Python: uv, PEP 723 script locks, `pylock.toml`, poetry, pdm,
205+
`Pipfile.lock`, requirements (+ `-r` includes), Hatch / PEP 621 direct
206+
references;
207+
- `Gemfile.lock` / `gems.locked`;
208+
- `composer.lock`;
209+
- maven: `pom.xml`;
210+
- nuget: `nuget.config` + `packages.lock.json`.
211+
212+
Each patch uuid is recovered from a Socket patch-host URL (or the
213+
`--patch-server-url` origin) or a `.socket/vendor/<eco>/<uuid>/…` path,
214+
validated fail-closed. A lock that resolves the same package elsewhere
215+
contests the wiring and blocks it. Records come from the manifest, then
216+
the ledgers, then (online only) the patch API by uuid; nothing is written
217+
to the manifest. `--offline` or a failed fetch omits the patch as
218+
`record_unavailable`, and a record naming another patch or package as
219+
`record_mismatch`. Evidence: vendored patches hash the committed artifact.
220+
Hosted patches hash the installed copy the build consumes (the Go
221+
replacement module, never the pristine cache copy), or, before any
222+
install, attest from the lockfile's integrity pin. Unreadable or
223+
unparseable lockfiles and rejected references surface as run warnings
224+
(`lockfile_unreadable`, `lockfile_unparseable`, `patched_ref_invalid`,
225+
`patched_ref_unattributable`) and never abort the run. Why a patch was
226+
gated rides `warnings[]` too, so `--json` keeps it: a failed record fetch
227+
(`vex_record_fetch_failed`, `vex_record_not_found`,
228+
`vex_record_offline`), a stale-credential fallback to the public proxy
229+
(`api_auth_fallback`, `get` / `scan`'s warning), a wiring conflict
230+
(`vex_wiring_conflict`), a superseded record (`vex_record_superseded`)
231+
and a dead ledger claim (`vex_claim_unwired`); a failed embedded `--vex`
232+
adds one `vex_omitted` per omitted patch to the host command's
233+
`warnings[]`. `apply --vex` and
234+
`vendor --vex` with no manifest now write the document instead of exiting
235+
0 with none. A project with nothing wired anywhere keeps that calm exit,
236+
and `apply --check` never generates. Manifest-less runs honor `vex
237+
--dry-run` and `-O -` like every `vex` run, and show a transient
238+
`Fetching patch records...` status line on a terminal. Per-PM support and
239+
limitations: the README's "No manifest needed for hosted and vendored
240+
patches" and CLI_CONTRACT.md's "Manifest-less VEX".
241+
- **One lockfile reader per format, and one ledger-liveness rule.**
242+
Discovery and the lock inventory read each lockfile through one reader
243+
(the package-lock, composer.lock and Pipfile.lock entry walks, the hosted
244+
rewriter's `pnpm-lock.yaml` grammar with one key grammar for every lock
245+
generation, the backends' fail-closed `bun.lock` line grammar — so a hand
246+
re-indented `bun.lock` is diagnosed `lockfile_unparseable` instead of
247+
attested — one berry key splitter, which the vendored berry backend now
248+
uses too, a shared `Gemfile.lock` model, the vendor backend's
249+
`Cargo.lock` model, the Python lock and requirements-file readers the
250+
rewriters use, with one exact-pin rule and one hosted pypi url
251+
grammar).
252+
`scan`'s cross-mode takeover warnings (`redirect_supersedes_vendored`,
253+
`vendor_supersedes_redirect`), `hosted_wiring_retained` and
254+
`redirectState.wiringLive` now prove the live lock with the same
255+
discovery and liveness rules `vex` gates attestations on, instead of a
256+
looser text scan: a ledger record counts as live only while a lockfile
257+
wires that package to the record's patch (a hosted url carrying its
258+
uuid, or the exact vendored artifact the ledger names). That rule reads
259+
EVERY lock's entry for the package (a PEP 723 script lock resolving it
260+
from PyPI no longer vetoes the project lock's hosted entry) and a
261+
recorded Gemfile with discovery's source-block grammar (a commented-out
262+
`source … do` block no longer keeps a reverted record alive). The
263+
shared readers
264+
also change the lock inventory at the edges: a v1 `Cargo.lock`'s
265+
`[metadata]` checksums now verify a crates.io fetch, `requirements.txt` is
266+
read as pip's logical lines (continuations joined, comments cut, a BOM
267+
dropped), a wildcard `requirements.txt` pin (`six==1.*`) is no exact
268+
version, a Pipfile.lock hosted reference to an sdist, an `http://` origin
269+
or a path-prefixed origin stays inventoried, a CRLF `pnpm-lock.yaml` is
270+
inventoried (it used to read as having no packages), and a `poetry.lock` /
271+
`pdm.lock` / `Cargo.lock` that is not valid TOML contributes nothing
272+
instead of a best-effort line scan. The vendored berry backend splits a
273+
multi-descriptor lock key the way yarn writes it, so a key mixing the
274+
patched package with another descriptor (`"lp@npm:left-pad@1.3.0,
275+
left-pad@npm:1.3.0"`) is refused `vendor_override_conflict` instead of
276+
being read as one descriptor.
277+
- **Standalone `vendor` embeds the patch record in its ledger entries too.**
278+
Vendored mode (`scan` / `get --mode vendored`) already writes every entry
279+
`detached: true` with its embedded `record`; the manifest-driven `vendor`
280+
command now also writes `record` into `.socket/vendor/state.json` (never
281+
`detached` — the manifest record stays authoritative while the manifest
282+
covers the package), so a project it wired whose manifest is gone still
283+
verifies, lists and attests offline: `vex`, `list` and `setup --check` read
284+
the embedded copy whenever no manifest entry covers the package, and
285+
`repair` recovers the record without the API when there is no manifest at
286+
all. Entries written by older releases keep working.
287+
- **VEX product auto-detection covers Go, Composer, Maven, NuGet and
288+
RubyGems projects**, after the existing git / `package.json` /
289+
`pyproject.toml` / `Cargo.toml` probes: `go.mod` `module`, `composer.json`
290+
`name`, `pom.xml` coordinates, the root's single `*.csproj`, the root's
291+
single `*.gemspec`. Ambiguous roots yield no product rather than a guess.
135292
- **Pipenv projects can use hosted patches, and vendored patches keep every
136293
category.** `scan --mode hosted` rewrites every `Pipfile.lock` category
137294
(`default`, `develop`, Pipenv 2022+ named categories) that pins the patched
@@ -452,8 +609,8 @@ into the new version's section — see docs/releasing.md.
452609
of being swallowed. `setup --check` reads the vendor ledger even without
453610
a manifest and, on a corrupt one, warns `unreadable vendor state` and
454611
reports a `vendor_ledger` error entry (verdict `error`, exit 1) — never
455-
`configured`; `vex` discloses the same unreadable ledger before its
456-
`manifest_not_found` / `no_patches` exit on a manifest-free project; `list`
612+
`configured`; `vex` refuses the same unreadable ledger outright
613+
(`vendor_ledger_corrupt`, see Changed); `list`
457614
degrades a corrupt vendor ledger to a `Warning: unreadable vendor ledger …`
458615
line (muted by `--silent`) rather than an error; `patch_setup` telemetry
459616
fires only for a successful, non-dry-run setup.
@@ -532,6 +689,30 @@ into the new version's section — see docs/releasing.md.
532689
"malformed"; a blob-cleanup pass keeps sweeping after one unremovable file
533690
and reports the first error afterwards; the ledgers skip byte-identical
534691
rewrites.
692+
- **Hosted composer redirects no longer fall back to the pristine git
693+
source.** Composer 1 and 2.2 LTS silently install the upstream commit from
694+
`source` when the hosted dist fails; the rewriter now drops the entry's
695+
adjacent `source` block (one fragment edit, reverted byte-for-byte) and
696+
warns `redirect_composer_source_kept` when a hand-ordered source cannot be
697+
dropped.
698+
- **Hosted gem locks keep bundler's source order.** The patch-registry
699+
`GEM` section is inserted where bundler sorts it, so `BUNDLE_FROZEN=true
700+
bundle install` on bundler ≥ 4.0.19 no longer refuses the converged lock.
701+
- **v1 `Cargo.lock` files redirect and vendor correctly.** Hosted and
702+
vendored rewrites now follow the `[metadata]` checksum table and rewrite
703+
dependents' full-id references, so `cargo --locked` accepts the lock (and
704+
the revert stays byte-identical).
705+
- **yarn 4.0.x checksums keep the lock's own spelling.** Vendored and hosted
706+
berry rewrites write bare-hex `cacheKey: 10c0` checksums when the lock
707+
does, so `yarn install --immutable` no longer fails with YN0028.
708+
- **npm 12 dual-lock projects vendor both locks.** `vendor` rewires a
709+
`package-lock.json` npm 12 keeps beside a committed shrinkwrap (else warns
710+
`vendor_npm_sibling_lock_unwired`), and hosted runs warn
711+
`redirect_npm_allow_remote` (npm 12's `allow-remote=none` refuses
712+
redirected tarballs unless `.npmrc` sets `allow-remote=all` — which hosted
713+
mode now writes itself, see Added) and `redirect_npm_legacy_client` (npm 6
714+
ignores a v1 lock's `resolved`).
715+
535716
- **Bun refusal safety:** hosted compatibility is checked before removing
536717
an existing vendored patch, including during dry-run. Vendored preflight
537718
exemptions require live local lock tuples; a ledger retained by

0 commit comments

Comments
 (0)