Repository navigation
fix(docker): make Node.js use the image's CA certificates - #2823
Conversation
The runtime image copies Node.js from node:24-slim for the Jellyfin Web installer. Node.js verifies TLS against its own bundled CA list, so Debian's ca-certificates updates never reach it. Debian's 20250419+deb13u1 drops the emSign ECC Root CA - G3 that the compromised eMudhra intermediates chain to; the list bundled with Node.js 24.22.0 still trusts it. The installer runs npm exec for the npm release a Jellyfin Web version declares in engines.npm. npm checks that download only against the integrity value the registry sends over the same connection, so a forged registry certificate would let someone on the network path serve a modified npm that then runs inside the Silo container. Set NODE_OPTIONS=--use-openssl-ca so Node.js uses the system store that curl, git and apt already use.
Silo Kody — review completeReview finished. Check the inline comments for findings and verify each suggestion against the code and tests. Reviewing changes in Silo
Review settingsReview OptionsThe following review options are enabled or disabled:
|
|
Suggested fix: Add before-and-after evidence of the Jellyfin Web install flow under Evidence. Automated check: Macroscope check run agent (gpt-6-luna). Evidence was not reviewed for correctness. Posted via Macroscope — Visible change evidence |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
Problem
Related issue: N/A
Validation tasks: none
The Node.js in the Silo image still trusts the compromised eMudhra intermediate certificates, and a
ca-certificatesupdate doesn't change that. Node.js checks TLS against its own bundled CA list, not the system store. Debian'sca-certificates20250419+deb13u1 removes the two roots in its store that those intermediates chain up to, emSign ECC Root CA - G3 and AAA Certificate Services. The list bundled with Node.js 24.22.0 (the currentnode:24-slim) has already dropped AAA Certificate Services but still has emSign ECC Root CA - G3, and Node.js hasn't released an updated list yet.Silo uses this Node.js for the Jellyfin Web installer. When a Jellyfin Web release declares
engines.npm, the installer runsnpm exec --yes --package=npm@<range>, which downloads that npm release from the registry and checks it only against the integrity value the registry sends over the same connection. Someone who can intercept the server's traffic and holds a registry certificate issued through one of those intermediates could serve a modified npm, which then runs inside the Silo container. Jellyfin Web's own dependencies are less exposed, becausenpm cichecks them against the hashes in Jellyfin Web's lockfile.Approach
Set
NODE_OPTIONS=--use-openssl-cain the runtime image, so Node.js reads the same system store as curl, git and apt. Node.js then trusts what Debian'sca-certificatestrusts, and a future removal reaches npm with the next image build instead of waiting for a Node.js release.--use-system-cadoesn't fix this: it adds the system store to the bundled list, and Node.js with that flag still loads the test host below.The flag only helps in an image built with Debian's patched store. The published build-1559 has
ca-certificates20250419, and Node.js there loads the test host with or without the flag. The next image build picks up the patched store; #2822 unblocks it by getting CI onmaingreen again.Validation
In throwaway containers from my own image built from the same runtime stage, with
ca-certificates20250419+deb13u1 and Node.js 24.22.0:fetchandnpm viewboth load https://dv-g3b-emudhra.we-have-a-problem.com/, a test host served through one of the compromised intermediates.NODE_OPTIONS=--use-openssl-ca, both fail withUNABLE_TO_GET_ISSUER_CERT_LOCALLY, as doesfetchto https://dv-g3b-aaa-emudhra.we-have-a-problem.com/, the test host on the AAA Certificate Services path.fetchto registry.npmjs.org and github.com still works, and the installer's path,npm exec --yes --package="npm@>=11.6.0 <12" -- npm --version, downloads and runs npm 11.21.1.npm exec --yes --package="npm@>=11.0.0" -- npm ci), completes with the flag and installs 1,771 packages.npm run build:productionstep, which doesn't use the network, and a local build of this branch's image.4fcbed1(run).Evidence
Evidence: none, no user-visible change
A Jellyfin Web install looks and behaves the same as before. It only differs when someone intercepts the connection with a certificate from these intermediates: npm then stops with a certificate error instead of installing, which the Validation section shows against the test hosts.
Risks
Every Node.js process in the container, which today means the Jellyfin Web installer, trusts exactly Debian's store instead of Node.js's bundled list. Both follow Mozilla's root store, so registry.npmjs.org and GitHub are unaffected. An operator who sets their own
NODE_OPTIONSreplaces this value, as with any imageENV.Checklist
AI Disclosure