Skip to content

fix(vendor): accurate refusals for bundled/alias/CRLF lock shapes - #185

Open
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/vendor-refusal-diagnostics
Open

fix(vendor): accurate refusals for bundled/alias/CRLF lock shapes#185
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/vendor-refusal-diagnostics

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

Problem

Three vendored-mode diagnostics defects from the 2026-08-13 audit sent users down the wrong remediation path or silently hid security-relevant facts:

  1. npm: all-bundled/all-link target refuses with a misleading "not found" and drops the UNPATCHED advisories (medium). When every lock instance of the target is bundled inside a parent's tarball (inBundle) or a link, scan_lock_matches returns zero rewritable matches and vendor_npm refused with the generic vendor_lock_entry_not_found ("make sure the package is installed and locked (npm install)"). The entry IS in the lock, npm install cannot help, and because VendorOutcome::Refused has no warnings channel, the per-instance "that copy stays UNPATCHED" advisories were silently discarded. Concrete scenario: a lock whose only left-pad@1.3.0 entries are an inBundle: true nested copy and a workspace link — the user is told the package isn't installed instead of being told it cannot be patched by vendoring and remains vulnerable.

  2. npm v2 legacy dependencies mirror: bundled nodes wrongly rewritten, alias consumers silently unpatched (low). rewrite_legacy_tree rewrote bundled: true nodes (the packages scan skips their inBundle twins, so the two lock halves desynced — npm never installs a bundled copy from resolved), and alias consumers ("alias": {"version": "npm:left-pad@1.3.0"}) matched neither key nor version, so npm 6 clients reading the mirror keep installing the unpatched registry bytes through the alias with zero warning.

  3. pnpm: CRLF lockfile refuses with a misleading "not found" (low). A CRLF pnpm-lock.yaml (Windows autocrlf checkout) passes check_lock_version (str::lines strips \r) but breaks every structural probe (split_lines keeps the trailing \r, so packages: never matches "packages:\r"), surfacing as vendor_lock_entry_not_found — wrong diagnosis for a lock pnpm itself accepts.

Fix

  • vendor_npm: when matches is empty but the scan pushed vendor_bundled_instance_skipped / vendor_link_entry_skipped warnings, refuse with the new vendor_lock_entry_not_rewritable code whose detail carries the per-instance stays-UNPATCHED advisories verbatim and explicitly says npm install will not help. The genuinely-absent case keeps the existing vendor_lock_entry_not_found. Both codes remain non-benign (refusal_is_benign unchanged), so exit behavior is unchanged.
  • rewrite_legacy_tree: skips bundled: true nodes (parity with the packages scan's inBundle skip) and emits a loud vendor_legacy_alias_skipped warning for alias consumers, naming the JSON-pointer node and that npm 6 still installs UNPATCHED bytes through it. Aliases are warned rather than rewritten because the legacy alias spelling has no proven equivalent rewrite — fail loud, not fail open.
  • vendor_pnpm: fails closed immediately after the version sniff with the new vendor_lockfile_crlf_unsupported refusal, naming the CRLF line endings and the LF-normalization remedy, before any structural probe can produce the wrong diagnosis.

Skipped (out of lane scope, third gap of the low-severity legacy-mirror finding): recomputing the legacy node's requires map from the patched manifest. A correct recompute needs the staged patched package.json plumbed into rewrite_legacy_tree plus a proven mapping to npm 6's requires grammar; doing it without a spike risks corrupting v2 locks for a purely npm-6-era reader.

Testing

  • New regression tests, each verified red against the pre-fix behavior (guards temporarily neutralized) before going green:
    • npm_lock.rs::tests::all_bundled_or_link_instances_refuse_with_accurate_reason
    • npm_lock.rs::tests::v2_legacy_bundled_node_is_not_rewritten
    • npm_lock.rs::tests::v2_legacy_alias_node_warns_and_keeps_registry_resolution
    • pnpm_lock.rs::tests::crlf_lock_refuses_naming_line_endings
  • cargo test -p socket-patch-core --lib — 2079 passed
  • cargo test -p socket-patch-cli --test in_process_vendor — 27 passed
  • cargo test -p socket-patch-cli --test e2e_vendor_npm_build — 7 passed
  • cargo test -p socket-patch-cli --test e2e_vendor_pnpm_build — passed

🤖 Generated with Claude Code


Note

Medium Risk
Changes vendor lock rewrite and refusal paths that affect which lock bytes are edited and what users see when patches cannot apply; incorrect behavior could leave unpatched installs or mislead remediation, but scope is diagnostic/parity fixes with targeted tests.

Overview
Improves npm and pnpm vendoring diagnostics when lock entries exist but cannot be rewritten, so users are not told to run npm install / pnpm install incorrectly and security-relevant UNPATCHED facts are not dropped.

For npm, when every matching lock instance was skipped (bundled inBundle or link), refusal now uses vendor_lock_entry_not_rewritable with bundled/link skip details in the message instead of vendor_lock_entry_not_found. The v2 legacy dependencies mirror now skips bundled: true nodes (aligned with packages inBundle) and emits vendor_legacy_alias_skipped for npm:name@ver alias nodes left at registry resolution for npm 6 readers.

For pnpm, CRLF pnpm-lock.yaml is rejected up front with vendor_lockfile_crlf_unsupported and LF normalization guidance before structural parsing can misdiagnose a missing package entry.

Regression tests cover all-bundled/link refusal, legacy bundled skip, legacy alias warning, and CRLF refusal.

Reviewed by Cursor Bugbot for commit 03fe6cd. Configure here.

Three diagnostics fixes so vendored-mode refusals name the real cause
instead of sending users down the wrong remediation path:

- npm: when every lock instance of the target is bundled inside a
  parent's tarball or a link, vendoring refused with the generic
  vendor_lock_entry_not_found ("run `npm install`" -- which cannot
  help) and silently dropped the stays-UNPATCHED advisories, since a
  Refused outcome has no warnings channel. It now refuses with
  vendor_lock_entry_not_rewritable and carries the per-instance
  UNPATCHED advisories in the refusal detail.

- npm v2 legacy `dependencies` mirror: `bundled: true` nodes are no
  longer rewritten (parity with the packages scan's inBundle skip --
  npm never installs a bundled copy from `resolved`, and rewriting it
  desynced the two lock halves), and alias consumers
  (`"alias": {"version": "npm:real@ver"}`) now emit a loud
  vendor_legacy_alias_skipped warning: npm 6 reading the mirror still
  installs the unpatched registry bytes through the alias.

- pnpm: a CRLF pnpm-lock.yaml (Windows autocrlf checkout) passed the
  version sniff but failed every structural probe, surfacing as the
  misleading vendor_lock_entry_not_found. It now fails closed up
  front with vendor_lockfile_crlf_unsupported, naming the line
  endings and the LF-normalization remedy.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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