You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix Bun/vlt bundled copies left unpatched (#469, #471) (#472)
* Start fix for #469, #471
Assisted-by: Claude Code:claude-opus-5-5
* Skip bundled bun.lock entries, never attest them
Bun records a bundleDependencies copy as its own parent/child entry
flagged { "bundled": true } and unpacks it from the parent's
tarball, never reading the entry. Hosted and vendored scans rewired
those entries and reported success, and vex attested not_affected,
while the copy the parent loads stayed unpatched.
Both rewriters now skip a bundled entry with a loud warning (vendored
refuses when it is the only instance), and vex never takes a bundled
entry as a ref: it contests a ref for the same name@version in the
same lock or any other, like npm's inBundle handling (#325).
Refs #469
Assisted-by: Claude Code:claude-opus-5-5
* Skip bundled bun.lockb records, never attest them
bun.lockb marks a bundleDependencies edge with the bundled behavior
bit. A record only such edges reach is unpacked from the parent's
tarball, so redirecting or vendoring it installed nothing while scan
reported success and vex attested not_affected. Bun also shares one
record between a regular and a bundled install of the same version,
where the bundled copy stays unpatched.
The binary codec now flags those records. Hosted and vendored skip a
bundled-only record (vendored refuses when nothing else matches),
warn when the record is shared, and vex never attests either case.
Refs #469
Assisted-by: Claude Code:claude-opus-5-5
* Contest vlt bundled store copies in vex/vendor
vlt unpacks a bundleDependencies copy into its parent's store entry
and records no vlt-lock.json node for it, so no hosted or vendored
rewire reaches it. vex still attested the patch as not_affected while
that copy stayed unpatched.
vex now looks for real package directories inside each store
package's own node_modules (vlt links real dependencies as symlinks
beside the package), and does not attest a ref whose name@version a
bundled copy also installs. Vendored warns about such a copy, and
when it is the only install it refuses with that reason instead of
"run vlt install".
Refs #471
Assisted-by: Claude Code:claude-opus-5-5
* Warn on vlt bundled copies in hosted scan
A hosted scan in a vlt project pinned the regular lock node and
reported a clean success while a bundled copy of the same version,
unpacked into its parent's store entry, stayed unpatched. The scan
now warns redirect_vlt_bundled_instance_skipped and keeps that purl
out of the in-run VEX attestation.
Refs #471
Assisted-by: Claude Code:claude-opus-5-5
* Document Bun and vlt bundled copies in contract
Assisted-by: Claude Code:claude-opus-5-5
* Keep patch uuids out of bundled-copy diagnostics
CodeQL flagged the new bun and vlt bundled-copy diagnostics for
printing the patch uuid. They now name only the package, matching
the other recent VEX diagnostics.
Assisted-by: Claude Code:claude-opus-5-5
* Add bundled bun.lockb golden, quiet test output
The new bun-lockb-bundled fixtures join the VEX discovery golden
corpus. The new bundled-copy tests no longer print lock specs or
diagnostics that carry patch URLs and uuids (CodeQL), only case
numbers and diagnostic codes.
Assisted-by: Claude Code:claude-opus-5-5
* Keep bun bundled copies out of in-run VEX
A hosted scan --vex treated every confirmed redirect as applied. When
Bun shares a record between a regular install and a bundled copy,
or bun.lock has both entries, the regular entry is redirected but
the bundled copy stays unpatched, and the in-run attestation still
said not_affected.
The bun rewriters now record the uuids whose bundled instance they
skipped, and hosted scan verifies those purls instead of assuming
them applied, matching a standalone vex run.
Refs #469
Assisted-by: Claude Code:claude-opus-5-5
* Keep rewrite goldens stable with the new field
The redirect equivalence goldens hash each RewriteResult. The new
bundled_skipped_uuids set is left out of the serde digests while
empty, and the two goldens that hash its Debug form (pdm, poetry)
are re-blessed: their inputs are unchanged, only the printed struct
gained the empty field.
Assisted-by: Claude Code:claude-opus-5-5
* Drop unrelated rustfmt churn from this PR
An earlier cargo fmt --all reformatted about 120 files this fix does
not touch (main is not rustfmt-clean and CI has no fmt check). Those
files are back to main's bytes, and the files this PR changes carry
only their real edits on top of main's formatting.
Assisted-by: Claude Code:claude-opus-5-5
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: crates/socket-patch-cli/CLI_CONTRACT.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -376,7 +376,7 @@ Recognition rules that hold for every ecosystem:
376
376
377
377
* **Patch hosts.** A hosted reference counts only on `https://patch.socket.dev` or the `--patch-server-url` / `SOCKET_PATCH_SERVER_URL` origin, with no userinfo. The uuid is the URL's LAST canonical-uuid path segment, because grant tokens may themselves be uuid-shaped. The Go module prefix is fixed. `socket-patch-<uuid>` registry / repository / source names count only through a pin. For a URL on any other host, see **Patch hosts** above.
378
378
* **Pins, not definitions.** A registry, index or source *definition* alone (cargo `[registries]`, nuget `<add>`, pom `<repository>`, uv index tables, `.npmrc`) never makes a reference, because it survives a reverted pin. Sections the package manager ignores are not read: npm's v2 `dependencies` mirror, a `.cargo/config.toml` shadowed by `.cargo/config`. A Socket pin inside a maven `<profile>` is diagnosed, never a reference.
379
-
* **Contested locks.** When one lock wires a package to a patch and another lock resolves the same `name@version` from a non-Socket source, the build's bytes depend on which package manager runs. The reference is then dropped with a `patched_ref_unattributable` diagnostic naming both files. This applies across npm / pnpm / yarn / bun and across uv / pylock / poetry / pdm / Pipfile.lock / requirements. PEP 723 script locks neither contest nor are contested. A **bundled** npm copy (`inBundle: true`, or v1 `bundled: true`) of the same `name@version` contests the reference too, in the same lock, in the other npm lock of a shrinkwrap/package-lock pair, or in any other lock. npm unpacks it from the parent package's tarball, so no rewire reaches it and it stays unpatched.
379
+
* **Contested locks.** When one lock wires a package to a patch and another lock resolves the same `name@version` from a non-Socket source, the build's bytes depend on which package manager runs. The reference is then dropped with a `patched_ref_unattributable` diagnostic naming both files. This applies across npm / pnpm / yarn / bun and across uv / pylock / poetry / pdm / Pipfile.lock / requirements. PEP 723 script locks neither contest nor are contested. A **bundled** npm copy (`inBundle: true`, or v1 `bundled: true`) of the same `name@version` contests the reference too, in the same lock, in the other npm lock of a shrinkwrap/package-lock pair, or in any other lock. npm unpacks it from the parent package's tarball, so no rewire reaches it and it stays unpatched. Bun and vlt unpack bundled copies the same way (#469, #471). For Bun, that is a `bun.lock` entry whose meta is `{ "bundled": true }`, or a `bun.lockb` record that a dependency edge with the `bundled` behavior bit reaches, including one Bun shares with a regular install. Such an entry is never a reference, and it contests the reference the same way. For vlt, the lock records no node for a bundled copy, so the copy is found in the installed store: a real package directory inside a store package's own `node_modules`. Hosted and vendored scans skip these copies with `redirect_bun_bundled_instance_skipped` / `redirect_vlt_bundled_instance_skipped` / `vendor_bundled_instance_skipped`. When a bundled copy is the only instance, vendoring refuses with `vendor_lock_entry_not_rewritable`.
380
380
* **Lockless pins.** With no lock to name a version, a `Cargo.toml` pin (every declaration on `socket-patch-<uuid>`, that registry defined on the patch host for the same uuid) or an exclusive nuget exact-id mapping is never a reference on its own, so v5.0 does not attest it (nor does `list` show it, or `rollback` / `remove` restore it — restore those files from version control). Only a pre-v5 redirect-ledger record naming a version the pin admits keeps it live. The same holds for a gem wired only in the `Gemfile` (the pre-bundler-2.6 mixed state, lock not converged).
381
381
382
382
**Record resolution.** A candidate's record must carry the patch uuid the lockfile actually **wires**. It is taken from the first source that has one: the manifest (matched qualifier-insensitively), the hosted records above (this run's, then a pre-v5 ledger's), then the vendor ledger's embedded records. If none has it and the run is online, `vex` fetches the patch view by uuid from the patch API — for a v5 hosted checkout this is the normal path. The fetch uses `get`'s API client: the public proxy when no token is configured, and a one-shot 401/403 fallback to the proxy (free patches only). At most 10 fetches run concurrently. Fetched records stay in memory: `vex` never writes the manifest. A candidate still has no record under `--offline`, after a transport error or a 404, or when the patch is refused (paid without an entitled token); it is then omitted as `record_unavailable`, and the run is not aborted. A record whose uuid or package disagrees with the wiring is omitted as `record_mismatch`. The informational `socket-patch.vendor.json` marker is never a record source. When the lockfile wires a package to patch U, a manifest or ledger record for that package under another uuid is superseded, and a human-mode `Note:` says so.
0 commit comments