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
{{ message }}
Repository navigation
Commit f04b2e6
Browse filesBrowse the repository at this point in the historyBrowse files
Fix npm/Bun VEX attesting a patch a same-lock copy skips (#588) (#589)
* Start fix for #588
Assisted-by: Claude Code:claude-opus-5-5
* Stop npm VEX attesting a patch one copy skips
A package-lock.json can wire one copy of name@version to the Socket
artifact while another entry for the same name@version (a workspace
member added after vendoring, then npm install) still resolves from
the registry. npm ci installs that copy unpatched, but vex attested
the patch as not_affected because only the sibling npm lock could
contest a ref.
An unwired entry in the ref's own lock now contests it too, with a
diagnostic naming the entry and asking to re-run vendor / scan.
Refs #588
Assisted-by: Claude Code:claude-opus-5-5
* Stop Bun VEX attesting a patch one copy skips
The bun.lock / bun.lockb twin of the npm fix: a second entry resolving
a wired name@version from the registry installs unpatched beside the
rewired one, so it now contests the ref instead of letting vex attest
it as not_affected.
Refs #588
Assisted-by: Claude Code:claude-opus-5-5
* Make vendor --check flag an unwired npm copy
vendor --check only verified the committed artifact for npm entries,
so it passed while package-lock.json still had a second entry for the
patched name@version resolving from the registry. That copy installs
unpatched and a reinstall cannot fix it.
The check now re-scans every present npm lock for the entries vendor
would rewire and fails with vendor_check_failed when one of them does
not resolve to the vendored artifact, naming the entry.
Refs #588
Assisted-by: Claude Code:claude-opus-5-5
* Format the #588 changes
Assisted-by: Claude Code:claude-opus-5-5
* Document same-lock npm copies contesting VEX
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
+5-2Lines changed: 5 additions & 2 deletions
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. 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`.
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`. Another entry of the **same** npm or Bun lock that resolves the wired `name@version` from a non-Socket source (for example a workspace member added after the rewire, then `npm install` / `bun install`) contests the reference too (#588). The package manager installs both entries, and that copy stays unpatched. Re-running `scan` / `vendor` rewires every copy.
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.
@@ -1633,7 +1633,10 @@ See [the JVM design](../../docs/design/maven-vendoring.md) for supported shapes.
1633
1633
1634
1634
`vendor --check` is an offline, read-only audit. Healthy entries emit `verified`
1635
1635
with `vendor_check_ok`; drift emits `failed` with `vendor_check_failed`, a
1636
-
`partialFailure` envelope and exit 1. Missing ledger entries fail with
1636
+
`partialFailure` envelope and exit 1. For a package-lock entry, drift includes a
1637
+
`package-lock.json` / `npm-shrinkwrap.json` entry for the vendored `name@version`
1638
+
that `vendor` would rewire but that does not resolve to the vendored artifact
1639
+
(#588); the reason names that entry. Missing ledger entries fail with
1637
1640
`vendor_ledger_missing`. Offline upstream metadata is reported as the run warning
1638
1641
`vendor_jvm_upstream_unverified`. The check never starts an API client or writes
1639
1642
lock/recovery files. `--check` conflicts with `--revert`.
if vendor::jvm::apply::upstream_unverified(entry){
959
964
env.warnings.push(RunWarning{code:"vendor_jvm_upstream_unverified".into(),detail:format!("{key}: upstream metadata was accepted offline; run vendor online to verify registry checksums")});
0 commit comments