Skip to content

fix(security): stop trusting the compromised eMudhra roots (v2.9.2) - #215

Closed
krish221997 wants to merge 1 commit into
mainfrom
fix/emudhra-ca-guard
Closed

krish221997 wants to merge 1 commit into
mainfrom
fix/emudhra-ca-guard

Conversation

@krish221997

Copy link
Copy Markdown
Collaborator

Why

Eight eMudhra intermediate certificate authorities were created with 8-bit private keys. Anyone can compute those keys and sign a valid-looking certificate for any domain, including api.withone.ai. Browsers blocked them on Oct 17. Node.js only blocks them from 22.23.4, 24.22.1 and 26.12.1, released Oct 21. Users run the CLI on whatever Node they have, so most are still exposed.

What

At startup, before any connection opens, bin/cli.js calls applyCaGuard() (src/lib/ca-guard.ts). It removes these roots from the CA list every TLS client in the process uses, global fetch included:

  • emSign ECC Root CA - G3, the root that signed the intermediates. It is matched by key fingerprint, the same root webpki-roots 1.0.10 and Mozilla NSS removed.
  • emSign Root TLS CA - G3 and AAA Certificate Services, the cross-signing roots that Ubuntu removed. They are matched by name. Neither is in Node's bundled list, but they can arrive through the system store.
User's Node Result
22.19+ or 24.5+ Guard applied: protected even before the Node update
Already on 22.23.4 / 24.22.1 / 26.12.1+ Protected by Node; the guard is harmless
Older (18, 20, early 22) Can't change the list, so the CLI prints a one-line stderr warning to update Node

The guard runs only in the binary. Importing @withone/cli as a library leaves the host process's TLS settings alone. It never throws and adds about 10 ms to startup.

Verified

  • Global fetch follows a changed default list when it's set before the first connection. With a root removed, a site that chains to it fails with UNABLE_TO_GET_ISSUER_CERT_LOCALLY, while other sites still work.
  • On Node 24.14 the built binary removes exactly emSign ECC Root CA - G3. The other emSign roots stay, and so do the roots behind our own domains: ISRG for Let's Encrypt and GTS for Google.
  • The new tests in ca-guard.test.ts pass and typecheck is clean.
  • The full suite shows 687 passing and 6 failing. All 6 failing tests are resolveConfig tests, and they fail the same way on untouched main. They pick up this machine's config, and this PR doesn't touch them.

After merge

Publish 2.9.2 to npm. Optionally deprecate older versions with a message pointing to 2.9.2:

npm deprecate @withone/cli@"<2.9.2" "Update to 2.9.2+ (eMudhra CA compromise guard) and Node.js 22.23.4 / 24.22.1 / 26.12.1+"

🤖 Generated with Claude Code

Eight eMudhra/emSign intermediate CAs were issued with 8-bit private keys,
so anyone can sign a certificate for any domain with them. Node.js blocks
them only from 22.23.4 / 24.22.1 / 26.12.1; on any older Node, an attacker
on the network could present a forged certificate for api.withone.ai and
read the user's API key.

At startup, before any connection opens, the CLI now removes from Node's
default CA list the root that signed those intermediates (emSign ECC Root
CA - G3, matched by SPKI, as webpki-roots 1.0.10 and Mozilla NSS did) and
the two cross-signing roots Ubuntu removed (matched by name, in case the
system store is enabled). Global fetch honours the new list.

- Needs tls.setDefaultCACertificates (Node 22.19+ / 24.5+). On an older
  Node without the fix, the CLI prints a one-line stderr warning asking the
  user to update Node.
- Runs only in bin/cli.js, so importing @withone/cli as a library does not
  change the host process's TLS settings.
- Never throws; about 10 ms at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@paulkr

paulkr commented Oct 21, 2026

Copy link
Copy Markdown
Member

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