GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — npm agent-mode apply never patches an npm-aliased install (lp@npm:left-pad), yet VEX attests the package not_affected · Issue #356 · SocketDev/socket-patch

npm agent-mode apply never patches an npm-aliased install (lp@npm:left-pad), yet VEX attests the package not_affected #356

New issue
Copy link
New issue
Copy link
Closed
#738
Closed
npm agent-mode apply never patches an npm-aliased install (lp@npm:left-pad), yet VEX attests the package not_affected#356
#738
Copy link
Labels
agent:triagedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:npmnpmnpmpriority:p1

Description

@mikolalysenko
Mikola Lysenko (mikolalysenko)
opened on Sep 30, 2026
Issue body actions

[agent] Found by the scheduled npm bug-hunt routine (ledger #302).

Summary

npm aliases ("lp": "npm:[email protected]") install the real [email protected] bytes under node_modules/lp/, and its package.json says "name": "left-pad". Agent-mode apply never finds that copy:

  • Alias-only project: apply reports skipped / package_not_installed ("No installed package matches this PURL"), exits partialFailure, and the installed copy stays unpatched.
  • Mixed project (a plain [email protected] plus an alias of the same name@version): apply patches node_modules/left-pad, reports success with no warning, and leaves node_modules/lp unpatched. Both apply --vex and a later socket-patch vex then attest pkg:npm/[email protected] as not_affected / inline_mitigations_already_exist, even though require('lp') loads the unpatched file.

Vendored mode handles the same alias correctly: vendor rewires the node_modules/lp lock entry ("name": "left-pad") to the vendored tarball. Only agent mode (the find_by_purls resolver) misses it.

Impact

  • The vulnerable code stays in the build for every consumer that imports the package through the alias. Aliases are common for side-by-side majors, for example "lodash4": "npm:[email protected]".
  • The OpenVEX document claims the vulnerability is mitigated when the shipped node_modules still holds an unpatched copy of the same name@version, so downstream scanners suppress a real finding.
  • Alias-only projects get a misleading package_not_installed error for a package that is installed.

Repro (Linux, main f6b7fb9, npm 12.1.0; reproduced twice)

mkdir app && cd app
echo '{"name":"app","version":"1.0.0","private":true,"dependencies":{"left-pad":"1.3.0","lp":"npm:[email protected]"}}' > package.json
npm install
# node_modules/left-pad and node_modules/lp both hold [email protected] (lp/package.json: "name": "left-pad")

# hand-stage .socket/manifest.json + blobs for pkg:npm/[email protected] (a marker prepended to
# package/index.js, same shape as crates/socket-patch-cli/tests/e2e_vendor_npm_build.rs
# stage_patch_with_vuln), plus "setup": {"manual": ["npm"]}
socket-patch apply --json --offline --vex inrun.json
#   status success, events [applied pkg:npm/[email protected]], no warnings
head -c 20 node_modules/left-pad/index.js   # /* SOCKET-PATCHED */
head -c 20 node_modules/lp/index.js         # /* This program is f   <- unpatched
node -e "console.log(require('fs').readFileSync(require.resolve('lp'),'utf8').slice(0,20))"  # unpatched
socket-patch vex --output v.json --json --offline
#   inrun.json and v.json: not_affected / inline_mitigations_already_exist for GHSA-xxxx-yyyy-zzzz

Alias-only variant: use {"dependencies":{"lp":"npm:[email protected]"}} and socket-patch apply returns partialFailure, skipped package_not_installed. A scoped alias ("@x/pad": "npm:[email protected]") behaves the same.

Expected vs actual

Expected: CLI_CONTRACT.md ("Monorepo / multi-project discovery model") says apply "patches a package by PURL against the manifest regardless of how deep in the dependency tree it was installed", and describes npm's multi-copy fan-out, where every copy of a name@version is patched. The purl is pkg:npm/[email protected] and the installed package's own package.json identity is [email protected], so the aliased directory is an installed copy of that purl. A VEX not_affected statement should also only come out when the build consumes patched bytes (Setup property 7: VEX reflects "on-disk verification").

Actual: aliased copies are never probed. The alias-only case reports package_not_installed, and the mixed case leaves the alias unpatched with a clean success and a not_affected attestation.

OS × version

npm 6.14.18 (lock v1) npm 8.19.4 (v2) npm 10.9.7 (v3) npm 12.1.0 (v3)
Linux, main f6b7fb9 reproduces reproduces reproduces (workspace root + alias, VEX not_affected) reproduces ×2 (alias-only, mixed + VEX)
Linux, release 4.0.0 reproduces (alias-only)
Linux, release 3.3.0 reproduces (alias-only)
macOS / Windows not probed (resolver logic is OS-independent)

Not a regression: 3.3.0 and 4.0.0 behave the same.

Suspect code

  • crates/socket-patch-core/src/crawlers/npm_crawler.rs:797 (find_by_purls): the resolver target's dir_key is built from the purl name, so only node_modules/<purl-name> is ever probed.
  • crates/socket-patch-core/src/crawlers/npm_crawler.rs:978-985 (visit_resolver_dir): it also requires found_name == target.dir_key. That correctly rejects a different package living under the purl's directory name, but it means a copy whose directory name differs from its package.json name (an alias) can never match. The vendored lock rewriter already resolves alias edges by the entry's name (vendor/npm_lock.rs:882), so the two modes disagree.
  • VEX then verifies only the resolved (hoisted, patched) copy, so the unpatched alias copy doesn't block the attestation. This is the same verification gap as npm VEX attests not_affected while a bundled (inBundle) copy of the same package@version stays unpatched #325 (bundled copies), but a different instance source.

Activity

  1. Mikola Lysenko (mikolalysenko)
    added
    bugSomething isn't workingSomething isn't working
    bughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agent
    pm:npmnpmnpm
    agent:triaged
    priority:p1
    on Sep 30, 2026
  2. mikolalysenko commented on Sep 30, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Sep 30, 2026
    CollaboratorAuthor
    More actions

    [agent] Triaged: priority:p1 (npm). This isn't a duplicate, and I found no existing fix PR. The cause is in agent-mode find_by_purls (crawlers/npm_crawler.rs), which resolves a purl by its node_modules/<name> path, so it never reaches an alias directory. That's separate from the in-flight npm lock work in #337 and #345.


    Generated by Claude Code

  3. mikolalysenko commented on Sep 30, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Sep 30, 2026
    CollaboratorAuthor
    More actions

    [agent] Cross-OS probe (main f6b7fb9 built on each runner, Node 22): reproduces on every OS that finished.

    OS npm 10.9.4 npm 12.1.0
    ubuntu-latest left-pad patched, lp unpatched, VEX not_affected same
    macos-latest same same
    windows-latest (still running at time of writing) same

    Run: https://github.com/SocketDev/socket-patch/actions/runs/36758644937


    Generated by Claude Code

  4. Mikola Lysenko (mikolalysenko)
    added a commit that references this issue on Sep 30, 2026

    npm: bug-hunt run 1 ledger (#356 #359)

    ef92c9b
  5. mikolalysenko commented on Sep 30, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Sep 30, 2026
    CollaboratorAuthor
    More actions

    [agent] This also reproduces under Bun (the Bun bug-hunt routine, ledger #306). Tested on main f6b7fb9, bun 1.4.2, Linux.

    • Mixed project {"left-pad":"1.3.0","lp":"npm:[email protected]"} (bun writes "lp": ["[email protected]", "", {}, "sha512-…"]): scan --mode agent returns success/exit 0 and patches node_modules/left-pad only. node_modules/lp/index.js stays unpatched, and vex (with setup.manual: ["npm"]) attests pkg:npm/[email protected] not_affected.
    • Alias-only project: apply gives partialFailure / exit 1, package_not_installed, which matches the npm alias-only cell.

    Same code path (find_by_purls), so no separate Bun issue.


    Generated by Claude Code

  6. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 1, 2026
    CollaboratorAuthor
    More actions

    [agent] Re-triage on main 2463257 (the v5 consolidation, #277): still reproduces, Linux, npm 10.9.7.

    "lp": "npm:[email protected]", with a free patch for pkg:npm/[email protected] served by a local mock of the patch API:

    • scan --mode agent --download-mode file exits with partial_failure. It discovers pkg:npm/[email protected] (rollout new: 1), but node_modules/lp/index.js stays unpatched.
    • apply --json returns partialFailure, exit 1, with {"action":"skipped","purl":"pkg:npm/[email protected]","errorCode":"package_not_installed"}.

    Hosted mode (the v5 default) and vendored mode handle the same alias correctly: the redirect is written, a fresh npm ci installs the patched bytes, vex returns not_affected, and rollback restores the lock byte for byte. So the defect is now limited to agent mode.


    Generated by Claude Code

  7. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 1, 2026
    CollaboratorAuthor
    More actions

    [agent] Re-triage on main 61cfb9b, which includes the crawler change for relocated stores (#365): still reproduces. Linux, npm 10.9.4.

    {"lp":"npm:[email protected]"}, with a free patch for pkg:npm/[email protected] served by a local mock of the patch API:

    • scan --mode agent --json --yes: partial_failure, exit 1, found: 1, downloaded: 1, applied: 0. node_modules/lp/index.js stays the original.
    • apply --json: partialFailure, [('skipped', 'package_not_installed')].

    Generated by Claude Code

  8. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 2, 2026
    CollaboratorAuthor
    More actions

    [agent] This also reproduces under pnpm with node-linker=hoisted (the pnpm bug-hunt routine, ledger #303). Tested on main 61cfb9b, Linux, Node 22. Reproduced twice per version.

    {"isnum":"npm:[email protected]","to-regex-range":"5.0.1"} with node-linker=hoisted (in .npmrc, plus nodeLinker: hoisted in pnpm-workspace.yaml on 11+). pnpm writes two physical copies: node_modules/is-number (hoisted for to-regex-range) and node_modules/isnum (the alias). The patch is a hand-staged agent manifest for pkg:npm/[email protected] (marker prepended to index.js), with a GHSA attached.

    pnpm apply --json node_modules/is-number node_modules/isnum (what require('isnum') loads) vex
    9.15.9 success, applied 1 patched unpatched —
    10.34.5 success, applied 1 patched unpatched —
    12.8.1 success, applied 1 patched unpatched not_affected for pkg:npm/[email protected]

    pnpm's default isolated linker is not affected: the alias symlinks into the shared .pnpm/[email protected] directory, so the one copy gets patched (pass on 7.33.7 / 9.15.9 / 10.34.5 / 11.28.3 / 12.8.1). Hosted mode handles the alias correctly on pnpm 7–12 (ledger #303, run 7). It's the same find_by_purls path as above, so I haven't opened a separate pnpm issue.

    mkdir ha && cd ha
    echo '{"name":"ha","version":"1.0.0","private":true,"dependencies":{"isnum":"npm:[email protected]","to-regex-range":"5.0.1"}}' > package.json
    echo node-linker=hoisted > .npmrc
    pnpm install
    # stage .socket/manifest.json + blobs for pkg:npm/[email protected] (index.js), then:
    socket-patch apply --json            # success, applied 1
    head -c 20 node_modules/is-number/index.js   # patched
    head -c 20 node_modules/isnum/index.js       # original

    Generated by Claude Code

  9. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 2, 2026
    CollaboratorAuthor
    More actions

    [agent] Re-triage on main 203e092, which now includes #517 (agent vex checks every installed copy) and #555 (the #403 fix): still reproduces, and the scan exit code got less helpful. Linux, npm 10.9.4, local mock patch API for pkg:npm/[email protected], 2/2 runs.

    • Mixed {"left-pad":"1.3.0","lp":"npm:[email protected]"}: scan --mode agent --yes --json gives success, exit 0. node_modules/left-pad is patched, node_modules/lp stays the original, and vex still exits 0 with not_affected for pkg:npm/[email protected]. Fix agent vex checking only one installed copy (#516) #517 doesn't cover it, because the alias directory is never one of the "installed copies". apply --json afterwards gives partialFailure.
    • Alias-only {"lp":"npm:[email protected]"}: scan --mode agent now returns success, exit 0. On 61cfb9b it returned partial_failure, exit 1. The manifest records the patch, but nothing is applied. Standalone apply --json still gives partialFailure, exit 1. vex refuses (package_not_found), so at least that case no longer attests.

    The exit-0 change in agent scan lines up with #555 treating skipped patched deps as non-failures.


    Generated by Claude Code

  10. Mikola Lysenko (mikolalysenko)
    added a commit that references this issue on Oct 2, 2026

    npm bughunt run 2026-10-02T18:18Z: file #588, re-triage #356

    9020435
  11. mikolalysenko commented on Oct 4, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 4, 2026
    CollaboratorAuthor
    More actions

    [agent] Claiming this issue (shared root cause: agent-mode find_by_purls only probes node_modules/<purl-name> and requires dir name == package.json name, so npm-alias install dirs are never treated as copies of the purl). Branch: agent/fix-npm-agent-alias-copies. Claim-ID: 2026-10-04T01:21:12Z-75c940


    Generated by Claude Code

  12. Mikola Lysenko (mikolalysenko)
    added
    agent:claimed
    on Oct 4, 2026
  13. mikolalysenko commented on Oct 4, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 4, 2026
    CollaboratorAuthor
    More actions

    [agent] Draft PR: #738


    Generated by Claude Code

  14. Mikola Lysenko (mikolalysenko)
    added 2 commits that reference this issue on Oct 4, 2026

    Patch npm-aliased installs in agent mode

    This issue will close once commit 6868534 is merged into the 'main' branch.
    6868534

    Avoid listing alias copies twice in hosted VEX

    This issue will close once commit 07824bd is merged into the 'main' branch.
    07824bd
  15. Mikola Lysenko (mikolalysenko)
    closed this as completedin #738on Oct 5, 2026
  16. Mikola Lysenko (mikolalysenko)
    removed
    agent:claimed
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:triagedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:npmnpmnpmpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.