8000 SECURITY: bun, plannotator: drop emSign roots from bun's compiled-in store (CVE-2026-111943) by bryan-minimal · Pull Request #951 · gominimal/pkgs · GitHub
[go: up one dir, main page]

Skip to content

SECURITY: bun, plannotator: drop emSign roots from bun's compiled-in store (CVE-2026-111943) - #951

Merged
bryan-minimal merged 2 commits into
mainfrom
security/bun-emsign-roots
Oct 21, 2026
Merged

bryan-minimal merged 2 commits into
mainfrom
security/bun-emsign-roots

Conversation

@bryan-minimal
Copy link
Copy Markdown
Member

What

CVE-2026-111943. The private keys of eight eMudhra intermediate CAs are recoverable from their public certificates (Mozilla bug 2083449). In Mozilla-derived stores, those intermediates chain to emSign ECC Root CA - G3, through its cross-sign of emSign Root TLS CA - G3.

bun 1.3.14 compiles 145 Mozilla roots into packages/bun-usockets/src/crypto/root_certs.h, including emSign ECC Root CA - G3, and always loads them:

  • us_get_default_ca_store() in root_certs.cpp adds every root_certs[] entry unconditionally.
  • --use-system-ca / NODE_USE_SYSTEM_CA=1 only add the system store on top of that.

So neither the ca-certificates fix nor an env switch removes the root. It has to come out of the binary.

This PR carries cve-2026-111943-remove-emsign-roots.patch, which removes exactly the three roots NSS 3.131.1 dropped. They are also exactly the curl cacert 2026-09-25 → 2026-10-20 diff:

root SHA-256
emSign ECC Root CA - G3 86a1ecba…a36bbe6b
emSign Root CA - C1 125609aa…c8c8608f
emSign ECC Root CA - C3 bc4d809b…dc0d7eb3

Root count goes from 145 to 142. Nothing is added, and the other 142 entries are byte-identical: I parsed the old and new header and compared every DER by SHA-256.

Upstream: oven-sh/bun#46159 (ea6a2c7b, merged 2026-10-20) is not in any release yet; the latest is v1.4.3.

  • It edits root_certs.der on main, so it doesn't apply to 1.3.14's root_certs.h.
  • It also drops emSign Root CA - G1. NSS and curl still ship G1, so this patch keeps it.
  • Drop the patch on the first bun release that contains #46159.

plannotator: the fix wouldn't have reached its amd64 binary

bun build --compile embeds the running bun only when --target equals bun's own default target (CompileTarget.exePath → selfExePath). For any other target, it downloads @oven/bun-<target> from the npm registry at build time.

plannotator's amd64 build used --target=bun-linux-x64-baseline, and our bun is a non-baseline build. So its amd64 binary has embedded upstream's stock 1.3.14 baseline runtime: fetched unpinned, and still bundling emSign G3. This PR switches it to bun-linux-x64, which equals our default target, so bun embeds itself the same way it already does on arm64.

Baseline buys nothing in minimal: glibc and bun are both built with -march=x86-64-v3.

The other consumers of ../bun/build.ncl already embed our bun:

  • hunk: --compile with no --target.
  • opencode: script/build.ts --single picks bun-linux-<arch> (non-baseline, no abi), which is the default. Its execArgv also passes --use-system-ca, which adds the system store on top of the bundled roots.

Fail-closed checks

bun/build.sh:

  1. The patch is applied by name (never a glob), and patch is a new build dep.
  2. None of the three /* <label> */ blocks may remain in root_certs.h, and at least 100 roots must remain.
  3. A fingerprint check hashes every DER in tls.rootCertificates (the compiled-in store) with SHA-256. It requires at least 100 roots and none of the three.
    • Control: it must fail on the stock bootstrap bun with the exact message naming all three. A crash in the check therefore can't pass as the expected failure.
    • Real check: it must pass on build/release/bun.
  4. New standalone test emsign_roots_removed runs the same fingerprint check on the installed bun.

plannotator/build.sh runs the shipped ./plannotator as plain bun (BUN_BE_BUN=1) through the same check. It fails if the embedded runtime still has the roots, for example if a target change makes bun fetch a stock runtime again.

