Is there an existing issue for this?
This issue exists in the latest npm version
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.5
npm 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
Is there an existing issue for this?
This issue exists in the latest npm version
Current Behavior
npm ls --json --longprints nothing to stdout and exits 1 whenever the dependency tree contains a package whosedeprecatedmessage 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 recordsverbose exit 1.Minimal reproduction:
(stdout is 0 bytes, stderr only prints the debug-log path.)
This silently breaks downstream tooling:
@cyclonedx/cyclonedx-npmrunsnpm ls --json --long --allinternally and now fails withError: failed to parse npm-ls responseon any project that transitively pulls in such a deprecated package (e.g. anything depending ontsx's predecessor packages) — i.e. SBOM generation is broken.Expected Behavior
npm ls --json --longprints 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 --longBisect: 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:display.jsredacts JSON output viaJSON.stringifyproduces a compact string, and@npmcli/redact's URL-credential matcher then matches from anhttps://inside thedeprecatedmessage across the (whitespace-free) JSON up to the next@(here inside the scoped_id), treating everything in between asuser:password@:The redacted string is no longer valid JSON,
JSON.parsethrows 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
redactValuestarted round-tripping through compactJSON.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