Back

Respond to the eMudhra CA compromise

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:

  1. 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.
  2. 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.

  1. Go to AI Agents > Agents, click Browse templates, filter by Equipment, and open Close the eMudhra CA exposure.
  2. In the instructions, find PILOT DEVICES and write a device group or device names after Pilot:. If you leave it blank, the agent suggests up to three Macs, three Linux devices, and three Windows devices that still need the fix.
  3. 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.
  4. 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.

  1. 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.
  2. 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.
  3. Run each script again, this time with Target all devices.
  4. 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.pem back as it was.
  • Watch for Homebrew being skipped. If brew cannot 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 with 0. The macOS report keeps showing its homebrew_ca_certificates row. Run sudo xcodebuild -license accept on that Mac, then run the script again. The copy that the custom agent puts in the Script library prints a warning and exits with 2 instead.

 

Roll back a change
  • On macOS and Linux, copy each *.pre-emudhra backup over the file it was taken from. On Linux, also remove the emudhra-*.pem files added to the p11-kit blocklist folder, put back /etc/ca-certificates.conf.pre-emudhra if it exists, and run update-ca-certificates or update-ca-trust.
  • On Windows, open certlm.msc and 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


 

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_present row 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 the ctl_autoupdate_policy row.
  • The Factorial IT script for Windows exits with 2. Some certificates could not be downloaded. Read the WARN lines and check that the device can reach crt.sh.
  • The macOS script exits with 0 but homebrew_ca_certificates is still there. Homebrew was skipped, usually because the Xcode licence has not been accepted. Run sudo xcodebuild -license accept on the Mac and run the script again.
  • A fleetd_osquery_ca_bundle row 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_SYNCED or BLOCK_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

  1. 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.
     
  2. 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.
     
  3. 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.
     
  4. Are packages installed with apt or dnf at risk?
    No. They are GPG-signed.
     
  5. Is it safe to run the remediation scripts twice?
    Yes. They keep a *.pre-emudhra backup of every file they change, so you can also roll back.
     
  6. Why do root_in_keychain and root_in_cert_store rows stay after the fix?
    They are context only. Apple and Microsoft keep both roots on every device and block the intermediates instead.
     
  7. 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.
     
  8. 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.

Was this article helpful?

Give feedback about this article

Can’t find what you’re looking for?

Our customer care team is here for you.

Contact us

Knowledge Base Software powered by Helpjuice