In October 2026, eight eMudhra intermediate certificate authorities (CAs) were found to have private keys anyone can work out. Use this guide to find the trust stores on your devices that still trust the compromised roots, and to clean them.
Feature/Functionality
Overview
Browsers and operating systems blocked the eight intermediates within a few days. Anything that carries its own copy of the trusted roots did not get that block: command-line tools, runtimes, and agents keep trusting forged certificates until their root file changes.
Factorial IT strips the two compromised roots from the CA bundle of the Factorial IT agent on every enrolled Mac, Windows, and Linux device. New enrolment packages come without them. A device that is offline gets the fix as soon as it reconnects. There is nothing for you to do about the agent.
Every other trust store belongs to the operating system, to your teams' tools, or to your own apps, so the timing is yours. You have two routes:
- A custom agent, Close the eMudhra CA exposure, which asks an admin before every change.
- By hand, with Fleet's detection reports and remediation scripts.
What happened
In April 2025, eMudhra created the eight private keys as integers smaller than 256, when they should have been random 384-bit numbers. Each key can therefore be rebuilt in milliseconds. With a key, anyone can sign a certificate for any hostname. Six of the intermediates issue TLS certificates; the other two sign code.
Every intermediate exists twice: once issued by eMudhra's emSign roots, and once as a cross-sign from Sectigo's AAA Certificate Services root. In total, 16 certificates must be distrusted. Two roots must also come out of any CA file that still holds them: emSign ECC Root CA - G3 and AAA Certificate Services.
| Date (2026) | Event |
|---|---|
| October 17 | A researcher reports the keys. Chrome and Firefox block the 16 certificates. |
| October 18 | eMudhra revokes the eight intermediates. Sectigo's cross-signs are revoked on October 19. |
| October 20 | Microsoft ships the block through Windows' automatic trust list updates. |
| By October 22 | Apple ships the block on macOS, iOS, and iPadOS. |
None of the TLS certificates from these intermediates shows up in Certificate Transparency (CT) logs. Browsers that enforce CT were therefore never open to attack. Most other clients, curl among them, ignore CT. An attacker also has to sit on the network path, for example through a rogue Wi-Fi network, a hijacked router or proxy, or tampered DNS.
What stays exposed
The Apple and Microsoft blocks only protect software that verifies through the OS or checks revocation. Software with its own file of roots keeps accepting forged chains until someone updates that file.
| Trust path | Covered by the vendor blocks | Worst case if left as is | Who fixes it |
|---|---|---|---|
| Browsers, and apps that verify through the OS (Safari, Mail, Windows Schannel, Go on macOS and Windows) | Yes, once the OS update is installed | None | Nothing beyond OS updates |
| The Factorial IT agent's CA bundle | No | An impostor Factorial IT server collects query results and the device's node key, and sends its own queries | Factorial IT |
macOS command-line tools (curl, git, python3, perl, ruby, openssl read /etc/ssl/cert.pem) |
No: Apple did not update this file | Passwords and API tokens read in transit; curl | sh installers swapped for malware |
You |
Linux system bundle (ca-certificates) |
After the package update only. On October 21, Rocky Linux 9 and Fedora 44 stable still shipped the root. | Every OpenSSL and GnuTLS tool on the device can be impersonated: backups, webhooks, monitoring agents, database clients | You |
| Windows devices that missed Microsoft's update (offline, WSUS-only, or blocked by Group Policy) | No | Forged TLS certificates accepted; malware signed with the two code-signing intermediates passes signature checks | You |
| Node.js and Electron | No: official builds embed a root snapshot fixed at release | API keys and service-to-service calls go to the attacker, even on a patched device | You |
certifi and pip installed with pip |
No: each carries its own roots | Same as Node.js | You |
| Go binaries on Linux | No: Go reads the system bundle there | Cloud CLIs, kubectl, and terraform send their credentials to an impostor API |
You |
| Containers, servers, and CI images | No: each image carries its own bundle | Same as the Linux system bundle, in every image built before the fix | You |
Packages you install with apt or dnf are not at risk, because they are GPG-signed.
What Factorial IT fixes for you
The Factorial IT agent brings its own CA bundle: /opt/orbit/certs.pem on macOS and Linux, and C:\Program Files\Orbit\certs.pem on Windows. Its osquery component relies on this file, not on the OS trust store, to talk to your Factorial IT server. The file is written once, when the agent is installed, and never refreshed. That is why the vendor blocks never reach it.
Factorial IT removes both roots from this file on every enrolled device. Do not touch it yourself: the remediation scripts below notice that it is already clean and leave it alone.
On Linux, the agent's updater relies on the system bundle, so updating ca-certificates covers it as well. On macOS and Windows, the updater verifies through the OS, so the Apple and Microsoft blocks already protect it.
How to use
Pick one of the two routes for the trust stores Factorial IT does not handle:
- With a custom agent. It follows the manual route on your behalf and waits for an admin's approval before each change. See Fix with a custom agent below.
- By hand. You run the detection reports and remediation scripts yourself. See Fix manually below.
Whichever route you take, finish by updating the runtimes that carry their own roots, then check the result as described in Verify.
Fix with a custom agent
The Close the eMudhra CA exposure agent template runs every two hours and moves forward in stages. It never runs a remediation script until an admin approves on its ticket.
- Go to AI Agents > Agents, click Browse templates, filter by Equipment, and open Close the eMudhra CA exposure.
- In the instructions, find
PILOT DEVICESand write a device group or device names afterPilot:. If you leave it blank, the agent suggests up to three Macs, three Linux devices, and three Windows devices that still need the fix. - Click Create. The agent starts on its next trigger. To see a run first, open the agent and click Dry run: every action is logged rather than carried out.
- The agent opens a single ticket, eMudhra CA compromise: what is left to fix, and emails the admins once. Reply on that ticket to approve the pilot, and later the rollout.
| Stage | What the agent does | What it needs from you |
|---|---|---|
| Inventory | Adds Factorial IT's eMudhra remediation kit: three reports that every computer runs each hour and, in the Script library, a read-only check script, Fleet's macOS and Linux remediation scripts, Factorial IT's Windows script, and Fleet's Windows script for admins to run by hand. Opens its ticket with the devices grouped by finding. | Nothing. The first results arrive within the hour. |
| Pilot | Runs the remediation script of each platform on the pilot devices and posts each result on the ticket. | An approval on the ticket |
| Rollout | Runs them on every Mac and Linux device with a system finding and every Windows device Microsoft's block has not reached, then checks that their next reports are clean. | An approval on the ticket |
| People | Opens a ticket on behalf of each person whose runtimes only they can update (Node.js, Python certifi, Go binaries), and tells them what to update. |
An approval before it writes to them |
| Done | Closes its ticket when nothing is left to fix, and says the agent can be paused. | Pause the agent |
On top of the stages, the agent:
- Warns the admins straight away, on the ticket and by email, if a device holds one of the compromised intermediates.
- Lists the Macs that cannot reach Apple's update servers, and the Windows devices where a Group Policy stops Microsoft's update.
- Never runs Fleet's Windows script, because Microsoft Defender blocks it. If an antivirus rejects one of its runs, it tells the admins. They can then download the script from the Script library and run it wherever their antivirus allows.
- Leaves the Factorial IT agent's bundle to Factorial IT. It never restores a backup, and never locks, wipes, or restarts a device.
Once the job is done, the kit stays where it is.
Fix manually
Step 1: run the detection reports
Fleet provides one detection report per OS. In the Fleet console, create a report and paste in the SQL for each OS below. You can also manage all three in GitOps with emudhra-ca-compromise.reports.yml, which runs them once a day.
macOS report
SELECT 'root_in_keychain' AS finding, common_name AS item, path AS detail,
CASE sha1
WHEN '3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1' THEN 'ROOT_TRUSTED emSign ECC Root CA - G3 (Apple root store; Apple block via trustd)'
WHEN 'd1eb23a46d17d68fd92564c2f1f1601764d8e349' THEN 'ROOT_TRUSTED AAA Certificate Services (Sectigo cross-sign path)'
ELSE 'ROOT_TRUSTED emSign Root TLS/CS CA - G3 (not in Apple store by default; manually added)'
END AS status
FROM certificates
WHERE sha1 IN ('3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1',
'd1eb23a46d17d68fd92564c2f1f1601764d8e349',
'39d3236e4a609ab6190cbfa1ad02b3cc61dfc2c3',
'1f74b6010a219fd9ca058d07aa053d15efd9300d')
UNION ALL
-- /etc/ssl/cert.pem is used by Apple-shipped curl, git, python3, ruby, perl, openssl. Apple did NOT update it.
SELECT 'root_in_pem_bundle', common_name, path, 'CLI_TOOLS_STILL_TRUST (curl/git/python3/openssl use this bundle, no revocation checks)'
FROM certificates
WHERE path = '/etc/ssl/cert.pem'
AND sha1 IN ('3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1',
'd1eb23a46d17d68fd92564c2f1f1601764d8e349')
UNION ALL
SELECT 'compromised_intermediate_present', common_name, path || ' sha1=' || sha1, 'IOC_HIT investigate'
FROM certificates
WHERE subject_key_id IN ('e21e2fe71c19390e554b62c120245735248a2514', -- EM DV TLS CA - G3B
'9bd86551afe7e72c37ad08ee6919d9bf3784ae0a', -- EM OV TLS CA - G3B
'3edc20d9894698877b873fd88147b25c15d84706', -- EM EV TLS CA - G3B
'ae7e105c3c2996c1a847270559f1e03dd4e50af3', -- EM DV TLS CA - G3C
'34eaea0cebc90adaf9d0c5e2ed9b78c9879e1b6c', -- EM OV TLS CA - G3C
'a6255e80a94db7f3967c1bc791df92bcee4beb5f', -- EM EV TLS CA - G3C
'79fae8eccfdb0d758da458d69fb6eddc7c49dee6', -- EM OV CS CA - G3B
'ee6f73298d6fbfa7b31bc39534f61556447866b7') -- EM OV CS CA - G3C
OR sha1 IN ('a99b289464f3b44343270a7a1e910f02370483fe','1396be1372bc93dfa2a433ca964dba91548a6e57',
'5f014e0db3bb8f950e0598ab280846d383fc57be','5dfa19197d1c1cf629af7c0cd865543c5050227c',
'f33acd1f5d31aa81ac18c72c928446226d3d5914','19069bd6533bff93fb67c71fb3376b4ea7402c3b',
'8ad23bc78845d6faae1d534bac89f0e36ba55a11','4b0944c14f8c2ccba4e4f32104ad920fa15ee331',
'7fcc31aab874ce6c8ee8584f54cfd08646b54117','305fc1e253dcab71b95d4995dae3cb09d562f22a',
'c988f3d0cc1629ffc43165d6464c9b6ef73d6e48','8588708e875a1f8aa83524f3559080f7e17068c7',
'd33195a244046a3263fb5e0e83c6803a5b2e7bc3','ee26d7ae3cc71718f6e07d7d0095076f0c158f63',
'1f4a5d178f8b6a8ef0f6784155bd5fea91158b12','7fde64753a957b98f172fd2ab0d922551909c7ba')
UNION ALL
-- Apple shipped the block on 2026-10-19 through trustd's "Valid" database (not a keychain change).
SELECT 'apple_block_applied', 'trustd valid.sqlite3', datetime(mtime, 'unixepoch') || 'Z',
CASE WHEN mtime >= 1792368000 THEN 'OK_VALID_DB_UPDATED_SINCE_2026-10-19'
ELSE 'STALE_NO_APPLE_BLOCK (offline or MobileAsset blocked)' END
FROM file
WHERE path = '/private/var/protected/trustd/valid.sqlite3'
UNION ALL
SELECT 'homebrew_ca_certificates', name, version,
CASE WHEN version >= '2026-09-25_1' THEN 'FIXED' ELSE 'UPDATE brew upgrade ca-certificates' END
FROM homebrew_packages
WHERE name = 'ca-certificates'
UNION ALL
SELECT 'python_certifi', name || ' uid=' || uid, version || ' ' || path,
CASE WHEN CAST(substr(version, 1, 4) AS INTEGER) * 10000
+ CAST(substr(version, 6, instr(substr(version, 6), '.') - 1) AS INTEGER) * 100
+ CAST(substr(version, 6 + instr(substr(version, 6), '.')) AS INTEGER) >= 20261019
THEN 'FIXED' ELSE 'UPDATE certifi>=2026.10.19' END
FROM python_packages
WHERE name = 'certifi'
UNION ALL
SELECT 'toolchain', name, version, 'REVIEW Go>=1.27.3/1.26.10, Node>=22.23.4/24.22.1/26.12.1 (binaries built earlier stay vulnerable)'
FROM homebrew_packages
WHERE name IN ('go', 'node')
UNION ALL
-- fleetd ships its own CA bundle. osqueryd uses it (not the OS trust store) for TLS to Fleet.
-- It is written at install time and never refreshed, so the OS vendor block does not cover it.
SELECT 'fleetd_osquery_ca_bundle', common_name, path,
'EXPOSED osqueryd-to-Fleet TLS trusts this root; run the remediation script'
FROM certificates
WHERE path = '/opt/orbit/certs.pem'
AND sha1 IN ('3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1', 'd1eb23a46d17d68fd92564c2f1f1601764d8e349')
UNION ALL
SELECT 'fleetd_agent', 'orbit ' || version, 'channel=' || orbit_channel || ' scripts_enabled=' || scripts_enabled,
'INFO orbit is Go; on macOS it verifies TLS through the OS, so the Apple block applies'
FROM orbit_info
UNION ALL
-- Go tools installed with go install (each user's ~/go/bin).
SELECT 'go_binary', name || ' ' || version, installed_path || ' ' || go_version,
CASE WHEN CAST(substr(go_version, 5) AS INTEGER) >= 28
OR (CAST(substr(go_version, 5) AS INTEGER) = 27 AND CAST(substr(go_version, 8) AS INTEGER) >= 3)
OR (CAST(substr(go_version, 5) AS INTEGER) = 26 AND CAST(substr(go_version, 8) AS INTEGER) >= 10)
THEN 'FIXED_GO'
ELSE 'REBUILD with Go>=1.27.3/1.26.10' END
FROM go_binaries
UNION ALL
SELECT 'host', name, version || ' (' || build || ')', 'INFO'
FROM os_version;Linux report
SELECT 'root_in_ca_bundle' AS finding, common_name AS item, path AS detail,
CASE sha1
WHEN '3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1' THEN 'UNPATCHED emSign ECC Root CA - G3 still trusted (fixed ca-certificates removes it)'
WHEN 'd1eb23a46d17d68fd92564c2f1f1601764d8e349' THEN 'AAA Certificate Services trusted: Sectigo cross-sign path open (Mozilla-derived bundles no longer ship it; old bundle or manually added)'
ELSE 'emSign Root TLS/CS CA - G3 present (not in Mozilla bundle; manually added)'
END AS status
FROM certificates
WHERE sha1 IN ('3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1',
'd1eb23a46d17d68fd92564c2f1f1601764d8e349',
'39d3236e4a609ab6190cbfa1ad02b3cc61dfc2c3',
'1f74b6010a219fd9ca058d07aa053d15efd9300d')
UNION ALL
SELECT 'compromised_intermediate_present', common_name, path || ' sha1=' || sha1, 'IOC_HIT investigate'
FROM certificates
WHERE subject_key_id IN ('e21e2fe71c19390e554b62c120245735248a2514', -- EM DV TLS CA - G3B
'9bd86551afe7e72c37ad08ee6919d9bf3784ae0a', -- EM OV TLS CA - G3B
'3edc20d9894698877b873fd88147b25c15d84706', -- EM EV TLS CA - G3B
'ae7e105c3c2996c1a847270559f1e03dd4e50af3', -- EM DV TLS CA - G3C
'34eaea0cebc90adaf9d0c5e2ed9b78c9879e1b6c', -- EM OV TLS CA - G3C
'a6255e80a94db7f3967c1bc791df92bcee4beb5f', -- EM EV TLS CA - G3C
'79fae8eccfdb0d758da458d69fb6eddc7c49dee6', -- EM OV CS CA - G3B
'ee6f73298d6fbfa7b31bc39534f61556447866b7') -- EM OV CS CA - G3C
OR sha1 IN ('a99b289464f3b44343270a7a1e910f02370483fe','1396be1372bc93dfa2a433ca964dba91548a6e57',
'5f014e0db3bb8f950e0598ab280846d383fc57be','5dfa19197d1c1cf629af7c0cd865543c5050227c',
'f33acd1f5d31aa81ac18c72c928446226d3d5914','19069bd6533bff93fb67c71fb3376b4ea7402c3b',
'8ad23bc78845d6faae1d534bac89f0e36ba55a11','4b0944c14f8c2ccba4e4f32104ad920fa15ee331',
'7fcc31aab874ce6c8ee8584f54cfd08646b54117','305fc1e253dcab71b95d4995dae3cb09d562f22a',
'c988f3d0cc1629ffc43165d6464c9b6ef73d6e48','8588708e875a1f8aa83524f3559080f7e17068c7',
'd33195a244046a3263fb5e0e83c6803a5b2e7bc3','ee26d7ae3cc71718f6e07d7d0095076f0c158f63',
'1f4a5d178f8b6a8ef0f6784155bd5fea91158b12','7fde64753a957b98f172fd2ab0d922551909c7ba')
UNION ALL
-- Fixed: Ubuntu 20260816ubuntu0.{22.04,24.04,26.04,26.10}.1, Debian 20250419~deb12u2 / 20250419+deb13u1, Fedora 2026.2.92_v9.0.319, Alpine 20261020-r0
SELECT 'ca_certificates_pkg', name, version || ' status=' || status, 'COMPARE to fixed version for this distro'
FROM deb_packages
WHERE name = 'ca-certificates'
UNION ALL
SELECT 'ca_certificates_pkg', name, version || '-' || release, 'COMPARE to fixed version for this distro'
FROM rpm_packages
WHERE name = 'ca-certificates'
UNION ALL
SELECT 'python_certifi', name || ' uid=' || uid, version || ' ' || path,
-- Distro-packaged certifi points at the system bundle (Debian/Ubuntu dist-packages, Fedora/RHEL site-packages).
CASE WHEN path LIKE '/usr/lib/python3/dist-packages%'
OR path LIKE '/usr/lib/python3%/site-packages%'
OR path LIKE '/usr/lib64/python3%/site-packages%'
THEN 'OK_DISTRO_CERTIFI (patched to use system bundle; see ca_certificates_pkg)'
WHEN CAST(substr(version, 1, 4) AS INTEGER) * 10000
+ CAST(substr(version, 6, instr(substr(version, 6), '.') - 1) AS INTEGER) * 100
+ CAST(substr(version, 6 + instr(substr(version, 6), '.')) AS INTEGER) >= 20261019
THEN 'FIXED' ELSE 'UPDATE certifi>=2026.10.19' END
FROM python_packages
WHERE name = 'certifi'
UNION ALL
SELECT 'toolchain', name, version, 'REVIEW Go>=1.27.3/1.26.10, Node>=22.23.4/24.22.1/26.12.1 (binaries built earlier stay vulnerable)'
FROM deb_packages
WHERE name IN ('golang-go', 'nodejs')
UNION ALL
SELECT 'toolchain', name, version || '-' || release, 'REVIEW Go>=1.27.3/1.26.10, Node>=22.23.4/24.22.1/26.12.1'
FROM rpm_packages
WHERE name IN ('golang', 'nodejs')
UNION ALL
-- fleetd ships its own CA bundle. osqueryd uses it (not the OS trust store) for TLS to Fleet.
-- It is written at install time and never refreshed, so the OS vendor block does not cover it.
SELECT 'fleetd_osquery_ca_bundle', common_name, path,
'EXPOSED osqueryd-to-Fleet TLS trusts this root; run the remediation script'
FROM certificates
WHERE path = '/opt/orbit/certs.pem'
AND sha1 IN ('3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1', 'd1eb23a46d17d68fd92564c2f1f1601764d8e349')
UNION ALL
SELECT 'fleetd_agent', 'orbit ' || version, 'channel=' || orbit_channel || ' scripts_enabled=' || scripts_enabled,
'INFO orbit is Go; on Linux it reads the system bundle, so it is safe only once root_in_ca_bundle rows are gone'
FROM orbit_info
UNION ALL
-- Go tools installed with go install (each user's ~/go/bin).
SELECT 'go_binary', name || ' ' || version, installed_path || ' ' || go_version,
CASE WHEN CAST(substr(go_version, 5) AS INTEGER) >= 28
OR (CAST(substr(go_version, 5) AS INTEGER) = 27 AND CAST(substr(go_version, 8) AS INTEGER) >= 3)
OR (CAST(substr(go_version, 5) AS INTEGER) = 26 AND CAST(substr(go_version, 8) AS INTEGER) >= 10)
THEN 'FIXED_GO'
ELSE 'REBUILD with Go>=1.27.3/1.26.10' END
FROM go_binaries
UNION ALL
SELECT 'host', name, version || ' ' || platform_like, 'INFO'
FROM os_version;Windows report
SELECT 'root_in_cert_store' AS finding, common_name AS item,
store_location || '\' || store || ' user=' || username AS detail,
CASE sha1
WHEN '3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1' THEN 'ROOT_TRUSTED emSign ECC Root CA - G3'
WHEN 'd1eb23a46d17d68fd92564c2f1f1601764d8e349' THEN 'ROOT_TRUSTED AAA Certificate Services (Sectigo cross-sign path)'
WHEN '39d3236e4a609ab6190cbfa1ad02b3cc61dfc2c3' THEN 'ROOT_TRUSTED emSign Root TLS CA - G3 (Microsoft root store)'
ELSE 'ROOT_TRUSTED emSign Root CS CA - G3 (Microsoft root store, code signing)'
END AS status
FROM certificates
WHERE sha1 IN ('3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1',
'd1eb23a46d17d68fd92564c2f1f1601764d8e349',
'39d3236e4a609ab6190cbfa1ad02b3cc61dfc2c3',
'1f74b6010a219fd9ca058d07aa053d15efd9300d')
UNION ALL
SELECT 'compromised_intermediate_present', common_name,
store_location || '\' || store || ' user=' || username || ' sha1=' || sha1, 'IOC_HIT investigate (cached intermediate or code-signing chain seen)'
FROM certificates
WHERE store <> 'Untrusted Certificates'
AND (subject_key_id IN ('e21e2fe71c19390e554b62c120245735248a2514', -- EM DV TLS CA - G3B
'9bd86551afe7e72c37ad08ee6919d9bf3784ae0a', -- EM OV TLS CA - G3B
'3edc20d9894698877b873fd88147b25c15d84706', -- EM EV TLS CA - G3B
'ae7e105c3c2996c1a847270559f1e03dd4e50af3', -- EM DV TLS CA - G3C
'34eaea0cebc90adaf9d0c5e2ed9b78c9879e1b6c', -- EM OV TLS CA - G3C
'a6255e80a94db7f3967c1bc791df92bcee4beb5f', -- EM EV TLS CA - G3C
'79fae8eccfdb0d758da458d69fb6eddc7c49dee6', -- EM OV CS CA - G3B
'ee6f73298d6fbfa7b31bc39534f61556447866b7') -- EM OV CS CA - G3C
OR sha1 IN ('a99b289464f3b44343270a7a1e910f02370483fe','1396be1372bc93dfa2a433ca964dba91548a6e57',
'5f014e0db3bb8f950e0598ab280846d383fc57be','5dfa19197d1c1cf629af7c0cd865543c5050227c',
'f33acd1f5d31aa81ac18c72c928446226d3d5914','19069bd6533bff93fb67c71fb3376b4ea7402c3b',
'8ad23bc78845d6faae1d534bac89f0e36ba55a11','4b0944c14f8c2ccba4e4f32104ad920fa15ee331',
'7fcc31aab874ce6c8ee8584f54cfd08646b54117','305fc1e253dcab71b95d4995dae3cb09d562f22a',
'c988f3d0cc1629ffc43165d6464c9b6ef73d6e48','8588708e875a1f8aa83524f3559080f7e17068c7',
'd33195a244046a3263fb5e0e83c6803a5b2e7bc3','ee26d7ae3cc71718f6e07d7d0095076f0c158f63',
'1f4a5d178f8b6a8ef0f6784155bd5fea91158b12','7fde64753a957b98f172fd2ab0d922551909c7ba'))
UNION ALL
-- Explicit block added by remediate-emudhra-ca-compromise-windows.ps1. "Untrusted Certificates" is the English
-- display name of the Disallowed store. Microsoft's own CTL block does not show up here.
SELECT 'disallowed_store_block', COUNT(DISTINCT sha1) || ' of 16 compromised intermediates',
'LocalMachine\Untrusted Certificates',
CASE WHEN COUNT(DISTINCT sha1) = 16 THEN 'OK_EXPLICITLY_DISTRUSTED'
ELSE 'NOT_PRESENT run the remediation script for an explicit block' END
FROM certificates
WHERE store_location = 'LocalMachine'
AND store = 'Untrusted Certificates'
AND sha1 IN ('a99b289464f3b44343270a7a1e910f02370483fe','1396be1372bc93dfa2a433ca964dba91548a6e57',
'5f014e0db3bb8f950e0598ab280846d383fc57be','5dfa19197d1c1cf629af7c0cd865543c5050227c',
'f33acd1f5d31aa81ac18c72c928446226d3d5914','19069bd6533bff93fb67c71fb3376b4ea7402c3b',
'8ad23bc78845d6faae1d534bac89f0e36ba55a11','4b0944c14f8c2ccba4e4f32104ad920fa15ee331',
'7fcc31aab874ce6c8ee8584f54cfd08646b54117','305fc1e253dcab71b95d4995dae3cb09d562f22a',
'c988f3d0cc1629ffc43165d6464c9b6ef73d6e48','8588708e875a1f8aa83524f3559080f7e17068c7',
'd33195a244046a3263fb5e0e83c6803a5b2e7bc3','ee26d7ae3cc71718f6e07d7d0095076f0c158f63',
'1f4a5d178f8b6a8ef0f6784155bd5fea91158b12','7fde64753a957b98f172fd2ab0d922551909c7ba')
UNION ALL
-- Microsoft's 2026-10-20 block ships through the Disallowed CTL auto-update (CTL Updater), not Windows Update.
SELECT 'ctl_autoupdate_policy', name, 'data=' || data,
CASE WHEN name = 'DisableRootAutoUpdate' AND data = '1' THEN 'BLOCK_WILL_NOT_ARRIVE auto root update disabled by policy'
WHEN name = 'EnableDisallowedCertAutoUpdate' AND data = '0' THEN 'BLOCK_WILL_NOT_ARRIVE disallowed CTL update disabled by policy'
ELSE 'OK' END
FROM registry
WHERE key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\AuthRoot'
AND name IN ('DisableRootAutoUpdate', 'EnableDisallowedCertAutoUpdate')
UNION ALL
SELECT 'disallowed_ctl_sync', name, 'key_mtime=' || datetime(mtime, 'unixepoch') || 'Z',
CASE WHEN mtime >= 1792454400 THEN 'OK_AUTOUPDATE_KEY_WRITTEN_SINCE_2026-10-20'
ELSE 'NOT_SYNCED_SINCE_BLOCK (offline, WSUS-only, or policy)' END
FROM registry
WHERE key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\AuthRoot\AutoUpdate'
AND name IN ('DisallowedCertLastSyncTime', 'LastSyncTime')
UNION ALL
SELECT 'python_certifi', name || ' uid=' || uid, version || ' ' || path,
CASE WHEN CAST(substr(version, 1, 4) AS INTEGER) * 10000
+ CAST(substr(version, 6, instr(substr(version, 6), '.') - 1) AS INTEGER) * 100
+ CAST(substr(version, 6 + instr(substr(version, 6), '.')) AS INTEGER) >= 20261019
THEN 'FIXED' ELSE 'UPDATE certifi>=2026.10.19' END
FROM python_packages
WHERE name = 'certifi'
UNION ALL
SELECT 'toolchain', name, version, 'REVIEW Go>=1.27.3/1.26.10, Node>=22.23.4/24.22.1/26.12.1 (binaries built earlier stay vulnerable)'
FROM programs
WHERE name LIKE 'Go Programming Language%' OR name = 'Node.js'
UNION ALL
-- fleetd ships its own CA bundle. osqueryd uses it (not the Windows store) for TLS to Fleet.
-- Windows osquery cannot parse PEM files, so match one unique line from each root
-- (fleetd bundles wrap at 76 columns). The join skips hosts without the file.
SELECT 'fleetd_osquery_ca_bundle',
CASE WHEN l.line LIKE 'CBeQyh%' THEN 'emSign ECC Root CA - G3' ELSE 'AAA Certificate Services' END,
l.path, 'EXPOSED osqueryd-to-Fleet TLS trusts this root; run the remediation script'
FROM file f
JOIN file_lines l ON l.path = f.path
WHERE f.path = 'C:\Program Files\Orbit\certs.pem'
AND l.line IN ('CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7',
'8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C')
UNION ALL
SELECT 'fleetd_agent', 'orbit ' || version, 'channel=' || orbit_channel || ' scripts_enabled=' || scripts_enabled,
'INFO orbit is Go; on Windows it verifies TLS through the OS, so the Microsoft block applies'
FROM orbit_info
UNION ALL
-- Go tools installed with go install (each user's go\bin).
SELECT 'go_binary', name || ' ' || version, installed_path || ' ' || go_version,
CASE WHEN CAST(substr(go_version, 5) AS INTEGER) >= 28
OR (CAST(substr(go_version, 5) AS INTEGER) = 27 AND CAST(substr(go_version, 8) AS INTEGER) >= 3)
OR (CAST(substr(go_version, 5) AS INTEGER) = 26 AND CAST(substr(go_version, 8) AS INTEGER) >= 10)
THEN 'FIXED_GO'
ELSE 'REBUILD with Go>=1.27.3/1.26.10' END
FROM go_binaries
UNION ALL
SELECT 'host', name, version || ' build ' || build, 'INFO'
FROM os_version;Every report returns four columns (finding, item, detail, and status) and one row per signal. If a finding has no row, it was not found.
| Finding | What it means |
|---|---|
compromised_intermediate_present |
One of the 16 revoked certificates sits in a local store. Investigate who put it there. |
root_in_ca_bundle / root_in_pem_bundle
|
A system CA file still trusts a compromised root. Command-line tools are exposed. |
fleetd_osquery_ca_bundle |
The Factorial IT agent's bundle still trusts a compromised root. Factorial IT cleans it. |
apple_block_applied / disallowed_ctl_sync
|
Whether the Apple or Microsoft block landed. STALE or NOT_SYNCED means it did not. |
disallowed_store_block |
Windows only: how many of the 16 certificates the remediation script has explicitly distrusted. |
ctl_autoupdate_policy |
Group Policy blocks Microsoft's automatic distrust updates. |
ca_certificates_pkg, homebrew_ca_certificates, python_certifi, toolchain
|
Package and runtime versions, to compare with the fixed versions in Update the runtimes that carry their own roots. |
go_binary |
A Go tool in a user's go/bin built with Go older than 1.27.3 or 1.26.10. |
root_in_keychain / root_in_cert_store, fleetd_agent, host
|
Context only. Apple and Microsoft ship both roots on every device and block the intermediates instead, so these rows stay after the fix. |
If a fleetd_osquery_ca_bundle row is still there on a device that has been online for a whole day, we need to check it. Email support@factorial.it with the device name.
Step 2: run the remediation scripts
Fleet keeps one remediation script for each OS:
| Script | What it changes |
|---|---|
| remediate-emudhra-ca-compromise-macos.sh | Removes both roots from /etc/ssl/cert.pem, and upgrades the Homebrew ca-certificates and certifi formulae as the Homebrew owner. |
| remediate-emudhra-ca-compromise-linux.sh | Upgrades ca-certificates (apt, dnf, yum, zypper, apk). Where the distribution has no fix yet, distrusts the roots natively: a ! entry in /etc/ca-certificates.conf on Debian, Ubuntu, and Alpine, a p11-kit blocklist file on Fedora, RHEL, Arch, and openSUSE. |
| remediate-emudhra-ca-compromise-windows.ps1 | Adds the 16 intermediates to the machine's Untrusted Certificates store, for devices Microsoft's update cannot reach. Microsoft Defender blocks it: use the Factorial IT script for Windows below instead. |
The scripts leave the Factorial IT agent's bundle alone once Factorial IT has cleaned it. You can run them more than once without harm. Before editing a file, they save a *.pre-emudhra backup of it. Exit codes: 0 means the device is protected, 2 means something needs a look, and 1 means an error. When a run shows as Failed, its output explains why.
- Download each script from GitHub with Download raw file. Then, in the Factorial IT console, go to Device Management > Scripts > Library and drop it into Add a script. The name and platform are filled in for you.
- Click Run script > Target by device group, choose a small group such as the IT team's devices, and read the output in Run History.
- Run each script again, this time with Target all devices.
- Check one device against the public test hosts and run the reports again, as described in Verify.
If Microsoft Defender is on. Defender stops Fleet's Windows script before it starts, whether you launch it with Run script or by hand. The script contains the compromised certificates, so the run fails with ScriptContainedMaliciousContent. On those devices you have two options: run the Factorial IT script for Windows below, or let Microsoft's automatic trust update fix the device. For the second, bring the device online and lift the Group Policy named in the ctl_autoupdate_policy row of the Windows report.
Factorial IT script for Windows
distrust-emudhra-intermediates-windows.ps1 fetches each of the 16 intermediates from crt.sh by its SHA-256, checks the bytes, and adds it to Untrusted Certificates. Because it contains no certificate, Microsoft Defender lets it run. The device must be able to reach crt.sh. Exit codes: 0 when all 16 are distrusted, 2 when some could not be downloaded (the WARN lines give the reason), and 1 on error. Save it under that file name and add it to the Script library alongside the others.
# distrust-emudhra-intermediates-windows.ps1
# Adds the 16 compromised eMudhra intermediates (October 2026) to this machine's Untrusted Certificates store,
# the block Microsoft ships through its automatic trust update, for a device that did not receive it.
# Embeds no certificate: each one is downloaded from crt.sh by SHA-256 and added only when its bytes match.
# Exit: 0 = all 16 are distrusted, 2 = some could not be downloaded or added (read the WARN lines), 1 = error.
$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$Sha256 = @(
'42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493' # EM DV TLS CA - G3B (via emSign),
'45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE' # EM DV TLS CA - G3B (via Sectigo AAA),
'98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B' # EM OV TLS CA - G3B (via emSign),
'5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699' # EM OV TLS CA - G3B (via Sectigo AAA),
'E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838' # EM EV TLS CA - G3B (via emSign),
'579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512' # EM EV TLS CA - G3B (via Sectigo AAA),
'0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19' # EM DV TLS CA - G3C (via emSign),
'62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72' # EM DV TLS CA - G3C (via Sectigo AAA),
'CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F' # EM OV TLS CA - G3C (via emSign),
'7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489' # EM OV TLS CA - G3C (via Sectigo AAA),
'432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3' # EM EV TLS CA - G3C (via emSign),
'FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F' # EM EV TLS CA - G3C (via Sectigo AAA),
'9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4' # EM OV CS CA - G3B (via emSign),
'A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8' # EM OV CS CA - G3B (via Sectigo AAA),
'4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1' # EM OV CS CA - G3C (via emSign),
'84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD' # EM OV CS CA - G3C (via Sectigo AAA)
)
function Get-Sha256Hex([byte[]]$Bytes) {
$sha = [Security.Cryptography.SHA256]::Create()
try { return -join ($sha.ComputeHash($Bytes) | ForEach-Object { $_.ToString('X2') }) } finally { $sha.Dispose() }
}
function Get-Certificate([string]$Fingerprint) {
$client = New-Object Net.WebClient
for ($attempt = 1; $attempt -le 5; $attempt++) {
try {
$pem = $client.DownloadString("https://crt.sh/?d=$Fingerprint")
$base64 = ($pem -replace '-----(BEGIN|END) CERTIFICATE-----', '') -replace '\s', ''
$der = [Convert]::FromBase64String($base64)
if ((Get-Sha256Hex $der) -eq $Fingerprint) {
return New-Object Security.Cryptography.X509Certificates.X509Certificate2 (, $der)
}
[Console]::WriteLine("WARN crt.sh answered other bytes for $Fingerprint (attempt $attempt)")
} catch {
[Console]::WriteLine("WARN crt.sh: $($_.Exception.Message) for $Fingerprint (attempt $attempt)")
}
Start-Sleep -Seconds (3 * $attempt)
}
return $null
}
$store = New-Object Security.Cryptography.X509Certificates.X509Store('Disallowed', 'LocalMachine')
$store.Open('ReadWrite')
try {
$present = @{}
foreach ($cert in $store.Certificates) { $present[(Get-Sha256Hex $cert.RawData)] = $true }
$added = 0
foreach ($fingerprint in $Sha256) {
if ($present.ContainsKey($fingerprint)) { Write-Output "PRESENT $fingerprint"; continue }
$cert = Get-Certificate $fingerprint
if ($null -eq $cert) { continue }
$store.Add($cert)
$present[$fingerprint] = $true
$added++
Write-Output "ADDED $fingerprint $($cert.Subject)"
}
$missing = @($Sha256 | Where-Object { -not $present.ContainsKey($_) }).Count
Write-Output "VERIFY $($Sha256.Count - $missing) of $($Sha256.Count) compromised intermediates in Untrusted Certificates ($added added)"
if ($missing -gt 0) { exit 2 }
exit 0
} finally {
$store.Close()
}
Good to know after running the scripts
- Restart long-running services on Linux servers. Most tools only read the CA bundle when they start.
-
Run the macOS report again after every macOS update. An update can put
/etc/ssl/cert.pemback as it was. -
Watch for Homebrew being skipped. If
brewcannot run as the Homebrew owner, for instance because the Xcode licence has not been accepted, Fleet's macOS script skips Homebrew silently and still exits with0. The macOS report keeps showing itshomebrew_ca_certificatesrow. Runsudo xcodebuild -license accepton that Mac, then run the script again. The copy that the custom agent puts in the Script library prints a warning and exits with2instead.
Roll back a change
- On macOS and Linux, copy each
*.pre-emudhrabackup over the file it was taken from. On Linux, also remove theemudhra-*.pemfiles added to the p11-kit blocklist folder, put back/etc/ca-certificates.conf.pre-emudhraif it exists, and runupdate-ca-certificatesorupdate-ca-trust. - On Windows, open
certlm.mscand remove the 16 added entries from Untrusted Certificates.
Never restore the Factorial IT agent's bundle. Doing so would make it trust the compromised roots again.
Automate the fix with Fleet policies
In the Fleet console, create one policy per OS and set its Run script automation to the matching remediation script. Devices that fail the policy then fix themselves. Fleet keeps all three policies in GitOps in emudhra-ca-compromise.policies.yml. If a device keeps failing, the script refused to change the agent's bundle, and the script output gives the reason.
macOS policy. It passes when /etc/ssl/cert.pem and the agent's bundle no longer hold either root:
SELECT 1 FROM os_version WHERE NOT EXISTS (
SELECT 1 FROM certificates
WHERE path IN ('/etc/ssl/cert.pem', '/opt/orbit/certs.pem')
AND sha1 IN ('3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1', 'd1eb23a46d17d68fd92564c2f1f1601764d8e349'));Linux policy. It passes when no certificate store and not the agent's bundle holds either root:
SELECT 1 FROM os_version WHERE NOT EXISTS (
SELECT 1 FROM certificates
WHERE sha1 IN ('3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1', 'd1eb23a46d17d68fd92564c2f1f1601764d8e349'))
AND NOT EXISTS (
SELECT 1 FROM certificates
WHERE path = '/opt/orbit/certs.pem'
AND sha1 IN ('3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1', 'd1eb23a46d17d68fd92564c2f1f1601764d8e349'));Windows policy. It passes when the agent's bundle holds neither root. Windows osquery cannot read PEM files, so the query looks for one line that only appears in each root:
SELECT 1 FROM os_version WHERE NOT EXISTS (
SELECT 1 FROM file f JOIN file_lines l ON l.path = f.path
WHERE f.path = 'C:\Program Files\Orbit\certs.pem'
AND l.line IN ('CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7',
'8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C'));
Update the runtimes that carry their own roots
These tools ship with their own copy of the roots, so only a new release fixes them. Update them on workstations, servers, and container images. Then rebuild your images and any binaries you compile yourselves.
| Software | Fixed in |
|---|---|
| Node.js | 22.23.4, 24.22.1, 26.12.1 |
| Electron (and the desktop apps built on it) | 42.11.16, 43.8.1, 44.9.1 |
| Go (rebuild every binary built with an older toolchain) | 1.26.10, 1.27.3 |
| Java | 8u511, 11.0.33.0.2, 17.0.21.0.2, 21.0.13.0.2, 25.0.5.0.2, 27.0.1.0.2 |
| Python | 3.13.17, 3.14.9, 3.15.1 |
Python certifi
|
2026.10.19 |
pip |
26.2.2 |
uv |
0.13.4 |
| Conda | 26.9.4 |
| Bun | 1.4.4 |
| Deno | 2.9.8 |
Rust webpki-roots / webpki-root-certs
|
1.0.10 |
Rust rustls-webpki
|
0.103.16 |
| Docker Desktop | 4.96.2 |
Homebrew ca-certificates
|
2026-09-25_1 |
curl CA bundle (cacert.pem) |
2026-10-20 |
On Linux, the fix comes in the ca-certificates package:
| Distribution | Fixed ca-certificates version |
|---|---|
| Ubuntu 22.04, 24.04, 26.04, 26.10 |
20260816ubuntu0.22.04.1, 20260816ubuntu0.24.04.1, 20260816ubuntu0.26.04.1, 20260816ubuntu0.26.10.1
|
| Debian 12, Debian 13 |
20250419~deb12u2, 20250419+deb13u1
|
| Fedora | 2026.2.92_v9.0.319 |
| Alpine | 20261020-r0 |
Arch Linux (ca-certificates-mozilla) |
3.131.1-1 |
certifi installed from the distribution, such as python3-certifi on Debian and Ubuntu, uses the system bundle. For the up-to-date list of patched software, see the disclosure site.
Verify
Two public test hosts present certificates from a compromised intermediate: one chains to the emSign root, the other to the Sectigo cross-sign. A protected client rejects both.
curl -sS -o /dev/null -w '%{http_code}\n' https://dv-g3b-emudhra.we-have-a-problem.com/
curl -sS -o /dev/null -w '%{http_code}\n' https://dv-g3b-aaa-emudhra.we-have-a-problem.com/Invoke-WebRequest -UseBasicParsing https://dv-g3b-emudhra.we-have-a-problem.com/If you get a certificate error, the client is protected. If you get 200, it still trusts the forged chain. Afterwards, read the reports again, or the agent's ticket: the system bundle findings and the Factorial IT agent's bundle finding should have gone.
Reference: compromised certificates
The two roots to take out of CA bundles:
| Root | SHA-1 | Included in |
|---|---|---|
emSign ECC Root CA - G3 |
3043fa4ff257dca0c380ee2e58ea78b23fe6bbc1 |
Apple, Chrome, Microsoft, Mozilla |
AAA Certificate Services (Sectigo) |
d1eb23a46d17d68fd92564c2f1f1601764d8e349 |
Apple, Microsoft (removed from Chrome and Mozilla) |
Two further roots issue the intermediates directly. Only Microsoft's store contains them, and Microsoft blocks the intermediates themselves:
| Root | SHA-1 |
|---|---|
emSign Root TLS CA - G3 |
39d3236e4a609ab6190cbfa1ad02b3cc61dfc2c3 |
emSign Root CS CA - G3 |
1f74b6010a219fd9ca058d07aa053d15efd9300d |
The 16 compromised intermediates:
| Intermediate | Use | Issued by | SHA-1 |
|---|---|---|---|
| EM DV TLS CA - G3B | TLS | emSign Root TLS CA - G3 | a99b289464f3b44343270a7a1e910f02370483fe |
| EM DV TLS CA - G3B | TLS | AAA Certificate Services | 1396be1372bc93dfa2a433ca964dba91548a6e57 |
| EM OV TLS CA - G3B | TLS | emSign Root TLS CA - G3 | 5f014e0db3bb8f950e0598ab280846d383fc57be |
| EM OV TLS CA - G3B | TLS | AAA Certificate Services | 5dfa19197d1c1cf629af7c0cd865543c5050227c |
| EM EV TLS CA - G3B | TLS | emSign Root TLS CA - G3 | f33acd1f5d31aa81ac18c72c928446226d3d5914 |
| EM EV TLS CA - G3B | TLS | AAA Certificate Services | 19069bd6533bff93fb67c71fb3376b4ea7402c3b |
| EM DV TLS CA - G3C | TLS | emSign Root TLS CA - G3 | 8ad23bc78845d6faae1d534bac89f0e36ba55a11 |
| EM DV TLS CA - G3C | TLS | AAA Certificate Services | 4b0944c14f8c2ccba4e4f32104ad920fa15ee331 |
| EM OV TLS CA - G3C | TLS | emSign Root TLS CA - G3 | 7fcc31aab874ce6c8ee8584f54cfd08646b54117 |
| EM OV TLS CA - G3C | TLS | AAA Certificate Services | 305fc1e253dcab71b95d4995dae3cb09d562f22a |
| EM EV TLS CA - G3C | TLS | emSign Root TLS CA - G3 | c988f3d0cc1629ffc43165d6464c9b6ef73d6e48 |
| EM EV TLS CA - G3C | TLS | AAA Certificate Services | 8588708e875a1f8aa83524f3559080f7e17068c7 |
| EM OV CS CA - G3B | Code signing | emSign Root CS CA - G3 | d33195a244046a3263fb5e0e83c6803a5b2e7bc3 |
| EM OV CS CA - G3B | Code signing | AAA Certificate Services | ee26d7ae3cc71718f6e07d7d0095076f0c158f63 |
| EM OV CS CA - G3C | Code signing | emSign Root CS CA - G3 | 1f4a5d178f8b6a8ef0f6784155bd5fea91158b12 |
| EM OV CS CA - G3C | Code signing | AAA Certificate Services | 7fde64753a957b98f172fd2ab0d922551909c7ba |
SHA-256 fingerprints, serial numbers, and key identifiers. The two certificates of one intermediate use the same key, so they also share its subject key identifier (SKI).
EM DV TLS CA - G3B (via emSign)
SHA-256 42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493
Serial A4AEE678F23362B4C0E460B772F214
SKI E21E2FE71C19390E554B62C120245735248A2514
EM DV TLS CA - G3B (via Sectigo AAA)
SHA-256 45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE
Serial A024FDF4BA665C785CE4A020483858B7
SKI E21E2FE71C19390E554B62C120245735248A2514
EM OV TLS CA - G3B (via emSign)
SHA-256 98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B
Serial 6CADA367FB1C100088120EA202D568
SKI 9BD86551AFE7E72C37AD08EE6919D9BF3784AE0A
EM OV TLS CA - G3B (via Sectigo AAA)
SHA-256 5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699
Serial E94223BDE92F914E5AB9ACABFDAC3710
SKI 9BD86551AFE7E72C37AD08EE6919D9BF3784AE0A
EM EV TLS CA - G3B (via emSign)
SHA-256 E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838
Serial C7C41EFE92D825F05EAE841A4A82C1
SKI 3EDC20D9894698877B873FD88147B25C15D84706
EM EV TLS CA - G3B (via Sectigo AAA)
SHA-256 579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512
Serial D8F8685E4F2BD9D82402A85A1C1EB731
SKI 3EDC20D9894698877B873FD88147B25C15D84706
EM DV TLS CA - G3C (via emSign)
SHA-256 0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19
Serial 588B2E3C2BB8CDA13E1A1209254321
SKI AE7E105C3C2996C1A847270559F1E03DD4E50AF3
EM DV TLS CA - G3C (via Sectigo AAA)
SHA-256 62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72
Serial 91F347F1A5B7D39962B95E0EE4E299D1
SKI AE7E105C3C2996C1A847270559F1E03DD4E50AF3
EM OV TLS CA - G3C (via emSign)
SHA-256 CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F
Serial 296926C227A3A1526392B19482A504
SKI 34EAEA0CEBC90ADAF9D0C5E2ED9B78C9879E1B6C
EM OV TLS CA - G3C (via Sectigo AAA)
SHA-256 7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489
Serial 8D5A4811939EF447D77111CDD9849AE9
SKI 34EAEA0CEBC90ADAF9D0C5E2ED9B78C9879E1B6C
EM EV TLS CA - G3C (via emSign)
SHA-256 432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3
Serial 8009928A64107601AF05FA2F19F109
SKI A6255E80A94DB7F3967C1BC791DF92BCEE4BEB5F
EM EV TLS CA - G3C (via Sectigo AAA)
SHA-256 FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F
Serial 05F078752DECE7A1BB8595D303167171
SKI A6255E80A94DB7F3967C1BC791DF92BCEE4BEB5F
EM OV CS CA - G3B (via emSign)
SHA-256 9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4
Serial 7818933F8F85B4619A2B35FBA83D0A
SKI 79FAE8ECCFDB0D758DA458D69FB6EDDC7C49DEE6
EM OV CS CA - G3B (via Sectigo AAA)
SHA-256 A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8
Serial 41E32B73AC185D6CE0D7480BACF84E69
SKI 79FAE8ECCFDB0D758DA458D69FB6EDDC7C49DEE6
EM OV CS CA - G3C (via emSign)
SHA-256 4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1
Serial 544B7F922C31AE220A4A922916F6E6
SKI EE6F73298D6FBFA7B31BC39534F61556447866B7
EM OV CS CA - G3C (via Sectigo AAA)
SHA-256 84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD
Serial D6A5EF613B390F32EF9CDF349F772841
SKI EE6F73298D6FBFA7B31BC39534F61556447866B7
emSign Root TLS CA - G3 (root)
SHA-256 7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D
Serial 0E760672F143459FC8FE0AB0BC05E394
SKI E13AAB217CF1E34D54AED472B7FD8B5942209054
emSign Root CS CA - G3 (root)
SHA-256 F2EDAAFF3D9B701FB4D14C9F285F8C0BAD90FC3BCC1FA07D47F4718EDABA34A5
Serial 018FCEC927C116F59C997EDC0CCD2E2B
SKI 687403F885CA8D5450571D16E0C8DE450DABCD2D
AAA Certificate Services (root)
SHA-256 D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4
Serial 01
SKI A0110A233E96F107ECE2AF29EF82A57FD030A4B4
emSign ECC Root CA - G3 (root)
SHA-256 86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B
Serial 3CF607A968700EDA8B84
SKI 7C5D028413D4CC8A9B81CE171C2E291E9C486342
Sources
- eMudhra intermediate CA compromise: disclosure site, certificate list, test hosts, and patched versions
- Mozilla Bugzilla 2083449: incident report and root cause
- Fleet: find and fix the trust stores your browser doesn't cover: detection, remediation scripts, and lab results
- Google and Mozilla: browser response
- Node.js CLI documentation and Go crypto/x509: how each runtime chooses its roots
Tips and Best Practices
- Leave the agent's bundle to Factorial IT. Do not edit or restore it. The scripts already skip it once it is clean.
- Start with a pilot group. Run each script on a handful of devices, read the output, then roll out to all devices.
- Keep the OS up to date. Browsers and apps that verify through the OS are only safe once the Apple or Microsoft update is installed.
-
Do not forget runtimes and images. Node.js, Electron, Go binaries,
certifi, and container images stay exposed even on a patched device. Update them and rebuild. -
Check again after macOS updates. An update can restore
/etc/ssl/cert.pem, so run the macOS report after each one. - Restart services on Linux servers. Most tools read the CA bundle only at start-up.
-
Treat an intermediate found on a device as an incident. A
compromised_intermediate_presentrow means someone or something put that certificate there.
Troubleshooting and FAQ
Troubleshooting
-
Fleet's Windows script fails with
ScriptContainedMaliciousContent. Microsoft Defender blocked it, because the script contains the compromised certificates. Run the Factorial IT script for Windows instead, or bring the device online and lift the Group Policy named in thectl_autoupdate_policyrow. -
The Factorial IT script for Windows exits with
2. Some certificates could not be downloaded. Read theWARNlines and check that the device can reachcrt.sh. -
The macOS script exits with
0buthomebrew_ca_certificatesis still there. Homebrew was skipped, usually because the Xcode licence has not been accepted. Runsudo xcodebuild -license accepton the Mac and run the script again. -
A
fleetd_osquery_ca_bundlerow stays after a full day online. Email support@factorial.it with the device name. - A Fleet policy keeps failing on one device. The script refused to change the agent's bundle. Its output explains why.
-
The Windows report shows
NOT_SYNCEDorBLOCK_WILL_NOT_ARRIVE. Microsoft's block has not reached the device: it is offline, uses WSUS only, or a Group Policy stops the automatic update. Run the remediation script, or fix the cause. -
The macOS report shows
STALE_NO_APPLE_BLOCK. The Mac has not received Apple's block. It is probably offline or cannot reach Apple's update servers. -
A test host returns
200. That client still trusts the forged chain. Check which trust store it uses and fix it with the steps above.
FAQ
-
Do I need to do anything for the Factorial IT agent?
No. Factorial IT removes both roots from the agent's bundle on every enrolled device, and new enrolment packages come without them.
-
My devices are up to date. Am I safe?
Browsers and apps that verify through the OS are. Command-line tools on macOS, the Linux system bundle, Node.js, Electron,certifi, Go binaries on Linux, and container images are not, until you fix them.
-
Could my users have been attacked through their browsers?
Not through browsers that enforce Certificate Transparency: no TLS certificate from these intermediates appears in CT logs. An attacker would also need to be on the network path.
-
Are packages installed with
aptordnfat risk?
No. They are GPG-signed.
-
Is it safe to run the remediation scripts twice?
Yes. They keep a*.pre-emudhrabackup of every file they change, so you can also roll back.
-
Why do
root_in_keychainandroot_in_cert_storerows stay after the fix?
They are context only. Apple and Microsoft keep both roots on every device and block the intermediates instead.
-
Will the custom agent restart or wipe devices?
No. It never restores a backup, locks, wipes, or restarts a device, and it waits for an admin's approval before each stage.
-
What happens to the remediation kit when the job is done?
It stays in place. The agent closes its ticket and tells you it can be paused.