aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docker/base.Dockerfile6
-rw-r--r--docker/bun-distrust.ts38
2 files changed, 44 insertions, 0 deletions
diff --git a/docker/base.Dockerfile b/docker/base.Dockerfile
index af323af9..594b0d70 100644
--- a/docker/base.Dockerfile
+++ b/docker/base.Dockerfile
@@ -1,3 +1,4 @@
+# docker build -f docker/base.Dockerfile -t ghcr.io/coinos/base docker/
FROM debian:trixie-slim
ARG BUN_VERSION=1.4.3
@@ -16,5 +17,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
/usr/share/doc /usr/share/man /usr/share/locale \
/usr/share/info /var/log/* /tmp/*
+COPY bun-distrust.ts /tmp/
+RUN cp /usr/local/bin/bun /tmp/bun \
+ && /tmp/bun /tmp/bun-distrust.ts /usr/local/bin/bun \
+ && rm /tmp/bun /tmp/bun-distrust.ts
+
RUN groupadd -g 1000 bun && useradd -u 1000 -g bun -m -s /bin/sh bun
WORKDIR /home/bun/app
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);