|
| 1 | +--- |
| 2 | +name: bump-transitive |
| 3 | +description: Surgically bump a vulnerable transitive dependency to its latest patch per major using pnpm.overrides, then clean up without leaving override traces in package.json. |
| 4 | +--- |
| 5 | + |
| 6 | +# Fix Vulnerable Transitive Dependency |
| 7 | + |
| 8 | +Use when a security advisory identifies a vulnerable transitive package (one not directly in any `package.json`) and you need to force it to a safe version across the monorepo without bumping unrelated packages. |
| 9 | + |
| 10 | +The user will provide the package name and the fixed versions from the advisory. |
| 11 | + |
| 12 | +## Process |
| 13 | + |
| 14 | +### 1. Discover all versions in use |
| 15 | + |
| 16 | +Run `pnpm why -r` to see every resolved version and the full dependency chain explaining why each exists: |
| 17 | + |
| 18 | +```sh |
| 19 | +pnpm why -r <package> |
| 20 | +``` |
| 21 | + |
| 22 | +Each top-level line (`<package>@x.y.z`) is a distinct version in the graph. The tree below it shows which packages pull it in — useful for understanding whether a version can be collapsed or must stay separate. |
| 23 | + |
| 24 | +To extract just the version list: |
| 25 | + |
| 26 | +```sh |
| 27 | +pnpm why -r <package> | grep '^<package>@' |
| 28 | +``` |
| 29 | + |
| 30 | +### 2. Find latest safe version per major |
| 31 | + |
| 32 | +For each major currently in the lockfile, find the latest available version on npm: |
| 33 | + |
| 34 | +```sh |
| 35 | +npm view <package> versions --json | node -e " |
| 36 | +const v = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); |
| 37 | +const majors = {}; |
| 38 | +v.forEach(ver => { |
| 39 | + const m = parseInt(ver.split('.')[0]); |
| 40 | + if (!majors[m]) majors[m] = []; |
| 41 | + majors[m].push(ver); |
| 42 | +}); |
| 43 | +Object.entries(majors).forEach(([m, vers]) => { |
| 44 | + console.log('Major', m, '— latest:', vers[vers.length-1]); |
| 45 | +}); |
| 46 | +" |
| 47 | +``` |
| 48 | + |
| 49 | +Cross-reference with the advisory's "Fixed in" versions — use whichever is higher. |
| 50 | + |
| 51 | +### 3. Add overrides to root package.json |
| 52 | + |
| 53 | +In the `pnpm.overrides` section of the root `package.json`, add one entry per major using the `@major` selector syntax: |
| 54 | + |
| 55 | +```json |
| 56 | +"pnpm": { |
| 57 | + "overrides": { |
| 58 | + "<package>@1": "1.x.y", |
| 59 | + "<package>@2": "2.x.y", |
| 60 | + "<package>@5": "5.x.y" |
| 61 | + } |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +Only add majors that are actually present in the lockfile. |
| 66 | + |
| 67 | +### 4. First pnpm install — update the lockfile |
| 68 | + |
| 69 | +```sh |
| 70 | +pnpm install |
| 71 | +``` |
| 72 | + |
| 73 | +This writes the pinned versions into `pnpm-lock.yaml`. |
| 74 | + |
| 75 | +### 5. Remove the overrides from package.json |
| 76 | + |
| 77 | +Delete the entries added in step 3. The lockfile now has the correct versions recorded and will enforce them on its own. |
| 78 | + |
| 79 | +### 6. Second pnpm install — clean up |
| 80 | + |
| 81 | +```sh |
| 82 | +pnpm install |
| 83 | +``` |
| 84 | + |
| 85 | +This removes the override snapshot from the lockfile's config block and leaves a clean state: no trace of the rewrite in `package.json`, no stale override metadata in the lockfile. |
| 86 | + |
| 87 | +### 7. Verify |
| 88 | + |
| 89 | +Check that the lockfile diff is surgical — only the target package versions changed: |
| 90 | + |
| 91 | +```sh |
| 92 | +git diff pnpm-lock.yaml | grep '^[-+]' | grep -v '^---\|^+++' | grep -v '<package>' |
| 93 | +``` |
| 94 | + |
| 95 | +The only non-target lines should be inconsequential formatting shuffles (identical values on both sides of the diff). If other packages were bumped, investigate before proceeding. |
| 96 | + |
| 97 | +## Rules |
| 98 | + |
| 99 | +- **Never run `pnpm update` or `pnpm upgrade`** — these re-resolve the entire dependency graph. |
| 100 | +- **Never delete `pnpm-lock.yaml`** — regenerating it from scratch bumps everything within semver ranges. |
| 101 | +- **Never use `--frozen-lockfile`** on either install — the first install must update the lockfile, the second must reconcile the config snapshot. |
| 102 | +- Only add overrides for majors actually present in the lockfile — do not add speculative entries. |
| 103 | +- If the package is a **direct** dependency (appears in a `package.json` `dependencies`/`devDependencies`), edit that file directly instead of using overrides. |
0 commit comments