Skip to content

Yarn classic VEX attests not_affected while a bundled copy of the patched package@version stays unpatched (yarn.lock has no inBundle, so the #325 fix can't see it) #758

Description

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

Summary

This is the yarn classic version of #325. A yarn classic project can install the same name@version twice: once from its normal yarn.lock block, and once bundled inside another package's tarball (bundledDependencies). yarn.lock never records bundled copies. It has no inBundle / bundled flag, and no block for the bundled copy at all. So socket-patch can't tell the copy exists:

  • Hosted, in-run (scan --mode hosted --vex): attests not_affected. The run gives no warning; npm's equivalent is redirect_npm_bundled_instance_skipped.
  • Vendored, in-run and post-install (socket-patch vex after a fresh yarn install --frozen-lockfile): attests not_affected. Post-install, it does print vendored_tree_out_of_sync, but the remedy it gives ("re-run your package manager's install to resync it") can't work, because yarn re-extracts the bundled copy from its parent's tarball.
  • Hosted post-install vex: correct. It hash-checks every installed copy and omits the purl as not_applied.
  • Agent mode: correct. It patches both copies.

PR #669 (the open #325 fix) only adds bundled_skipped_uuids for package-lock.json (inBundle / legacy bundled). The yarn classic rewriter never sets it, so the hosted in-run path stays as it is. #337's VEX-discovery fix also keys on package-lock's inBundle, so it doesn't cover yarn.lock either.

Impact

A false VEX statement. The shipped node_modules contains an unpatched copy of the vulnerable name@version (node_modules/<parent>/node_modules/<pkg>), and the OpenVEX document says not_affected / inline_mitigations_already_exist. Downstream scanners then suppress the finding. In vendored mode the result is the same whether or not the tree was reinstalled.

Repro

This uses a local mock patch API (--proxy-url / --patch-server-url / --vendor-url) serving a free patch for left-pad@1.3.0 that prepends a marker to index.js. The bundling parent is a local tarball here for brevity. yarn handles registry packages with bundledDependencies the same way.

# parent package that bundles left-pad@1.3.0
mkdir -p bundsrc/package/node_modules/left-pad
tar xzf left-pad-1.3.0.tgz -C bundsrc/package/node_modules/left-pad --strip-components=1
echo '{"name":"bund","version":"1.0.0","dependencies":{"left-pad":"1.3.0"},"bundledDependencies":["left-pad"]}' > bundsrc/package/package.json
echo 'module.exports=require("left-pad")' > bundsrc/package/index.js
tar czf bund-1.0.0.tgz -C bundsrc package

mkdir proj && cd proj && git init -q && cp ../bund-1.0.0.tgz .
echo '{"name":"proj","version":"1.0.0","dependencies":{"left-pad":"^1.3.0","bund":"file:./bund-1.0.0.tgz"}}' > package.json
yarn install                                   # yarn.lock has no trace of the bundled copy
socket-patch scan --mode hosted --vex v0.json  # (or --mode vendored) + mock URLs
grep '"status"' v0.json                        # "not_affected", no warning in either mode
rm -rf node_modules && yarn install --frozen-lockfile
head -c 25 node_modules/left-pad/index.js                # /* SOCKET-PATCHED-HUNT */
head -c 25 node_modules/bund/node_modules/left-pad/index.js  # /* This program is free s  (unpatched)
socket-patch vex --output v1.json
# vendored: "not_affected" (+ vendored_tree_out_of_sync warning); hosted: purl omitted (correct)

Expected vs actual

Matrix (Linux, Node 22, main 045d7ec)

yarn hosted in-run --vex hosted post-install vex vendored in-run --vex vendored post-install vex scan-time bundled warning
1.7.0 not_affected (wrong) omitted (correct) not_affected (wrong) not_affected (wrong) none
1.10.1 not_affected (wrong) omitted (correct) not_affected (wrong) not_affected (wrong) none
1.22.22 not_affected (wrong) omitted (correct) not_affected (wrong) not_affected (wrong, ×2) none

Agent mode (scan --mode agent --vex) patches both copies and attests correctly (1.22.22). macOS and Windows weren't probed. The logic is OS-independent (lock-only rewriters plus VEX assembly).

Suspect code

  • crates/socket-patch-cli/src/commands/scan/hosted.rs:1262: assume_applied only drops uuids in rewrite.bundled_skipped_uuids, and rewrite_yarn_classic (crates/socket-patch-core/src/patch/redirect/mod.rs:3060) never fills it. It can't from yarn.lock alone; it would need the installed tree, or the parents' bundledDependencies.
  • crates/socket-patch-cli/src/commands/vex.rs:672-688: the vendored attestation stands on the committed artifact even when an installed copy differs. For a bundled copy, the "re-run your install" remedy is wrong, and the copy should block the attestation, as npm VEX attests not_affected while a bundled (inBundle) copy of the same package@version stays unpatched #325 does for npm.

Related: #325 (npm, fix in #669), #497 (Bun's version of the npm-only #326 fix), #601 (bundled copies in vlt/pnpm agent mode).

Activity

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