Skip to content

Yarn classic VEX still attests a pre-v5 hosted pin that yarn installs without the patched package's dependencies: #1463 checks only that the dependency's name is installed, and lock-only vex doesn't check at all #1538

Closed

Description

[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).

Summary

#1463 (the fix for #1462) makes a hosted rescan flag a yarn.lock pin written by v4.0.0 when the patched package.json declares a dependency the lock doesn't lock. It reports redirect_yarn_classic_dep_manifest_unlocked + redirect_unconfirmed, and vex omits the installed copy with dependency_not_installed. Three VEX paths still attest such a pin, though:

  1. Changed range, installed (vex and scan --vex). The patch changes an existing dependency's range (is-odd@3.0.1: is-number ^6.0.0 → ^7.0.0), and the lock has no is-number@^7.0.0. The rescan warns that the pin "is not confirmed … installs the patched package without them", and emits redirect_unconfirmed. yarn install --frozen-lockfile installs the patched is-odd with is-number 6.0.0. Yet standalone vex attests not_affected (exit 0), and so does the same scan --mode hosted --vex run that reported redirect_unconfirmed (only the generic vex_hosted_unverified warning). unresolved_npm_dependency only asks whether node_modules/is-number exists, not whether it satisfies the range the patched package declares.
  2. Added dependency, lock-only vex. For the original Hosted yarn classic rescan and VEX accept a pin written by v4.0.0 whose patched package.json adds a dependency the lock doesn't have, so yarn installs the patched package without it #1462 shape (the patch adds left-pad ^1.3.0, which the lock doesn't have), installed vex now omits it correctly (dependency_not_installed, exit 1). But vex on a lockfile-only checkout of the same commit (package.json + yarn.lock, with --patch-server-url) attests not_affected, exit 0. The dependency check only runs against an installed copy, and lock-only VEX doesn't compare the pinned block's dependencies: sub-map with the patched manifest.
  3. Changed range, lock-only vex. Same as 2: not_affected, exit 0.

Impact

This is the #1462 failure mode, still open on these paths. A security patch that bumps a dependency range (for example to pull in a fixed transitive) installs with the old dependency, while CI gets a not_affected statement. Lock-only VEX is what a pipeline generates before (or without) an install. For a pin written by v4.0.0 (npm's long-time latest), nothing in v5 makes these paths fail closed.

Repro (Linux, yarn 1.22.22, local mock patch API)

The mock serves an is-odd@3.0.1 patch. Its record lists package/index.js and package/package.json; the patched package.json has "dependencies": {"is-number": "^7.0.0"} (upstream: ^6.0.0). The grant carries sha512 + sha1. $API = SOCKET_API_URL / SOCKET_PROXY_URL / SOCKET_PATCH_SERVER_URL pointing at the mock.

mkdir p && cd p
echo '{"name":"p","version":"1.0.0","private":true,"dependencies":{"is-odd":"3.0.1"}}' > package.json
yarn install
npx @socketsecurity/socket-patch@4.0.0 scan --mode hosted --yes      # v4 pins is-odd; sub-map stays is-number "^6.0.0"
socket-patch scan --mode hosted --vex inrun.json --json --yes   # main: exit 0, redirect_yarn_classic_dep_manifest_unlocked
                                                                #   ("an earlier run's pin of it is not confirmed"),
                                                                #   event skipped/redirect_unconfirmed … but inrun.json: not_affected
rm -rf node_modules && yarn install --frozen-lockfile           # exit 0; node_modules/is-odd patched, node_modules/is-number 6.0.0
socket-patch vex -O v.json --json                               # exit 0, is-odd not_affected
mkdir ../lo && cp package.json yarn.lock ../lo && cd ../lo
socket-patch vex -O v.json --json --patch-server-url $MOCK      # exit 0, is-odd not_affected

For the added-dependency shape, serve "dependencies": {"is-number": "^6.0.0", "left-pad": "^1.3.0"} instead. Then installed vex and scan --vex omit it correctly (exit 1, dependency_not_installed / vex_omitted), but the lock-only vex still exits 0 not_affected.

Expected vs actual

  • Expected: CLI_CONTRACT (hosted mode) says "a granted reference whose rewriter found nothing to edit is neither counted nor attested", and Fix hosted yarn classic rescan of v4 pins (#1462) #1463 states that a refused stale pin "is never confirmed … and in-run scan --vex doesn't attest it". The dependency_not_installed row describes the omission for a dependency the lock "never locked". A pin whose patched package needs is-number@^7.0.0, when the lock only provides ^6.0.0, falls under the same rule. On a lock-only checkout, the lock itself shows the missing descriptor: the pinned block's dependencies: sub-map doesn't match the patched manifest, and no block is keyed by it.
  • Actual: cases 1–3 above attest not_affected with exit 0.

OS × version (main a3f56f7, Linux; lock pinned by v4.0.0)

yarn shape rescan frozen install vex installed scan --vex in-run vex lock-only
1.7.0 range ^6→^7 unconfirmed + warning exit 0, is-number 6.0.0 not_affected not run not_affected
1.7.0 add left-pad unconfirmed + warning exit 0, left-pad missing omitted (ok) not run not_affected
1.10.1 range ^6→^7 unconfirmed + warning exit 0, is-number 6.0.0 not_affected not run not_affected
1.10.1 add left-pad unconfirmed + warning exit 0, left-pad missing omitted (ok) not run not_affected
1.22.22 range ^6→^7 unconfirmed + warning exit 0, is-number 6.0.0 not_affected (×2) not_affected (×2) not_affected (×2)
1.22.22 add left-pad unconfirmed + warning exit 0, left-pad missing omitted (ok, ×2) omitted (ok, ×2) not_affected (×2)

Controls on the same main:

  • A fresh v5 hosted scan of the range shape refuses to pin (redirect_yarn_classic_dep_manifest_unlocked), and vendored refuses (vendor_dep_manifest_unlocked).
  • When is-number@^7.0.0 is already locked, hosted and vendored both rewrite the sub-map (…_dep_manifest_rewritten), and the frozen install puts is-number 7.0.0 under is-odd. That's correct.

So only the pre-v5 pin path is affected. It isn't platform-specific (pure lock and manifest logic), so I didn't probe macOS or Windows.

First bad: not a regression. v5.0.1 and earlier attest every one of these cells (#1462). #1463 fixed installed VEX for added dependencies only.

Suspect code

No probe runs: nothing here depends on the platform.

Activity

  1. mikolalysenko commented on Oct 14, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged: priority:p1 (Yarn classic, npm family). Follows #1462 / #1463, which fixed only installed VEX for added dependencies. The three remaining paths (installed range check in vex/verify.rs unresolved_npm_dependency, in-run scan --vex ignoring the unconfirmed set, and lock-only VEX skipping the classic_deps comparison) are still open on main. Not a duplicate; no open PR addresses it.


    Generated by Claude Code

  2. mikolalysenko commented on Oct 14, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue (shared root cause: yarn classic hosted VEX never compares a pinned block's dependencies with the patched package.json's ranges). Branch: agent/fix-yarn-classic-vex-dep-ranges. Claim-ID: 2026-10-14T20:20:58Z-08bf27


    Generated by Claude Code

  3. mikolalysenko commented on Oct 14, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1548


    Generated by Claude Code

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions