npm / npm/cli

`npm diff` runs `prepare` scripts while comparing local directories

Open
#9,955 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
10.1k
Forks
4.7k
Avg merge
2d 2h
Merged PRs (30d)
19

Description

Summary

When both --diff operands are local package directories, npm diff packages those directories through libnpmdiff and pacote. Pacote runs each directory package's prepare lifecycle unless --ignore-scripts is set. A command that appears read-only can therefore modify files or perform other actions declared by the selected package before showing the diff.

I am reporting this as a command behavior/UX issue, not as a security or bounty vulnerability. The user must run npm in, or explicitly select, the local project content.

Pre-submission checks

  • I searched existing issues and did not find a report covering this same npm diff and local prepare-script behavior.
  • I reproduced this with npm 12.0.2, the current latest release according to the npm registry at the time of this update.
  • This is not a request to update a dependency solely to address a CVE.

Environment

  • npm 12.0.2
  • Node.js 26.5.0
  • Windows
  • Observed source revision: c9876d7ea7150b0702e4151210b9fa1a8dbc7fbf

Safe reproduction

Create left/package.json:

{
  "name": "prepare-repro",
  "version": "1.0.0",
  "scripts": { "prepare": "node prepare.js" }
}

Create left/prepare.js:

require('node:fs').writeFileSync('prepare-ran.txt', 'ran\n')

Create left/index.js containing:

module.exports = 'left'

Create right/package.json:

{ "name": "prepare-repro", "version": "1.0.1" }

Create right/index.js containing:

module.exports = 'right'

From the temporary parent directory, run:

npm diff --diff=./left --diff=./right

After completion, left/prepare-ran.txt exists and is included in the computed diff because prepare ran before the package file list was created.

Control case:

npm diff --ignore-scripts --diff=./left --diff=./right

After removing the marker, the control command does not recreate it.

Expected behavior

Preferably, npm diff should not execute lifecycle scripts by default because it is an inspection command. If generated build artifacts must be compared, an explicit opt-in could retain that behavior. At minimum, the documentation and terminal output should clearly warn that local and Git operands may execute prepare.

Relevant flow

  • lib/commands/diff.js:55-62 passes normal flat options to libnpmdiff.
  • workspaces/libnpmdiff/lib/index.js:35-39 obtains tarballs for both manifests.
  • pacote/lib/dir.js:33-59 runs prepare when present and not ignored.
  • pacote/lib/dir.js:74-82 runs preparation before generating the package file list.

Impact and limitations

  • Inspection can have surprising side effects and can alter the package before the diff is calculated.
  • Scripts run with the invoking user's permissions, as normal npm lifecycle scripts do.
  • --ignore-scripts prevents the behavior.
  • The demonstrated case requires user-selected local project content; I did not establish an eligible npm bug-bounty attack path.
  • Pacote may intentionally run prepare to match package materialization semantics. If so, documentation and warning improvements may be preferable to changing the default.

Suggested remediation

For npm diff, consider setting ignoreScripts: true when materializing directory operands unless the user explicitly opts in. Please retain a regression test for --ignore-scripts and verify ordinary registry comparisons remain unchanged.


I used AI-assisted tooling to support source-code review, hypothesis generation, duplicate searching, and report drafting. I reviewed the resulting analysis, verified claims against the cited source code, and reproduced behaviors locally where stated. No report was submitted or external system modified autonomously by the AI.

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

Start with lib/commands/diff.js:55-62, then trace directory materialization through workspaces/libnpmdiff/lib/index.js and pacote/lib/dir.js:33-59, 74-82. Reproduce the local-directory command and its --ignore-scripts control case; done means the chosen behavior is covered by a regression test while ordinary registry comparisons remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nodejs
Domain
cli, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.