psf-emudhra-macos-mitigate(8)
eMudhra intermediate CA compromise, macOS mitigation (v1)
Disclosed 20 Oct 2026: https://emudhra.we-have-a-problem.com/
If this software is helpful, consider a donation to our 501c3 nonprofit:
https://privacysafe.foundation
The private keys of eight eMudhra intermediate CAs can be recovered from their
certificates. macOS blocks them in apps that use Apple's own networking and
security frameworks, but the command line tools that ship with macOS and with
the Xcode Command Line Tools (curl, git, python3, ruby, perl, openssl and pip)
verify certificates against /etc/ssl/cert.pem without checking revocation, and that file still contains the roots the intermediates
chain to. Apple has said it will update the file in a future release. Until
then this script removes those roots from it, the way Apple's support page
(support.apple.com/en-us/149555) and the disclosure page describe, and proves
they are gone.
Homebrew has already shipped a fixed ca-certificates. The script reports its
bundle and the formula version, but never runs brew: brew refuses to run as
root, so update it as your own user (brew update && brew upgrade ca-certificates).
Usage:
./emudhra-macos-mitigate.sh [command] [options]
check (default) [--online] [--cert-file FILE]
read-only; changes nothing, root not needed. Reports
whether emSign ECC Root CA - G3 and AAA Certificate
Services are still in the system bundle and in
Homebrew's, and whether the pip fix is in place.
--online also connects to the disclosure site's test
hosts with curl: a connection that succeeds means the
client still trusts a compromised intermediate.
Exit 0 = both roots absent, 1 = emSign ECC Root CA - G3
present or a test host accepted, 2 = only AAA
Certificate Services remains (your decision, see below)
or the bundle could not be read.
apply [--include-aaa] [--pip] [--dry-run] [--cert-file FILE]
back up the bundle, remove emSign ECC Root CA - G3 from
it, and check the result. --include-aaa also removes
AAA Certificate Services, which blocks the six Sectigo
cross-signed intermediates but may break unaffected
sites that chain to it. --pip makes the built-in pip
use the fixed system file. Needs root.
rollback [--dry-run] [--force]
put the original bundle back and remove the pip line.
This re-exposes you. It refuses when the bundle has
changed since apply (a macOS update) unless --force.
version print the script version and exit
Environment:
CERT_FILE=/etc/ssl/cert.pem the bundle to check and change
STATE_DIR=/var/lib/psf-security-patches/emudhra-macos backups and record
PLATFORM=Darwin override the detected platform
BREW_PREFIX=/opt/homebrew where Homebrew is installed
CURL_BIN=/usr/bin/curl the curl that --online uses
PYTHON_BIN=/usr/bin/python3 the Python whose standard library folder holds the
sitecustomize.py that --pip checks
SITECUSTOMIZE=FILE that sitecustomize.py, instead of asking PYTHON_BIN
LIVE_HOSTS="host ..." replace the disclosure site's test hosts
PSF_NO_ROOT_NOTE=1 hide the note about a root login
Exit codes: 0 done or clear, 1 exposed or a failed change, 2 usage error or
a bundle that could not be read. Every command prints the script version on
its first line.
PSF_LOCK_WAIT seconds a run that changes the host waits for another one that is already
changing it (default 30), then stops with exit 2; the lock is STATE_DIR/.lockThe Security Patches software and this documentation are dedicated to the public domain under CC0 1.0 Universal. Other pages on privacysafe.app/docs are CC BY-SA 4.0 unless the page says otherwise.