Skip to content

fix(docker): eliminate npm-bundled CVEs from the published image - #10182

Merged
diegosouzapw merged 2 commits into
release/v3.8.50from
fix/trivy-npm-bundled-0812
Aug 12, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.50from
fix/trivy-npm-bundled-0812

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Clears the 9 Trivy alerts raised against the npm CLI's own bundled node_modules
inside the published image. These are npm internals, not application dependencies:

Path CVEs
usr/local/lib/node_modules/npm/node_modules/brace-expansion/package.json CVE-2026-69152, CVE-2026-14257
usr/local/lib/node_modules/npm/node_modules/ip-address/package.json CVE-2026-69192, CVE-2026-69198, CVE-2026-54272
usr/local/lib/node_modules/npm/node_modules/tar/package.json GHSA-r292-9mhp-454m
usr/local/lib/node_modules/npm/node_modules/undici/package.json CVE-2026-16729, CVE-2026-16728, CVE-2026-15157

No npm release fixes these

The base stage already ran npm install -g npm@latest, and its comment claimed that
shipped patched copies. That claim was false. Measured directly off the published
tarball of the latest npm (npm pack npm@12.0.2, 2026-08-12):

brace-expansion: 5.0.7    (fixed in >= 5.0.9)
ip-address:      10.2.0   (fixed in >= 10.3.1)
tar:             7.5.19   (fixed in >= 7.5.21)
undici:          6.27.0   (fixed in >= 6.28.0)

Every one still vulnerable. So npm install -g npm@latest was buying build time and
zero CVEs — the alerts could never clear by refreshing npm.

Chosen fix — overlay the patched copies (option B)

The base stage now installs the fixed versions into a scratch prefix with
--install-strategy=nested (so each replacement is self-contained) and copies them over
npm's bundled ones, then smoke-tests the result:

brace-expansion@5.0.9  ip-address@10.5.0  tar@7.5.22  undici@6.28.0

Semver-compatible with the ranges npm's own tree declares — minimatch → brace-expansion ^5.0.5, socks → ip-address ^10.1.1, node-gyp → tar ^7.5.4 and node-gyp → undici ^6.25.0. That last one is why undici stays on the 6.x line: the current undici@8.10.0
would fall outside node-gyp's range, and node-gyp is what the builder stage uses to
compile better-sqlite3. The loop test -ds each target first, so a future npm layout
change fails the build loudly instead of silently skipping the patch.

Alternative discarded — deleting npm from the runner stages (option A)

Rejected on evidence. The old comment asserted "npm is not invoked at runtime in the
runner stages"; that is wrong. The application shells out to npm at runtime in four
places:

  • src/lib/services/installers/utils.ts::runNpm — embedded services (bifrost, ninerouter, mux, dario) install/update/version-check
  • src/lib/system/globalPackagePath.ts — npm root -g
  • src/lib/system/autoUpdate.ts — the npm auto-update channel
  • src/app/api/system/version/route.ts — version/update endpoints

Removing npm would silently break embedded services and auto-update inside Docker.
runner-cli (npm install -g @openai/codex @anthropic-ai/claude-code droid openclaw) would
break at build time as well. The false comment is corrected in this PR.

.trivyignore (option C) — not used

No suppression added. This is a real fix; .trivyignore stays empty, per its own header
policy (prefer fixing over suppressing).

Validation

I could not run docker build — this sandbox has no Docker daemon (dial unix /var/run/docker.sock: no such file), so docker build --check is unavailable too. What
was validated for real instead:

  1. The claim — npm pack npm@12.0.2 + reading the bundled package.jsons produced the four vulnerable versions quoted above.
  2. The overlay, end to end, against a real npm tree — installed npm@12.0.2 into a scratch prefix, ran the exact overlay steps, and confirmed 5.0.7 → 5.0.9, 10.2.0 → 10.5.0, 7.5.19 → 7.5.22, 6.27.0 → 6.28.0.
  3. The patched npm still works — npm --version → 12.0.2; npm pack npm@12.0.2 --dry-run (exercises pacote + tar) succeeded; npm view tar version (registry round-trip) returned 7.5.22; minimatch, socks, and node-gyp all resolve the replaced packages (node-gyp tar 7.5.22 | undici 6.28.0).
  4. Shell syntax — the RUN block was extracted with Docker's line-continuation joining applied and passed bash -n.
  5. Regression guard — tests/unit/dockerfile-npm-bundled-cve-patch.test.ts, 5 tests, all passing. Mutation-checked: reverting the pin to brace-expansion@5.0.7 makes it fail.

