[BUG] `npm ls --json --long` outputs nothing and exits 1 when a deprecation message contains a URL (redaction corrupts compact JSON)
Nobody has claimed this yet.
- 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
npm install @esbuild-kit/esm-loader@2.6.5npm ls --json --long- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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