npm / npm/cli

[BUG] `npm ls --json --long` outputs nothing and exits 1 when a deprecation message contains a URL (redaction corrupts compact JSON)

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

Nobody has claimed this yet.

Bug cmd:list config:display Priority 2 regresion:11.x
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
Current Behavior

npm ls --json --long prints nothing to stdout and exits 1 whenever the dependency tree contains a package whose deprecated message contains a URL and whose compact-serialized JSON later contains an @ (e.g. a scoped package _id). No error is shown; the debug log only records verbose exit 1.

Minimal reproduction:

$ mkdir repro && cd repro
$ npm init -y >/dev/null
$ npm install @esbuild-kit/esm-loader@2.6.5   # deprecated: "Merged into tsx: https://tsx.hirok.io"
$ npm ls --json --long; echo "exit=$?"
exit=1

(stdout is 0 bytes, stderr only prints the debug-log path.)

This silently breaks downstream tooling: @cyclonedx/cyclonedx-npm runs npm ls --json --long --all internally and now fails with Error: failed to parse npm-ls response on any project that transitively pulls in such a deprecated package (e.g. anything depending on tsx's predecessor packages) — i.e. SBOM generation is broken.

Expected Behavior

npm ls --json --long prints the JSON tree (as npm ≤ 11.14.1 does), exit 0.

Steps To Reproduce
  1. npm install @esbuild-kit/esm-loader@2.6.5
  2. npm ls --json --long
  3. Empty stdout, exit code 1

Bisect: works on 11.14.1, broken since 11.15.0, still broken on 11.19.0 and 12.0.2.

Root cause analysis

The swallowed exception (surfaced by calling npm.exec('ls', …) directly) is:

SyntaxError: Unexpected token '*', ..."io","dev":***@esbuil"... is not valid JSON
    at JSON.parse (<anonymous>)
    at redactValue (npm/lib/utils/display.js:102:35)
    at getJsonBuffer (npm/lib/utils/display.js:111:27)

display.js redacts JSON output via

const redactValue = (obj) => JSON.parse(redactLog(JSON.stringify(obj)))

JSON.stringify produces a compact string, and @npmcli/redact's URL-credential matcher then matches from an https:// inside the deprecated message across the (whitespace-free) JSON up to the next @ (here inside the scoped _id), treating everything in between as user:password@:

before: ..."deprecated":"Merged into tsx: https://tsx.hirok.io","dev":true,"license":"MIT","_id":"@esbuild-kit/esm-loader@2.6.5",...
after:  ..."deprecated":"Merged into tsx: https://tsx.hirok.io","dev":***@esbuild-kit/esm-loader@2.6.5",...

The redacted string is no longer valid JSON, JSON.parse throws inside the output flush, and the error is swallowed → empty stdout, exit 1.

The same corruption cannot happen when redacting the pretty-printed CLI output (newlines stop the matcher), which is why this only bit once redactValue started round-tripping through compact JSON.stringify (first released in 11.15.0).

Possible fixes: redact leaf string values individually (walk the object) instead of redacting the serialized document, or make the credential matcher stop at "/\ characters.

Environment
  • npm: 11.15.0 … 12.0.2 (bisected; 11.14.1 OK)
  • Node.js: v24.18.0
  • OS: macOS (Darwin 27.0.0); also reproduced on Linux CI runners via cyclonedx-npm failures
  • npm config: defaults

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 npm/lib/utils/display.js, especially redactValue and getJsonBuffer, then reproduce with npm ls --json --long using @esbuild-kit/esm-loader@2.6.5. Trace how @npmcli/redact transforms the compact JSON. Done means the command emits a valid JSON dependency tree and exits 0 for deprecated messages containing URLs and scoped package IDs.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
cli, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.