Verified, and how

  • Patch: applies to the 1.3.14 source with patch -Np1. The result is byte-identical to the intended header, removes 51 lines and adds none.
  • Fingerprint script, run with local node (a script run, not a package build):
    • stock node 26.9.0 → roots=121 still bundled: emSign ECC Root CA - G3, emSign Root CA - C1, emSign ECC Root CA - C3, exit 1;
    • stock bun 1.3.14 → roots=145 still bundled: …, exit 1, matching the control pattern exactly;
    • the original header's PEMs → exit 1; the patched header's PEMs → roots=142 ok, exit 0.
  • Blast radius: MINIMAL_SCIENCE_MODE=1 mip dump --packages --arch amd64, diffed against main, changes 4 spec hashes: bun, hunk, opencode, plannotator.
  • nickel format leaves bun/build.ncl unchanged, and sh -n passes on both scripts.

Unverified

  • Not built locally (per policy). CI is the first build.
  • bun is build_cost_multiple = 2, and arm64 has a history of slow cold builds.
  • BUN_BE_BUN=1 running a script inside a compiled 1.3.14 executable is read from source (src/cli/cli.zig), not executed.
  • pkgs CI skips standalone tests, so a green check doesn't prove emsign_roots_removed passes.

Not covered here

  • bun has no SPKI blocklist for the 8 keys; the node backport in SECURITY: node, node-lts: backport CVE-2026-111943 eMudhra fixes (nodejs/node#66871, #66877) #946 has one, and upstream bun has none either. A chain through a compromised key still verifies if a caller supplies a CA store containing emSign Root TLS CA - G3 or Sectigo AAA Certificate Services, for example via NODE_EXTRA_CA_CERTS or a custom ca. AAA isn't in bun's bundle. With --use-system-ca, the system store is clean once the ca-certificates fix lands.
  • Prebuilt Bun-runtime binaries (claude-code, varlock) don't consume packages/bun and are unaffected by this PR.

🤖 Generated with Claude Code

bryan-minimal and others added 2 commits October 20, 2026 15:49
… store (CVE-2026-111943)

bun 1.3.14 compiles 145 Mozilla roots into packages/bun-usockets/src/crypto/
root_certs.h and always loads them; --use-system-ca / NODE_USE_SYSTEM_CA only
add the system store on top. So the ca-certificates fix never reaches bun, and
emSign ECC Root CA - G3 (the anchor for the compromised eMudhra intermediates,
Mozilla bug 2083449) stays trusted.

Carry a patch removing exactly the three roots NSS 3.131.1 / curl cacert
2026-10-20 dropped: emSign ECC Root CA - G3, emSign Root CA - C1 and emSign
ECC Root CA - C3 (145 -> 142, nothing else changes). Upstream oven-sh/bun#46159
does the same on main (unreleased) and also drops emSign Root CA - G1, which
NSS still ships.

Fail-closed: the patch is applied by name, the labels must be gone from
root_certs.h, and a SHA-256 check of tls.rootCertificates must flag the stock
bootstrap bun (control) and pass on the built binary. A standalone test repeats
the fingerprint check on the installed bun.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(CVE-2026-111943)

On amd64 plannotator compiled for bun-linux-x64-baseline. That is not our bun's
default target, so `bun build --compile` downloaded @oven/bun-linux-x64-baseline
from npm at build time (unpinned) and embedded upstream's runtime, which still
bundles emSign ECC Root CA - G3. The packages/bun root-store fix would never
have reached the amd64 binary.

Compile for bun-linux-x64, which equals our bun's default target, so bun embeds
itself (as on arm64 already). Baseline buys nothing in minimal: glibc and bun
are built with -march=x86-64-v3. The build now runs the shipped binary as bun
(BUN_BE_BUN=1) and fails if its compiled-in store still has the removed roots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bryan-minimal
bryan-minimal requested a review from a team as a code owner October 20, 2026 22:50
@coderabbitai
coderabbitai Bot commented Oct 20, 2026
Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 4 billable files and costs up to $1.00.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 3 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available. Your 35 included PR review attempts over the past 7 days set your current allowance at 4 reviews per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: gominimal/pkgs/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 75227181-5660-4ddb-9736-6310ca003ebc
📥 Commits

Reviewing files that changed from the base of the PR and between 24009b6 and 2adcf24.

📒 Files selected for processing: 4
  • packages/bun/build.ncl
  • packages/bun/build.sh
  • packages/bun/cve-2026-111943-remove-emsign-roots.patch
  • packages/plannotator/build.sh
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@bryan-minimal
bryan-minimal added this pull request to the merge queue Oct 21, 2026
Merged via the queue into main with commit d36c9c5 Oct 21, 2026
10 checks passed
@bryan-minimal
bryan-minimal deleted the security/bun-emsign-roots branch October 21, 2026 04:58
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

0