From ad6ebbb9f5803b92b22b8803b491df19d7cf433b Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 21 Oct 2026 05:58:15 -0700 Subject: base: take emSign ECC Root CA - G3 out of bun's built-in root store Bun compiles Mozilla's roots into its binary and NODE_USE_SYSTEM_CA only adds to them, so the fixed Debian ca-certificates doesn't reach fetch/tls. docker/bun-distrust.ts overwrites the root's DER in place with a same-length self-signed placeholder (key discarded) at image build time. Drop it once a bun release removes the root itself. Co-Authored-By: Claude Opus 5.5 --- docker/bun-distrust.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 docker/bun-distrust.ts (limited to 'docker/bun-distrust.ts') diff --git a/docker/bun-distrust.ts b/docker/bun-distrust.ts new file mode 100644 index 00000000..3b324caf --- /dev/null +++ b/docker/bun-distrust.ts @@ -0,0 +1,38 @@ +// Bun compiles Mozilla's root store into its binary, and NODE_USE_SYSTEM_CA +// only adds to it. Until a Bun release drops a distrusted root, overwrite its +// DER in place with a same-length self-signed placeholder whose key was thrown +// away (zeroing it instead makes every TLS handshake fail). +// +// emSign ECC Root CA - G3: eMudhra intermediate key compromise, CVE-2026-111943. +import { readFileSync, writeFileSync } from "node:fs"; +import { getCACertificates } from "node:tls"; +import { X509Certificate } from "node:crypto"; + +const distrust = { + "emSign ECC Root CA - G3": + "MIICTjCCAdSgAwIBAgIUfFrc+EjEsB3lBo+k60ijjiGPT/IwCgYIKoZIzj0EAwIwXjEfMB0GA1UEAwwWeHh4eHh4eHh4eHh4eHh4eHh4eHh4eDE7MDkGA1UECgwyY29pbm9zIHJldm9rZWQgZW1TaWduIEVDQyBSb290IENBIC0gRzMgcGxhY2Vob2xkZXIwHhcNMjYxMDIxMTI1NjQzWhcNMzYxMDE4MTI1NjQzWjBeMR8wHQYDVQQDDBZ4eHh4eHh4eHh4eHh4eHh4eHh4eHh4MTswOQYDVQQKDDJjb2lub3MgcmV2b2tlZCBlbVNpZ24gRUNDIFJvb3QgQ0EgLSBHMyBwbGFjZWhvbGRlcjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBBYruwA2K1haT213dKQAOYRE5Gdy1PMXpM7V57BH6VwUToz9hkWTCXqeOr7/NctfY1fpqbT+tcKl0/OnTVwZuO3ePtUkTvREL/fuVKd9LTU/eTMT1Pqf2GQfVTmkS8C76NTMFEwHQYDVR0OBBYEFARKTDRI5W1qeguIYelguHqh3gvBMB8GA1UdIwQYMBaAFARKTDRI5W1qeguIYelguHqh3gvBMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIwIW/6zsZDBk7LGTetfJJzKqXbhsvMo+4ykb8JSHjLn5zoIsuyBPGvox71gdDPcttgAjEAu3xr0zjPLGQnlM4cwgv6mKTgiiXbt7HneYNN2hu/DvSuCj99c0KuGKEyfHGqotCL", +}; + +const bin = process.argv[2]; +let buf = readFileSync(bin); + +for (const [cn, placeholder] of Object.entries(distrust)) { + const pem = getCACertificates("bundled").find( + (p) => new X509Certificate(p).subject.split("\n").includes(`CN=${cn}`), + ); + if (!pem) { + console.log(`${cn}: not in bun's root store, nothing to do`); + continue; + } + const der = new X509Certificate(pem).raw; + const repl = Buffer.from(placeholder, "base64"); + if (repl.length !== der.length) + throw new Error(`${cn}: placeholder is ${repl.length} bytes, need ${der.length}`); + const at = buf.indexOf(der); + if (at < 0 || buf.indexOf(der, at + 1) >= 0) + throw new Error(`${cn}: expected exactly one copy in ${bin}`); + repl.copy(buf, at); + console.log(`${cn}: replaced at offset ${at}`); +} + +writeFileSync(bin, buf); -- cgit v1.2.3