The remaining end-to-end proof is a clean Trivy scan on the next published image.

Note on the second commit

chore(repo): re-untrack the _tasks self-referential symlink is not part of the fix.
_tasks is an absolute symlink into one machine's checkout; caf768e3c4 untracked it and
the DeepAI merge (44069c5f54, #9443) re-added it. While it is tracked,
scripts/check/check-tracked-artifacts.mjs fails on every pre-commit on this branch,
so nothing can be committed at all. Restoring the precedent fix was the only way to commit
without bypassing the Husky hook (Hard Rule #10). Drop that commit if it is being handled
elsewhere.

⚠️ base-red inherited: #9985

`caf768e3c4` untracked it; the DeepAI merge (44069c5, #9443) re-added
it. It is an absolute symlink pointing at one machine's checkout, and
AGENTS.md keeps `_tasks/` out of the main repo entirely. While tracked,
`check-tracked-artifacts.mjs` fails on pre-commit, so no commit can be
made on this branch at all — this restores the precedent fix purely to
unblock committing, and is unrelated to the Docker change that follows.
Trivy reported 9 HIGH/MEDIUM CVEs against the npm CLI's own bundled
node_modules inside the published image (brace-expansion, ip-address,
tar, undici under /usr/local/lib/node_modules/npm/node_modules).

The base stage claimed `npm install -g npm@latest` shipped patched
copies. It does not: npm@12.0.2 (latest) bundles brace-expansion 5.0.7,
ip-address 10.2.0, tar 7.5.19 and undici 6.27.0 — all still vulnerable.
No npm release fixes them, so that step was buying zero CVEs.

Overlay the patched versions onto npm's bundled tree instead, pinned and
semver-compatible with the ranges npm's own tree declares (minimatch ->
brace-expansion ^5.0.5, socks -> ip-address ^10.1.1, node-gyp -> tar
^7.5.4 and undici ^6.25.0, so undici stays on 6.x). Removing npm from
the runner stages was not viable — the app shells out to npm at runtime
(installers/utils.ts::runNpm, system/autoUpdate.ts,
system/globalPackagePath.ts, api/system/version) — and the old comment
asserting otherwise is corrected.
@diegosouzapw
diegosouzapw merged commit 3852ae5 into release/v3.8.50 Aug 12, 2026
9 of 10 checks passed
@diegosouzapw
diegosouzapw deleted the fix/trivy-npm-bundled-0812 branch August 13, 2026 08:02
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…gosouzapw#10182)

* chore(repo): re-untrack the _tasks self-referential symlink

`c1caabe7c5` untracked it; the DeepAI merge (7d85321, diegosouzapw#9443) re-added
it. It is an absolute symlink pointing at one machine's checkout, and
AGENTS.md keeps `_tasks/` out of the main repo entirely. While tracked,
`check-tracked-artifacts.mjs` fails on pre-commit, so no commit can be
made on this branch at all — this restores the precedent fix purely to
unblock committing, and is unrelated to the Docker change that follows.

* fix(docker): eliminate npm-bundled CVEs from the published image

Trivy reported 9 HIGH/MEDIUM CVEs against the npm CLI's own bundled
node_modules inside the published image (brace-expansion, ip-address,
tar, undici under /usr/local/lib/node_modules/npm/node_modules).

The base stage claimed `npm install -g npm@latest` shipped patched
copies. It does not: npm@12.0.2 (latest) bundles brace-expansion 5.0.7,
ip-address 10.2.0, tar 7.5.19 and undici 6.27.0 — all still vulnerable.
No npm release fixes them, so that step was buying zero CVEs.

Overlay the patched versions onto npm's bundled tree instead, pinned and
semver-compatible with the ranges npm's own tree declares (minimatch ->
brace-expansion ^5.0.5, socks -> ip-address ^10.1.1, node-gyp -> tar
^7.5.4 and undici ^6.25.0, so undici stays on 6.x). Removing npm from
the runner stages was not viable — the app shells out to npm at runtime
(installers/utils.ts::runNpm, system/autoUpdate.ts,
system/globalPackagePath.ts, api/system/version) — and the old comment
asserting otherwise is corrected.

---------

Co-authored-by: backryun <bakryun0718@proton.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants