npm / npm/cli

[BUG] Direct dependency's bin can be overwritten by a transitive dependency based on lexical package name

Open
#9,868 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Bug cmd:install ws:arborist
Dominant language
JavaScript
Stars
10.1k
Forks
4.7k
Avg merge
2d 2h
Merged PRs (30d)
19

Description

Is there an existing issue for this?
  • I have searched the existing issues
This issue exists in the latest npm version
  • I am using the latest npm
This is not just a request to bump a dependency for a CVE
  • This is not solely a request to bump a dependency for a CVE
Current Behavior

When a direct dependency and one of its transitive dependencies expose the same
bin name, npm can link the transitive dependency's executable into
node_modules/.bin. The winner appears to be determined by the lexical order
of the installed package paths rather than dependency depth.

This is the npm equivalent of yarnpkg/berry#7215, but Yarn's winner depends on
dependency-tree order while npm's depends on lexical package name:
https://github.com/yarnpkg/berry/issues/7215

This affects the documented setup for running TypeScript 6 and TypeScript 7
side by side. In the reproduction below:

  • @typescript/z-native is a direct alias for TypeScript 7 and exposes tsc.
  • typescript is a direct alias for @typescript/typescript6.
  • @typescript/typescript6 transitively installs @typescript/old, which also
    exposes tsc.

npm links node_modules/.bin/tsc to the transitive @typescript/old package,
so invoking tsc unexpectedly runs TypeScript 6.

Actual Behavior

node_modules/.bin/tsc points to node_modules/@typescript/old/bin/tsc, and
invoking it reports TypeScript 6.0.3.

Renaming only the direct alias from @typescript/z-native to
@typescript/a-native, then reinstalling, makes the same bin point to the
direct TypeScript 7 package. This suggests that lexical package-name ordering,
rather than directness, decides the collision.

Expected Behavior

For a bin-name collision, an executable from a direct dependency should take
precedence over one from a transitive dependency. npm exec tsc -- --version
should therefore run TypeScript 7.0.2.

Package alias names should not change which dependency supplies the executable.

Steps To Reproduce

Create this package.json in an empty directory:

{
  "name": "npm-alias-bin-repro",
  "version": "1.0.0",
  "private": true,
  "devDependencies": {
    "@typescript/z-native": "npm:typescript@~7.0.2",
    "typescript": "npm:@typescript/typescript6@~6.0.2"
  }
}

Then run:

npm install
npm exec tsc -- --version

Actual output:

Version 6.0.3

On Unix, the selected target can also be seen with:

readlink -f node_modules/.bin/tsc

It resolves to:

<repro>/node_modules/@typescript/old/bin/tsc

As a control, change @typescript/z-native to @typescript/a-native, remove
node_modules and package-lock.json, and run npm install again. Now:

npm exec tsc -- --version

reports:

Version 7.0.2

even though the dependency graph is otherwise equivalent.

Environment

Reproduced with:

Node.js: v22.18.0
npm: 11.19.0
OS: Windows

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No source file or test is named. Start by reproducing the collision with the supplied package.json, npm install, and npm exec tsc -- --version commands, then trace npm's executable-linking path and related tests. Done means the direct dependency's bin wins regardless of alias lexical order, with the reproduction selecting TypeScript 7.0.2.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
cli, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.