Bug Report: jf npm install never populates dependencies in published build-info
Summary
jf npm install --build-name=<name> --build-number=<number> (followed by jf rt build-publish and jf bs) consistently produces a build-info record with zero modules/dependencies, even though:
- The underlying
npm install/npm ci completes successfully with the full dependency tree resolved on disk. - A plain, standalone
npm ls --json --all --long (run outside of jf, in the exact same project directory, same shell, same environment) returns the correct, complete dependency tree with no errors.
https://github.com//pull/2 (secondary, real, but not the root cause of Bug #1): internally-invoked npm ls/npm config get cache fail with prefix cannot be changed from project config
Observed error
npm error config prefix cannot be changed from project config: /path/to/project/.npmrc.
(with npm 10.x; with npm 8.19.4 this specific error does not occur, but a different warning about unscoped _auth/username/_password appears instead — see jfrog/build-info#764, a related, already-reported issue about the extractor not running npm config fix after writing its own .npmrc)
Root cause, captured live
jf npm install --build-name=... swaps out the project's real .npmrc for a temporary one during the internal npm ls/npm config get cache calls (confirmed via --debug log):
Debug: The file /path/to/project/.npmrc was backed up successfully to jfrog.npmrc.backup
Debug: Removing existing .npmrc file
Debug: Creating temporary .npmrc file.
Debug: Running 'npm ls --json --all --long' command.
Warn: Encountered some issues while running 'npm ls' command:
npm error config prefix cannot be changed from project config: /path/to/project/.npmrc.
...
Debug: Restored the file /path/to/project/.npmrc successfully
We captured the actual contents of that ephemeral, otherwise-invisible temporary .npmrc by backgrounding the jf process and reading the file mid-execution (it exists for several seconds before being restored):
prefix = "/opt/homebrew"
json = false
registry = https://repo.ds365.ai/artifactory/api/npm/<repo>
jf itself writes an explicit prefix = "..." line into this temporary, project-level .npmrc (apparently copying the machine's active npm config get prefix value). npm ≥ (at least) 10.4.0 rejects any prefix value set at what it considers the "project config" layer with a hard error, regardless of what the user's own .npmrc contains (we verified the user's real .npmrc had no prefix line at all — the offending line is added by jf, not present in the source file).
Request: don't write prefix into the temporary swapped .npmrc at all — nothing in this workflow appears to require pinning prefix, and doing so breaks the very npm ls call this mechanism exists to support.
Bug #3 (separate, unrelated, but dangerous): reusing a build number returns a stale/cached scan result instead of re-scanning
Steps to reproduce
Publish and scan build-A / 1 (real content, e.g. 750 dependencies) — scan takes ~90s, correctly reports real findings.
Modify the dependency tree (e.g. apply a fix), republish build-info under the same build-A / 1 (a new revision of the same name+number).
Run jf bs build-A 1 --project= --vuln (with and without --rescan=true) again.
Actual
The scan completes in ~7 seconds (vs. ~90s for a genuine scan of similar size) and reports 0 vulnerabilities, even when we deliberately did not fix all known-vulnerable components (confirmed via GET /artifactory/api/build/... that the newly-published revision still listed the same-version vulnerable components as before). Waiting up to 60s and calling --rescan=true did not change the result.
Expected / workaround
Publishing under a new, never-before-used number for the same build name, with identical dependency content, produces a correct 90s scan with the real findings. This strongly suggests some layer (build revision handling, or an Xray-side cache keyed on name+number rather than content hash) is short-circuiting the scan for a previously-seen name+number pair.
Request: either document this explicitly (so users know never to reuse a build number, which is not currently mentioned anywhere in jf bs --help), or fix the caching so it's keyed on content/checksum rather than name+number alone.
Root cause analysis performed (ruled out, in order)
❌ Local .npmrc content (prefix, registry lines) — cleaned to bare minimum, bug persisted.
❌ jf CLI version — reproduced on 2.120.0 and 2.122.0.
❌ npm install vs npm ci.
❌ JFROG_RUN_NATIVE=true (native mode) — bug #2 warning disappears, but dependencies are still 0 (see matrix row 8).
❌ Passing build-name/number via flags vs JFROG_CLI_BUILD_NAME/JFROG_CLI_BUILD_NUMBER env vars.
❌ "Up to date" vs forced-fresh install (rm -rf node_modules before running) — no difference.
❌ npm version — reproduced on 8.19.4, 10.4.0, 10.9.4 (spanning 3 major generations with entirely different warning behavior on the internal npm ls call).
✅ Confirmed via direct inspection of build-info-go's public source (build/utils/npm.go, CalculateDependenciesMap/runNpmLsWithNodeModules) that dependency collection depends entirely on the npm ls --json --all --long subprocess call succeeding and returning a "dependencies" key — but we could not get further visibility into why, in our environment, this consistently yields nothing even when the exact same subprocess call, run manually with identical flags and working directory, succeeds.
We were unable to go further without adding instrumentation to the jf binary itself or getting access to JFrog's own internal debugging.
Working workaround currently in use
Since jf audit --sca (a different code path) and a bare npm ls --json --all --long both work correctly and return real data, we bypass the broken extractor entirely:
Run npm ls --json --all --long directly (not via jf).
For each resolved dependency, compute real sha1/md5/sha256 checksums by looking up its tarball in the local npm cache (/.npm/_cacache/content-v2//<hex[0:2]>/<hex[2:4]>/<hex[4:]>, keyed from the integrity field npm already reports).
Construct a build-info JSON matching the public schema (build-info-go/entities/buildinfo.go) by hand: {name, number, agent, buildAgent, started, modules: [{type: "npm", id, dependencies: [{id, type, scopes, sha1, md5, sha256}]}]}.
PUT it directly: jf api -X PUT --input build-info.json -H "Content-Type: application/vnd.org.jfrog.artifactory+json" "/artifactory/api/build?project=".
jf bs --project= --vuln.
This works reliably and Xray correctly indexes and scans the resulting build — provided every dependency includes a checksum. We separately confirmed that a build-info with correct modules/dependencies but no checksums is accepted (204) but never gets indexed by Xray (SBOM: 0, Vulnerabilities: 0 in the UI, indefinitely) — this may be worth documenting explicitly in the build-info schema docs, since it's not an obvious requirement and the API doesn't reject or warn about its absence.
Related existing (unresolved) issues
jfrog/jfrog-cli#1822 — "XRay doesn't scan dependencies in NPM builds" (same symptom, open, no maintainer response)
#321 — same title, same symptom, open, no maintainer response
jfrog/build-info#764 — npm ≥9 _auth config format issue in the same extractor, closed "not planned"
npm/cli#7501 — upstream npm bug behind the prefix cannot be changed from project config message (Bug #2 here)
Requested from JFrog
Root-cause why CalculateDependenciesMap/the npm build-info extractor produces zero dependencies in this environment even when its own internal npm ls subprocess call succeeds cleanly (matrix row 8) — this suggests the bug may not even be in the npm ls invocation itself, but somewhere after (parsing, or a step gating collection on something unrelated to npm ls's success).
Stop writing prefix into the temporary swapped .npmrc (Bug #2) — separate, real, cheap fix.
Either fix or clearly document the build-number-reuse stale-scan behavior (Bug #3).
Document the checksum requirement for Xray to index a manually- or CLI-published npm build-info module.
Happy to provide the two full JSON build-info examples (empty vs. our manually-constructed working one), full --debug logs, and the reproduction script on request.
Bug Report: jf npm install never populates dependencies in published build-info
Summary
jf npm install --build-name= --build-number= (followed by jf rt build-publish and jf bs) consistently produces a build-info record with zero modules/dependencies, even though:
The underlying npm install/npm ci completes successfully with the full dependency tree resolved on disk.
A plain, standalone npm ls --json --all --long (run outside of jf, in the exact same project directory, same shell, same environment) returns the correct, complete dependency tree with no errors.
This was reproduced consistently across 3 different npm major versions, 2 jf CLI versions, and every combination of the CLI's own npm-install strategies (native vs default), install command (install vs ci), and build-name/number supplied via flags vs environment variables — 19 combinations tested, 100% reproduction rate.
A secondary, distinct bug was found and root-caused along the way (npm ls/npm config get cache failing with prefix cannot be changed from project config), but it turned out to be a red herring, not the root cause of the empty build-info — a run with zero warnings and a fully successful npm ls was equally empty. The true root cause was never isolated further than "the dependency-collection step of the npm build-info extractor silently produces nothing," documented below with full evidence.
A working manual workaround was found (construct the build-info JSON directly from a standalone npm ls + real checksums resolved from the local npm cache, then PUT it to /artifactory/api/build) and is described at the end, in case it's useful context for where in the pipeline the real extractor's output diverges.
A separate, unrelated bug was also found during this investigation and is reported here too, since it's dangerous from a data-integrity standpoint: re-scanning a build under an already-used build number silently returns a stale/cached result instead of re-scanning.
Environment
Component Version
JFrog CLI (jf) 2.120.0, and independently reproduced on 2.122.0 (installed via npm install -g jfrog-cli-v2-jf)
npm Reproduced on 8.19.4, 10.4.0, and 10.9.4
Node.js v22.22.0
OS / Arch macOS, darwin/arm64
JFrog Platform JFrog Cloud (SaaS)
Project structure Single package.json + package-lock.json per project (no npm workspaces/monorepo), ~750–1040 resolved packages
Codebases reproduced against
The bug was reproduced independently on two unrelated, real-world codebases, ruling out anything project-specific:
Codebase Stack Key devDependency (build tooling) Resolved packages
Frontend Angular 21 SPA @angular/cli, @angular-devkit/build-angular 934
Backend NestJS 11 REST API (TypeORM + PostgreSQL) @nestjs/cli 750–751
Both are conventional single-package (non-monorepo, non-workspaces) npm projects with a committed package-lock.json, built and run with Node.js v22.22.0. Neither project uses npm workspaces, Yarn, or pnpm — plain npm throughout.
Bug #1 (primary): npm build-info extractor produces an empty modules[].dependencies array
Steps to reproduce
cd /path/to/npm-project # has package.json, package-lock.json
npm install # confirm this succeeds and node_modules is populated
jf npm install --build-name=my-build --build-number=1
jf rt build-publish my-build 1 --project=
jf bs my-build 1 --project= --vuln
Expected
The published build-info's modules[0].dependencies array contains one entry per resolved npm package (matching what npm ls reports), and the subsequent Xray scan reports real vulnerabilities matching what jf audit --sca finds for the same project.
Actual
GET /artifactory/api/build//?project= returns a buildInfo object with no modules key at all:
{
"buildInfo": {
"version": "1.0.1",
"name": "my-build",
"number": "1",
"buildAgent": { "name": "GENERIC", "version": "2.122.0" },
"agent": { "name": "jfrog-cli-go", "version": "2.122.0" },
"started": "2026-08-26T21:03:39.177-0300",
"durationMillis": 0,
"artifactoryPrincipal": ""
}
}
The Xray scan (jf bs) correctly reports Overall: Done, SCA: Done (confirmed via the Scans List UI's "Scan Status Details" panel — SCA genuinely ran, it is not skipped), but with 0 vulnerable dependencies, 0 SBOM components — because there is nothing in the build-info to scan, not because the project is clean. Cross-checked: jf audit --sca on the identical project reports real, known CVEs (6–9 findings depending on the project) for the same package-lock.json.
Local evidence: the extractor's own partials cache is empty
Before jf rt build-publish runs, the CLI writes a local per-build cache directory:
$TMPDIR/jfrog-/builds//partials/
In every single reproduction (19 combinations, see matrix below), this directory contains only one file, details, with nothing but a timestamp:
{ "Timestamp": "2026-08-26T21:05:26.704282-03:00" }
No dependency/module partial file is ever written — this is true regardless of whether the internal npm ls call below succeeds or fails.
Reproduction matrix (all combinations tested, all resulted in 0 dependencies)
npm version Mode Install command build-name/number via npm ls warning? Result
1 10.9.4 default install flags Yes (prefix error) 0 deps
2 10.9.4 JFROG_RUN_NATIVE=true install flags No 0 deps
3 10.9.4 default ci flags Yes 0 deps
4 10.9.4 default install env vars (JFROG_CLI_BUILD_NAME/NUMBER) Yes 0 deps
5 10.9.4 (fresh node_modules, forced "added" not "up to date") default install flags Yes 0 deps
6 10.4.0 default install flags Yes (same prefix error) 0 deps
7 8.19.4 default install flags Different warning (_auth/scoping, not prefix) 0 deps
8 10.9.4, JFROG_RUN_NATIVE=true, clean .npmrc (no prefix/registry lines) native install flags No warning at all, npm ls fully successful 0 deps
... (jf 2.120.0 vs 2.122.0 makes no difference; tested both) 0 deps
Row 8 is the key finding: a run with zero errors, zero warnings, and a confirmed-successful internal npm ls call still produced an empty build-info. This rules out the prefix bug (below) as the root cause of the missing dependencies — it's a real, separate bug that happens to co-occur, not the explanation.
Bug #2 (secondary, real, but not the root cause of Bug #1): internally-invoked npm ls/npm config get cache fail with prefix cannot be changed from project config
Observed error
npm error config prefix cannot be changed from project config: /path/to/project/.npmrc.
(with npm 10.x; with npm 8.19.4 this specific error does not occur, but a different warning about unscoped _auth/username/_password appears instead — see jfrog/build-info#764, a related, already-reported issue about the extractor not running npm config fix after writing its own .npmrc)
Root cause, captured live
jf npm install --build-name=... swaps out the project's real .npmrc for a temporary one during the internal npm ls/npm config get cache calls (confirmed via --debug log):
Debug: The file /path/to/project/.npmrc was backed up successfully to jfrog.npmrc.backup
Debug: Removing existing .npmrc file
Debug: Creating temporary .npmrc file.
Debug: Running 'npm ls --json --all --long' command.
Warn: Encountered some issues while running 'npm ls' command:
npm error config prefix cannot be changed from project config: /path/to/project/.npmrc.
...
Debug: Restored the file /path/to/project/.npmrc successfully
We captured the actual contents of that ephemeral, otherwise-invisible temporary .npmrc by backgrounding the jf process and reading the file mid-execution (it exists for several seconds before being restored):
prefix = "/opt/homebrew"
json = false
registry = https://repo.ds365.ai/artifactory/api/npm/
jf itself writes an explicit prefix = "..." line into this temporary, project-level .npmrc (apparently copying the machine's active npm config get prefix value). npm ≥ (at least) 10.4.0 rejects any prefix value set at what it considers the "project config" layer with a hard error, regardless of what the user's own .npmrc contains (we verified the user's real .npmrc had no prefix line at all — the offending line is added by jf, not present in the source file).
Request: don't write prefix into the temporary swapped .npmrc at all — nothing in this workflow appears to require pinning prefix, and doing so breaks the very npm ls call this mechanism exists to support.
Bug #3 (separate, unrelated, but dangerous): reusing a build number returns a stale/cached scan result instead of re-scanning
Steps to reproduce
Publish and scan build-A / 1 (real content, e.g. 750 dependencies) — scan takes ~90s, correctly reports real findings.
Modify the dependency tree (e.g. apply a fix), republish build-info under the same build-A / 1 (a new revision of the same name+number).
Run jf bs build-A 1 --project= --vuln (with and without --rescan=true) again.
Actual
The scan completes in ~7 seconds (vs. ~90s for a genuine scan of similar size) and reports 0 vulnerabilities, even when we deliberately did not fix all known-vulnerable components (confirmed via GET /artifactory/api/build/... that the newly-published revision still listed the same-version vulnerable components as before). Waiting up to 60s and calling --rescan=true did not change the result.
Expected / workaround
Publishing under a new, never-before-used number for the same build name, with identical dependency content, produces a correct ~90s scan with the real findings. This strongly suggests some layer (build revision handling, or an Xray-side cache keyed on name+number rather than content hash) is short-circuiting the scan for a previously-seen name+number pair.
Request: either document this explicitly (so users know never to reuse a build number, which is not currently mentioned anywhere in jf bs --help), or fix the caching so it's keyed on content/checksum rather than name+number alone.
Root cause analysis performed (ruled out, in order)
❌ Local .npmrc content (prefix, registry lines) — cleaned to bare minimum, bug persisted.
❌ jf CLI version — reproduced on 2.120.0 and 2.122.0.
❌ npm install vs npm ci.
❌ JFROG_RUN_NATIVE=true (native mode) — bug #2 warning disappears, but dependencies are still 0 (see matrix row 8).
❌ Passing build-name/number via flags vs JFROG_CLI_BUILD_NAME/JFROG_CLI_BUILD_NUMBER env vars.
❌ "Up to date" vs forced-fresh install (rm -rf node_modules before running) — no difference.
❌ npm version — reproduced on 8.19.4, 10.4.0, 10.9.4 (spanning 3 major generations with entirely different warning behavior on the internal npm ls call).
✅ Confirmed via direct inspection of build-info-go's public source (build/utils/npm.go, CalculateDependenciesMap/runNpmLsWithNodeModules) that dependency collection depends entirely on the npm ls --json --all --long subprocess call succeeding and returning a "dependencies" key — but we could not get further visibility into why, in our environment, this consistently yields nothing even when the exact same subprocess call, run manually with identical flags and working directory, succeeds.
We were unable to go further without adding instrumentation to the jf binary itself or getting access to JFrog's own internal debugging.
Working workaround currently in use
Since jf audit --sca (a different code path) and a bare npm ls --json --all --long both work correctly and return real data, we bypass the broken extractor entirely:
Run npm ls --json --all --long directly (not via jf).
For each resolved dependency, compute real sha1/md5/sha256 checksums by looking up its tarball in the local npm cache (~/.npm/_cacache/content-v2//<hex[0:2]>/<hex[2:4]>/<hex[4:]>, keyed from the integrity field npm already reports).
Construct a build-info JSON matching the public schema (build-info-go/entities/buildinfo.go) by hand: {name, number, agent, buildAgent, started, modules: [{type: "npm", id, dependencies: [{id, type, scopes, sha1, md5, sha256}]}]}.
PUT it directly: jf api -X PUT --input build-info.json -H "Content-Type: application/vnd.org.jfrog.artifactory+json" "/artifactory/api/build?project=".
jf bs --project= --vuln.
This works reliably and Xray correctly indexes and scans the resulting build — provided every dependency includes a checksum. We separately confirmed that a build-info with correct modules/dependencies but no checksums is accepted (204) but never gets indexed by Xray (SBOM: 0, Vulnerabilities: 0 in the UI, indefinitely) — this may be worth documenting explicitly in the build-info schema docs, since it's not an obvious requirement and the API doesn't reject or warn about its absence.
Related existing (unresolved) issues
jfrog/jfrog-cli#1822 — "XRay doesn't scan dependencies in NPM builds" (same symptom, open, no maintainer response)
#321 — same title, same symptom, open, no maintainer response
jfrog/build-info#764 — npm ≥9 _auth config format issue in the same extractor, closed "not planned"
npm/cli#7501 — upstream npm bug behind the prefix cannot be changed from project config message (Bug #2 here)
Requested from JFrog
Root-cause why CalculateDependenciesMap/the npm build-info extractor produces zero dependencies in this environment even when its own internal npm ls subprocess call succeeds cleanly (matrix row 8) — this suggests the bug may not even be in the npm ls invocation itself, but somewhere after (parsing, or a step gating collection on something unrelated to npm ls's success).
Stop writing prefix into the temporary swapped .npmrc (Bug #2) — separate, real, cheap fix.
Either fix or clearly document the build-number-reuse stale-scan behavior (Bug #3).
Document the checksum requirement for Xray to index a manually- or CLI-published npm build-info module.
Happy to provide the two full JSON build-info examples (empty vs. our manually-constructed working one), full --debug logs, and the reproduction script on request.
Reproduction steps
No response
Expected behavior
No response
JFrog CLI-Security version
v1.35.0
JFrog CLI version (if applicable)
JFrog CLI (jf) | 2.120.0 / 2.122.0
Operating system type and version
macOS, darwin/arm64
JFrog Xray version
No response
Describe the bug
Bug Report:
jf npm installnever populates dependencies in published build-infoSummary
jf npm install --build-name=<name> --build-number=<number>(followed byjf rt build-publishandjf bs) consistently produces a build-info record with zero modules/dependencies, even though:npm install/npm cicompletes successfully with the full dependency tree resolved on disk.npm ls --json --all --long(run outside ofjf, in the exact same project directory, same shell, same environment) returns the correct, complete dependency tree with no errors.https://github.com//pull/2 (secondary, real, but not the root cause of Bug #1): internally-invoked npm ls/npm config get cache fail with prefix cannot be changed from project config
Observed error
npm error config prefix cannot be changed from project config: /path/to/project/.npmrc.
(with npm 10.x; with npm 8.19.4 this specific error does not occur, but a different warning about unscoped _auth/username/_password appears instead — see jfrog/build-info#764, a related, already-reported issue about the extractor not running npm config fix after writing its own .npmrc)
Root cause, captured live
jf npm install --build-name=... swaps out the project's real .npmrc for a temporary one during the internal npm ls/npm config get cache calls (confirmed via --debug log):
Debug: The file /path/to/project/.npmrc was backed up successfully to jfrog.npmrc.backup
Debug: Removing existing .npmrc file
Debug: Creating temporary .npmrc file.
Debug: Running 'npm ls --json --all --long' command.
Warn: Encountered some issues while running 'npm ls' command:
npm error config prefix cannot be changed from project config: /path/to/project/.npmrc.
...
Debug: Restored the file /path/to/project/.npmrc successfully
We captured the actual contents of that ephemeral, otherwise-invisible temporary .npmrc by backgrounding the jf process and reading the file mid-execution (it exists for several seconds before being restored):
prefix = "/opt/homebrew"
json = false
registry = https://repo.ds365.ai/artifactory/api/npm/<repo>
jf itself writes an explicit prefix = "..." line into this temporary, project-level .npmrc (apparently copying the machine's active npm config get prefix value). npm ≥ (at least) 10.4.0 rejects any prefix value set at what it considers the "project config" layer with a hard error, regardless of what the user's own .npmrc contains (we verified the user's real .npmrc had no prefix line at all — the offending line is added by jf, not present in the source file).
Request: don't write prefix into the temporary swapped .npmrc at all — nothing in this workflow appears to require pinning prefix, and doing so breaks the very npm ls call this mechanism exists to support.
Bug #3 (separate, unrelated, but dangerous): reusing a build number returns a stale/cached scan result instead of re-scanning
Steps to reproduce
Publish and scan build-A / 1 (real content, e.g. 750 dependencies) — scan takes ~90s, correctly reports real findings.
Modify the dependency tree (e.g. apply a fix), republish build-info under the same build-A / 1 (a new revision of the same name+number).
Run jf bs build-A 1 --project= --vuln (with and without --rescan=true) again.
Actual
The scan completes in ~7 seconds (vs. ~90s for a genuine scan of similar size) and reports 0 vulnerabilities, even when we deliberately did not fix all known-vulnerable components (confirmed via GET /artifactory/api/build/... that the newly-published revision still listed the same-version vulnerable components as before). Waiting up to 60s and calling --rescan=true did not change the result.
Expected / workaround
Publishing under a new, never-before-used number for the same build name, with identical dependency content, produces a correct 90s scan with the real findings. This strongly suggests some layer (build revision handling, or an Xray-side cache keyed on name+number rather than content hash) is short-circuiting the scan for a previously-seen name+number pair.
Request: either document this explicitly (so users know never to reuse a build number, which is not currently mentioned anywhere in jf bs --help), or fix the caching so it's keyed on content/checksum rather than name+number alone.
Root cause analysis performed (ruled out, in order)
❌ Local .npmrc content (prefix, registry lines) — cleaned to bare minimum, bug persisted.
❌ jf CLI version — reproduced on 2.120.0 and 2.122.0.
❌ npm install vs npm ci.
❌ JFROG_RUN_NATIVE=true (native mode) — bug #2 warning disappears, but dependencies are still 0 (see matrix row 8).
❌ Passing build-name/number via flags vs JFROG_CLI_BUILD_NAME/JFROG_CLI_BUILD_NUMBER env vars.
❌ "Up to date" vs forced-fresh install (rm -rf node_modules before running) — no difference.
❌ npm version — reproduced on 8.19.4, 10.4.0, 10.9.4 (spanning 3 major generations with entirely different warning behavior on the internal npm ls call).
✅ Confirmed via direct inspection of build-info-go's public source (build/utils/npm.go, CalculateDependenciesMap/runNpmLsWithNodeModules) that dependency collection depends entirely on the npm ls --json --all --long subprocess call succeeding and returning a "dependencies" key — but we could not get further visibility into why, in our environment, this consistently yields nothing even when the exact same subprocess call, run manually with identical flags and working directory, succeeds.
We were unable to go further without adding instrumentation to the jf binary itself or getting access to JFrog's own internal debugging.
Working workaround currently in use
Since jf audit --sca (a different code path) and a bare npm ls --json --all --long both work correctly and return real data, we bypass the broken extractor entirely:
Run npm ls --json --all --long directly (not via jf).
For each resolved dependency, compute real sha1/md5/sha256 checksums by looking up its tarball in the local npm cache (/.npm/_cacache/content-v2//<hex[0:2]>/<hex[2:4]>/<hex[4:]>, keyed from the integrity field npm already reports).
Construct a build-info JSON matching the public schema (build-info-go/entities/buildinfo.go) by hand: {name, number, agent, buildAgent, started, modules: [{type: "npm", id, dependencies: [{id, type, scopes, sha1, md5, sha256}]}]}.
PUT it directly: jf api -X PUT --input build-info.json -H "Content-Type: application/vnd.org.jfrog.artifactory+json" "/artifactory/api/build?project=".
jf bs --project= --vuln.
This works reliably and Xray correctly indexes and scans the resulting build — provided every dependency includes a checksum. We separately confirmed that a build-info with correct modules/dependencies but no checksums is accepted (204) but never gets indexed by Xray (SBOM: 0, Vulnerabilities: 0 in the UI, indefinitely) — this may be worth documenting explicitly in the build-info schema docs, since it's not an obvious requirement and the API doesn't reject or warn about its absence.
Related existing (unresolved) issues
jfrog/jfrog-cli#1822 — "XRay doesn't scan dependencies in NPM builds" (same symptom, open, no maintainer response)
#321 — same title, same symptom, open, no maintainer response
jfrog/build-info#764 — npm ≥9 _auth config format issue in the same extractor, closed "not planned"
npm/cli#7501 — upstream npm bug behind the prefix cannot be changed from project config message (Bug #2 here)
Requested from JFrog
Root-cause why CalculateDependenciesMap/the npm build-info extractor produces zero dependencies in this environment even when its own internal npm ls subprocess call succeeds cleanly (matrix row 8) — this suggests the bug may not even be in the npm ls invocation itself, but somewhere after (parsing, or a step gating collection on something unrelated to npm ls's success).
Stop writing prefix into the temporary swapped .npmrc (Bug #2) — separate, real, cheap fix.
Either fix or clearly document the build-number-reuse stale-scan behavior (Bug #3).
Document the checksum requirement for Xray to index a manually- or CLI-published npm build-info module.
Happy to provide the two full JSON build-info examples (empty vs. our manually-constructed working one), full --debug logs, and the reproduction script on request.
Bug Report: jf npm install never populates dependencies in published build-info
Summary
jf npm install --build-name= --build-number= (followed by jf rt build-publish and jf bs) consistently produces a build-info record with zero modules/dependencies, even though:
The underlying npm install/npm ci completes successfully with the full dependency tree resolved on disk.
A plain, standalone npm ls --json --all --long (run outside of jf, in the exact same project directory, same shell, same environment) returns the correct, complete dependency tree with no errors.
This was reproduced consistently across 3 different npm major versions, 2 jf CLI versions, and every combination of the CLI's own npm-install strategies (native vs default), install command (install vs ci), and build-name/number supplied via flags vs environment variables — 19 combinations tested, 100% reproduction rate.
A secondary, distinct bug was found and root-caused along the way (npm ls/npm config get cache failing with prefix cannot be changed from project config), but it turned out to be a red herring, not the root cause of the empty build-info — a run with zero warnings and a fully successful npm ls was equally empty. The true root cause was never isolated further than "the dependency-collection step of the npm build-info extractor silently produces nothing," documented below with full evidence.
A working manual workaround was found (construct the build-info JSON directly from a standalone npm ls + real checksums resolved from the local npm cache, then PUT it to /artifactory/api/build) and is described at the end, in case it's useful context for where in the pipeline the real extractor's output diverges.
A separate, unrelated bug was also found during this investigation and is reported here too, since it's dangerous from a data-integrity standpoint: re-scanning a build under an already-used build number silently returns a stale/cached result instead of re-scanning.
Environment
Component Version
JFrog CLI (jf) 2.120.0, and independently reproduced on 2.122.0 (installed via npm install -g jfrog-cli-v2-jf)
npm Reproduced on 8.19.4, 10.4.0, and 10.9.4
Node.js v22.22.0
OS / Arch macOS, darwin/arm64
JFrog Platform JFrog Cloud (SaaS)
Project structure Single package.json + package-lock.json per project (no npm workspaces/monorepo), ~750–1040 resolved packages
Codebases reproduced against
The bug was reproduced independently on two unrelated, real-world codebases, ruling out anything project-specific:
Codebase Stack Key devDependency (build tooling) Resolved packages
Frontend Angular 21 SPA @angular/cli, @angular-devkit/build-angular 934
Backend NestJS 11 REST API (TypeORM + PostgreSQL) @nestjs/cli 750–751
Both are conventional single-package (non-monorepo, non-workspaces) npm projects with a committed package-lock.json, built and run with Node.js v22.22.0. Neither project uses npm workspaces, Yarn, or pnpm — plain npm throughout.
Bug #1 (primary): npm build-info extractor produces an empty modules[].dependencies array
Steps to reproduce
cd /path/to/npm-project # has package.json, package-lock.json
npm install # confirm this succeeds and node_modules is populated
jf npm install --build-name=my-build --build-number=1
jf rt build-publish my-build 1 --project=
jf bs my-build 1 --project= --vuln
Expected
The published build-info's modules[0].dependencies array contains one entry per resolved npm package (matching what npm ls reports), and the subsequent Xray scan reports real vulnerabilities matching what jf audit --sca finds for the same project.
Actual
GET /artifactory/api/build//?project= returns a buildInfo object with no modules key at all:
{
"buildInfo": {
"version": "1.0.1",
"name": "my-build",
"number": "1",
"buildAgent": { "name": "GENERIC", "version": "2.122.0" },
"agent": { "name": "jfrog-cli-go", "version": "2.122.0" },
"started": "2026-08-26T21:03:39.177-0300",
"durationMillis": 0,
"artifactoryPrincipal": ""
}
}
The Xray scan (jf bs) correctly reports Overall: Done, SCA: Done (confirmed via the Scans List UI's "Scan Status Details" panel — SCA genuinely ran, it is not skipped), but with 0 vulnerable dependencies, 0 SBOM components — because there is nothing in the build-info to scan, not because the project is clean. Cross-checked: jf audit --sca on the identical project reports real, known CVEs (6–9 findings depending on the project) for the same package-lock.json.
Local evidence: the extractor's own partials cache is empty
Before jf rt build-publish runs, the CLI writes a local per-build cache directory:
$TMPDIR/jfrog-/builds//partials/
In every single reproduction (19 combinations, see matrix below), this directory contains only one file, details, with nothing but a timestamp:
{ "Timestamp": "2026-08-26T21:05:26.704282-03:00" }
No dependency/module partial file is ever written — this is true regardless of whether the internal npm ls call below succeeds or fails.
Reproduction matrix (all combinations tested, all resulted in 0 dependencies)
npm version Mode Install command build-name/number via npm ls warning? Result
1 10.9.4 default install flags Yes (prefix error) 0 deps
2 10.9.4 JFROG_RUN_NATIVE=true install flags No 0 deps
3 10.9.4 default ci flags Yes 0 deps
4 10.9.4 default install env vars (JFROG_CLI_BUILD_NAME/NUMBER) Yes 0 deps
5 10.9.4 (fresh node_modules, forced "added" not "up to date") default install flags Yes 0 deps
6 10.4.0 default install flags Yes (same prefix error) 0 deps
7 8.19.4 default install flags Different warning (_auth/scoping, not prefix) 0 deps
8 10.9.4, JFROG_RUN_NATIVE=true, clean .npmrc (no prefix/registry lines) native install flags No warning at all, npm ls fully successful 0 deps
... (jf 2.120.0 vs 2.122.0 makes no difference; tested both) 0 deps
Row 8 is the key finding: a run with zero errors, zero warnings, and a confirmed-successful internal npm ls call still produced an empty build-info. This rules out the prefix bug (below) as the root cause of the missing dependencies — it's a real, separate bug that happens to co-occur, not the explanation.
Bug #2 (secondary, real, but not the root cause of Bug #1): internally-invoked npm ls/npm config get cache fail with prefix cannot be changed from project config
Observed error
npm error config prefix cannot be changed from project config: /path/to/project/.npmrc.
(with npm 10.x; with npm 8.19.4 this specific error does not occur, but a different warning about unscoped _auth/username/_password appears instead — see jfrog/build-info#764, a related, already-reported issue about the extractor not running npm config fix after writing its own .npmrc)
Root cause, captured live
jf npm install --build-name=... swaps out the project's real .npmrc for a temporary one during the internal npm ls/npm config get cache calls (confirmed via --debug log):
Debug: The file /path/to/project/.npmrc was backed up successfully to jfrog.npmrc.backup
Debug: Removing existing .npmrc file
Debug: Creating temporary .npmrc file.
Debug: Running 'npm ls --json --all --long' command.
Warn: Encountered some issues while running 'npm ls' command:
npm error config prefix cannot be changed from project config: /path/to/project/.npmrc.
...
Debug: Restored the file /path/to/project/.npmrc successfully
We captured the actual contents of that ephemeral, otherwise-invisible temporary .npmrc by backgrounding the jf process and reading the file mid-execution (it exists for several seconds before being restored):
prefix = "/opt/homebrew"
json = false
registry = https://repo.ds365.ai/artifactory/api/npm/
jf itself writes an explicit prefix = "..." line into this temporary, project-level .npmrc (apparently copying the machine's active npm config get prefix value). npm ≥ (at least) 10.4.0 rejects any prefix value set at what it considers the "project config" layer with a hard error, regardless of what the user's own .npmrc contains (we verified the user's real .npmrc had no prefix line at all — the offending line is added by jf, not present in the source file).
Request: don't write prefix into the temporary swapped .npmrc at all — nothing in this workflow appears to require pinning prefix, and doing so breaks the very npm ls call this mechanism exists to support.
Bug #3 (separate, unrelated, but dangerous): reusing a build number returns a stale/cached scan result instead of re-scanning
Steps to reproduce
Publish and scan build-A / 1 (real content, e.g. 750 dependencies) — scan takes ~90s, correctly reports real findings.
Modify the dependency tree (e.g. apply a fix), republish build-info under the same build-A / 1 (a new revision of the same name+number).
Run jf bs build-A 1 --project= --vuln (with and without --rescan=true) again.
Actual
The scan completes in ~7 seconds (vs. ~90s for a genuine scan of similar size) and reports 0 vulnerabilities, even when we deliberately did not fix all known-vulnerable components (confirmed via GET /artifactory/api/build/... that the newly-published revision still listed the same-version vulnerable components as before). Waiting up to 60s and calling --rescan=true did not change the result.
Expected / workaround
Publishing under a new, never-before-used number for the same build name, with identical dependency content, produces a correct ~90s scan with the real findings. This strongly suggests some layer (build revision handling, or an Xray-side cache keyed on name+number rather than content hash) is short-circuiting the scan for a previously-seen name+number pair.
Request: either document this explicitly (so users know never to reuse a build number, which is not currently mentioned anywhere in jf bs --help), or fix the caching so it's keyed on content/checksum rather than name+number alone.
Root cause analysis performed (ruled out, in order)
❌ Local .npmrc content (prefix, registry lines) — cleaned to bare minimum, bug persisted.
❌ jf CLI version — reproduced on 2.120.0 and 2.122.0.
❌ npm install vs npm ci.
❌ JFROG_RUN_NATIVE=true (native mode) — bug #2 warning disappears, but dependencies are still 0 (see matrix row 8).
❌ Passing build-name/number via flags vs JFROG_CLI_BUILD_NAME/JFROG_CLI_BUILD_NUMBER env vars.
❌ "Up to date" vs forced-fresh install (rm -rf node_modules before running) — no difference.
❌ npm version — reproduced on 8.19.4, 10.4.0, 10.9.4 (spanning 3 major generations with entirely different warning behavior on the internal npm ls call).
✅ Confirmed via direct inspection of build-info-go's public source (build/utils/npm.go, CalculateDependenciesMap/runNpmLsWithNodeModules) that dependency collection depends entirely on the npm ls --json --all --long subprocess call succeeding and returning a "dependencies" key — but we could not get further visibility into why, in our environment, this consistently yields nothing even when the exact same subprocess call, run manually with identical flags and working directory, succeeds.
We were unable to go further without adding instrumentation to the jf binary itself or getting access to JFrog's own internal debugging.
Working workaround currently in use
Since jf audit --sca (a different code path) and a bare npm ls --json --all --long both work correctly and return real data, we bypass the broken extractor entirely:
Run npm ls --json --all --long directly (not via jf).
For each resolved dependency, compute real sha1/md5/sha256 checksums by looking up its tarball in the local npm cache (~/.npm/_cacache/content-v2//<hex[0:2]>/<hex[2:4]>/<hex[4:]>, keyed from the integrity field npm already reports).
Construct a build-info JSON matching the public schema (build-info-go/entities/buildinfo.go) by hand: {name, number, agent, buildAgent, started, modules: [{type: "npm", id, dependencies: [{id, type, scopes, sha1, md5, sha256}]}]}.
PUT it directly: jf api -X PUT --input build-info.json -H "Content-Type: application/vnd.org.jfrog.artifactory+json" "/artifactory/api/build?project=".
jf bs --project= --vuln.
This works reliably and Xray correctly indexes and scans the resulting build — provided every dependency includes a checksum. We separately confirmed that a build-info with correct modules/dependencies but no checksums is accepted (204) but never gets indexed by Xray (SBOM: 0, Vulnerabilities: 0 in the UI, indefinitely) — this may be worth documenting explicitly in the build-info schema docs, since it's not an obvious requirement and the API doesn't reject or warn about its absence.
Related existing (unresolved) issues
jfrog/jfrog-cli#1822 — "XRay doesn't scan dependencies in NPM builds" (same symptom, open, no maintainer response)
#321 — same title, same symptom, open, no maintainer response
jfrog/build-info#764 — npm ≥9 _auth config format issue in the same extractor, closed "not planned"
npm/cli#7501 — upstream npm bug behind the prefix cannot be changed from project config message (Bug #2 here)
Requested from JFrog
Root-cause why CalculateDependenciesMap/the npm build-info extractor produces zero dependencies in this environment even when its own internal npm ls subprocess call succeeds cleanly (matrix row 8) — this suggests the bug may not even be in the npm ls invocation itself, but somewhere after (parsing, or a step gating collection on something unrelated to npm ls's success).
Stop writing prefix into the temporary swapped .npmrc (Bug #2) — separate, real, cheap fix.
Either fix or clearly document the build-number-reuse stale-scan behavior (Bug #3).
Document the checksum requirement for Xray to index a manually- or CLI-published npm build-info module.
Happy to provide the two full JSON build-info examples (empty vs. our manually-constructed working one), full --debug logs, and the reproduction script on request.
Reproduction steps
No response
Expected behavior
No response
JFrog CLI-Security version
v1.35.0
JFrog CLI version (if applicable)
JFrog CLI (jf) | 2.120.0 / 2.122.0
Operating system type and version
macOS, darwin/arm64
JFrog Xray version
No response