> For the complete documentation index, see [llms.txt](https://docs.sonarsource.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sonarsource.com/sonarqube-community-build/server-update-and-maintenance/release-notes/emudhra-certificate-authority-compromise.md).

# eMudhra certificate authority compromise

Workarounds to stop SonarQube Community Build and its scanners from trusting the compromised eMudhra certificate authorities.

In October 2026, the private keys of several eMudhra intermediate certificate authorities were found to be compromised. Certificates issued with these keys can be used to impersonate any website. Browsers and operating systems already block them, and Mozilla removed emSign ECC Root CA - G3 from its trust store. AAA Certificate Services is also affected because it cross-signed the same keys:

| Root                     | SHA-256 fingerprint                                                                               |
| ------------------------ | ------------------------------------------------------------------------------------------------- |
| emSign ECC Root CA - G3  | `86:A1:EC:BA:08:9C:4A:8D:3B:BE:27:34:C6:12:BA:34:1D:81:3E:04:3C:F9:E8:A8:62:CD:5C:57:A3:6B:BE:6B` |
| AAA Certificate Services | `D7:A7:A0:FB:5D:7E:27:31:D7:71:E9:48:4E:BC:DE:F7:1D:5F:0C:3E:0A:29:48:78:2B:C8:3E:E0:EA:69:9E:F4` |

Java does not check certificate revocation by default, so the revocations by eMudhra and Sectigo do not protect SonarQube Community Build. It keeps trusting these certificates until the roots are removed.

This page explains how to stop SonarQube Community Build and its scanners from trusting these roots until an updated release is available.

## Impact on SonarQube Community Build

SonarQube Community Build uses the trust store of its Java runtime when it connects to other services over HTTPS:

* DevOps platforms (GitHub, GitLab, Bitbucket, and Azure DevOps)
* Authentication providers, including LDAP over TLS (LDAPS)
* Webhooks
* Email
* The Marketplace

The Java runtime used by SonarQube Community Build still trusts at least one of these roots. The scanners are affected in the same way when they use the Java runtime that SonarQube Community Build provides to them.

To exploit this, an attacker would also need to intercept the network traffic between SonarQube Community Build and those services. Connections from your users to SonarQube Community Build are not affected.

**Affected versions:** SonarQube Community Build, including 26.10.0.133078, on all installation types (Docker, Kubernetes, and ZIP).

The fix for SonarQube Community Build will ship in an upcoming release. The Java runtimes that SonarQube Community Build provides to scanners will be updated as well. Until then, apply the workaround for your installation type and the [scanner workaround](#scanners).

Removing these roots does not affect connections to GitHub, GitLab, Bitbucket, Azure DevOps, or Sonar services. However, connections to self-hosted services whose certificate chains to emSign ECC Root CA - G3, such as a DevOps platform, identity provider, SMTP server, or webhook receiver, will fail. This is intended: replace the certificates of those services.

## Docker <a href="#docker" id="docker"></a>

The SonarQube Community Build Docker image trusts **emSign ECC Root CA - G3**. Choose one of the following options.

### Option A: Mount a cleaned trust store <a href="#docker-option-a" id="docker-option-a"></a>

This option does not require you to rebuild the image.

1. Create a copy of the trust store without the root, using the image you run:

   ```sh
   IMAGE=sonarqube:26.10.0.133078-community   # replace with your image
   mkdir -p truststore
   docker run --rm --user "$(id -u):$(id -g)" --entrypoint bash -v "$PWD/truststore":/out "$IMAGE" -c '
     cp "$JAVA_HOME/lib/security/cacerts" /out/cacerts &&
     keytool -delete -keystore /out/cacerts -storepass changeit \
       -alias "cn_emsign_ecc_root_ca__g3,o_emudhra_technologies_limited,ou_emsign_pki,c_in [jdk]"'
   ```

   If SonarQube Community Build must trust a private CA, import it into the same file:

   ```sh
   docker run --rm --user "$(id -u):$(id -g)" --entrypoint keytool -v "$PWD/truststore":/out -v "$PWD/my-ca.crt":/my-ca.crt:ro "$IMAGE" \
     -importcert -noprompt -keystore /out/cacerts -storepass changeit -alias my-ca -file /my-ca.crt
   ```
2. Mount the file and set **both** environment variables:

   ```sh
   docker run -d \
     -v "$PWD/truststore":/opt/sonarqube/truststore:ro \
     -e SONAR_WEB_JAVAADDITIONALOPTS="-Djavax.net.ssl.trustStore=/opt/sonarqube/truststore/cacerts -Djavax.net.ssl.trustStorePassword=changeit" \
     -e SONAR_CE_JAVAADDITIONALOPTS="-Djavax.net.ssl.trustStore=/opt/sonarqube/truststore/cacerts -Djavax.net.ssl.trustStorePassword=changeit" \
     "$IMAGE"
   ```

   For Docker Compose, add the same volume and environment variables to the SonarQube service. If you already set these variables, add the two `-D` options to your existing values. The environment variables replace any `sonar.web.javaAdditionalOpts` and `sonar.ce.javaAdditionalOpts` set in a mounted `conf/sonar.properties` file, so copy those values into the variables as well.
3. Check that SonarQube Community Build uses the cleaned trust store. If the path is wrong, or the file is missing or cannot be read, Java silently falls back to the default trust store of the image, which still trusts the root. This command checks that the Web and Compute Engine processes both use the file, and that the file can be read and no longer contains the root. It should print `OK`:

   ```sh
   docker exec <container> bash -c '
     f=/opt/sonarqube/truststore/cacerts
     n=$(grep -l "[j]avax.net.ssl.trustStore=$f" /proc/[0-9]*/cmdline 2>/dev/null | wc -l)
     [ "$n" -eq 2 ] || { echo "ERROR: the trust store option is set on $n of 2 processes"; exit 1; }
     ts=$(keytool -list -keystore "$f" -storepass changeit 2>&1) || { echo "ERROR: cannot read $f"; exit 1; }
     if printf "%s" "$ts" | grep -q "86:A1:EC:BA:08:9C:4A:8D"; then echo "ERROR: $f still trusts emSign ECC Root CA - G3"; exit 1; fi
     echo "OK: SonarQube Community Build uses the cleaned trust store"'
   ```

{% hint style="warning" %}
Recreate the file whenever you change the image version.
{% endhint %}

### Option B: Build a derived image

This option also cleans the operating system trust store used by tools such as `curl`:

```dockerfile
FROM sonarqube:26.10.0.133078-community
USER root
RUN set -eux; \
    apt-get update; \
    apt-get --no-install-recommends -y install ca-certificates; \
    rm -rf /var/lib/apt/lists/*; \
    if keytool -list -cacerts -storepass changeit -alias 'cn_emsign_ecc_root_ca__g3,o_emudhra_technologies_limited,ou_emsign_pki,c_in [jdk]' > /dev/null 2>&1; then \
        keytool -delete -cacerts -storepass changeit -alias 'cn_emsign_ecc_root_ca__g3,o_emudhra_technologies_limited,ou_emsign_pki,c_in [jdk]'; \
    fi; \
    keytool -list -cacerts -storepass changeit > /tmp/cacerts.txt; \
    if grep -q '86:A1:EC:BA:08:9C:4A:8D:3B:BE:27:34:C6:12:BA:34:1D:81:3E:04:3C:F9:E8:A8:62:CD:5C:57:A3:6B:BE:6B' /tmp/cacerts.txt; then \
        echo 'ERROR: emSign ECC Root CA - G3 is still trusted' >&2; exit 1; \
    fi; \
    rm /tmp/cacerts.txt
USER sonarqube
```

Replace the `FROM` line with the image you run. The build fails if the root is still present, and still succeeds once you switch to an image that no longer includes it.

## Kubernetes <a href="#kubernetes" id="kubernetes"></a>

1. Create the cleaned trust store as described in [Docker Option A](#docker-option-a), step 1, using the image your deployment runs. Include any private CAs.
2. Store the trust store in a Secret:

   ```sh
   kubectl -n <namespace> create secret generic sonarqube-truststore --from-file=cacerts=truststore/cacerts
   ```
3. Add the following to the values of your `sonarqube` chart release, which already has `community.enabled: true`, then run `helm upgrade`:

   ```yaml
   env:
     - name: SONAR_WEB_JAVAADDITIONALOPTS
       value: "-Djavax.net.ssl.trustStore=/opt/sonarqube/truststore/cacerts -Djavax.net.ssl.trustStorePassword=changeit"
     - name: SONAR_CE_JAVAADDITIONALOPTS
       value: "-Djavax.net.ssl.trustStore=/opt/sonarqube/truststore/cacerts -Djavax.net.ssl.trustStorePassword=changeit"
   extraVolumes:
     - name: truststore
       secret:
         secretName: sonarqube-truststore
   extraVolumeMounts:
     - name: truststore
       mountPath: /opt/sonarqube/truststore
       readOnly: true
   ```
4. Check that SonarQube Community Build uses the cleaned trust store. As with Docker, a missing or unreadable file falls back silently to the default trust store. This command should print `OK`:

   ```sh
   kubectl -n <namespace> exec <sonarqube-pod> -c sonarqube -- bash -c '
     f=/opt/sonarqube/truststore/cacerts
     n=$(grep -l "[j]avax.net.ssl.trustStore=$f" /proc/[0-9]*/cmdline 2>/dev/null | wc -l)
     [ "$n" -eq 2 ] || { echo "ERROR: the trust store option is set on $n of 2 processes"; exit 1; }
     ts=$(keytool -list -keystore "$f" -storepass changeit 2>&1) || { echo "ERROR: cannot read $f"; exit 1; }
     if printf "%s" "$ts" | grep -q "86:A1:EC:BA:08:9C:4A:8D"; then echo "ERROR: $f still trusts emSign ECC Root CA - G3"; exit 1; fi
     echo "OK: SonarQube Community Build uses the cleaned trust store"'
   ```

{% hint style="warning" %}
If you use `caCerts.enabled: true`, the chart builds its own trust store from the image, which adds the root back. Instead, import your CA certificates in step 1, set `caCerts.enabled: false`, and use the values above.
{% endhint %}

Recreate the Secret whenever you change the image version.

## ZIP installation on Linux or macOS <a href="#zip-linux-macos" id="zip-linux-macos"></a>

SonarQube Community Build runs on the Java runtime installed on your machine. Upgrading SonarQube Community Build does not change that Java runtime, and upgrading Java alone is not reliable either, because vendors fixed this differently. Remove **both** roots from the Java runtime that SonarQube Community Build uses.

1. Find the Java runtime that SonarQube Community Build uses:

   * **Started with `bin/<os>/sonar.sh`:** SonarQube Community Build uses `SONAR_JAVA_PATH`, or else `java` from the `PATH`.
   * **Running as a systemd service or from an init script:** `SONAR_JAVA_PATH` does not apply. SonarQube Community Build uses the Java executable set in the `ExecStart` line of the unit file (for example, `/opt/java/bin/java`) or in your init script. Set `JAVA` to that path in the command below.

   ```sh
   JAVA="${SONAR_JAVA_PATH:-java}"   # for a service, the Java executable from ExecStart
   "$JAVA" -XshowSettings:properties -version 2>&1 | grep java.home
   export JAVA_HOME=<the java.home value printed above>
   ```

   If SonarQube Community Build already uses its own trust store (`-Djavax.net.ssl.trustStore=` in `conf/sonar.properties` or in the `SONAR_*_JAVA*OPTS` variables), clean that file instead: whatever way Java was installed, run the script from step 2 with the trust store path and password, and in step 3, replace `-cacerts -storepass changeit` with `-keystore <trust store path> -storepass <password>`.
2. Remove the roots. The procedure depends on how Java was installed:
   * **Java from a vendor download** (Eclipse Temurin, Oracle, Azul Zulu, Amazon Corretto, SapMachine, Liberica, and others): save this script as `remove-emsign-roots.sh`:

     ```sh
     #!/bin/sh
     # Remove emSign ECC Root CA - G3 and AAA Certificate Services from a Java trust store.
     # Usage: remove-emsign-roots.sh <java.home> [<trust store path> <trust store password>]
     # Without a trust store path, cleans the cacerts of the Java runtime at <java.home>.
     # Certificates are matched by SHA-256 fingerprint, so this works whatever alias is used.
     set -eu
     KEYTOOL="$1/bin/keytool"
     if [ $# -ge 3 ]; then STORE="$2"; PASS="$3"; else STORE=""; PASS=changeit; fi
     kt() {
       if [ -n "$STORE" ]; then "$KEYTOOL" "$@" -keystore "$STORE" -storepass "$PASS"
       else "$KEYTOOL" "$@" -cacerts -storepass "$PASS"; fi
     }
     LIST="$(mktemp)"
     trap 'rm -f "$LIST"' EXIT
     FPS='86:A1:EC:BA:08:9C:4A:8D:3B:BE:27:34:C6:12:BA:34:1D:81:3E:04:3C:F9:E8:A8:62:CD:5C:57:A3:6B:BE:6B
     D7:A7:A0:FB:5D:7E:27:31:D7:71:E9:48:4E:BC:DE:F7:1D:5F:0C:3E:0A:29:48:78:2B:C8:3E:E0:EA:69:9E:F4'
     kt -list -v > "$LIST"
     for fp in $FPS; do
       awk -v fp="SHA256: $fp" '/^Alias name: /{a=substr($0,13)} index($0,fp){print a}' "$LIST" |
         while IFS= read -r alias; do
           echo "Removing alias: $alias"
           kt -delete -alias "$alias"
         done
     done
     kt -list > "$LIST"
     for fp in $FPS; do
       if grep -q "$fp" "$LIST"; then echo "ERROR: $fp is still trusted" >&2; exit 1; fi
     done
     echo "OK: emSign ECC Root CA - G3 and AAA Certificate Services are no longer trusted by ${STORE:-the cacerts of $1}"
     ```

     Then run it as a user allowed to write to the Java installation.

     ```sh
     sudo sh remove-emsign-roots.sh "$JAVA_HOME"
     # or, for your own trust store:
     sudo sh remove-emsign-roots.sh "$JAVA_HOME" /path/to/truststore.p12 '<password>'
     ```
   * **Java from Debian or Ubuntu packages:** disable both roots in the system store, then regenerate the Java trust store from it. This works whether or not your release has an updated `ca-certificates` package:

     ```sh
     sudo apt-get update
     sudo apt-get install --only-upgrade ca-certificates
     sudo sed -i -E 's#^(mozilla/(emSign_ECC_Root_CA_-_G3|Comodo_AAA_Services_root)\.crt)#!\1#' /etc/ca-certificates.conf
     sudo rm -f /etc/ssl/certs/java/cacerts
     sudo update-ca-certificates --fresh
     ```

     This rebuilds `/etc/ssl/certs/java/cacerts`. If you imported certificates directly into that file with `keytool`, import them again, or place them in `/usr/local/share/ca-certificates/` so that they are kept.
   * **Java from Red Hat Enterprise Linux, Rocky Linux, AlmaLinux, or Fedora packages:**

     ```sh
     sudo dnf update ca-certificates
     ```
3. Check that the roots are gone. This command should print `0`:

   ```sh
   "$JAVA_HOME/bin/keytool" -list -cacerts -storepass changeit | grep -cE '86:A1:EC:BA:08:9C:4A:8D|D7:A7:A0:FB:5D:7E:27:31'
   ```

   Whatever way Java was installed, if `$JAVA_HOME/lib/security/jssecacerts` exists, Java uses it instead of `cacerts`, and the check above does not read it. Remove the roots from that file with the script from step 2, then check it. Its password is usually `changeit`:

   ```sh
   sudo sh remove-emsign-roots.sh "$JAVA_HOME" "$JAVA_HOME/lib/security/jssecacerts" changeit
   "$JAVA_HOME/bin/keytool" -list -keystore "$JAVA_HOME/lib/security/jssecacerts" -storepass changeit | grep -cE '86:A1:EC:BA:08:9C:4A:8D|D7:A7:A0:FB:5D:7E:27:31'
   ```
4. Restart SonarQube Community Build.

## ZIP installation on Windows <a href="#zip-windows" id="zip-windows"></a>

1. Find the Java runtime that SonarQube Community Build uses:

   * **Windows service:** open `bin\windows-x86-64\lib\SonarServiceWrapper.xml` and look at the Java executable. If it's only `java.exe`, the service uses the first `java.exe` on the **system** `PATH` (System environment variables).
   * **Started from the command line:** SonarQube Community Build uses `SONAR_JAVA_PATH`, or else `java.exe` from the `PATH`.

   Then get the home directory of that Java runtime:

   ```bat
   "<path to java.exe>" -XshowSettings:properties -version 2>&1 | findstr java.home
   ```
2. Remove both roots. In a command prompt opened as Administrator, run:

   ```bat
   "<java.home>\bin\keytool" -list -v -cacerts -storepass changeit > cacerts.txt
   ```

   In `cacerts.txt`, search for `86:A1:EC:BA:08:9C:4A:8D` and `D7:A7:A0:FB:5D:7E:27:31`. For each match, note the `Alias name:` shown above it. A root can appear under more than one alias. Then delete each of those entries:

   ```bat
   "<java.home>\bin\keytool" -delete -cacerts -storepass changeit -alias "<alias name>"
   ```

   If SonarQube Community Build already uses its own trust store, clean that file instead: in this step and the next, replace `-cacerts -storepass changeit` with `-keystore "<trust store path>" -storepass <password>`.
3. Check that the roots are gone. Each command should print nothing:

   ```bat
   "<java.home>\bin\keytool" -list -cacerts -storepass changeit | findstr /c:"86:A1:EC:BA:08:9C:4A:8D"
   "<java.home>\bin\keytool" -list -cacerts -storepass changeit | findstr /c:"D7:A7:A0:FB:5D:7E:27:31"
   ```

   If `<java.home>\lib\security\jssecacerts` exists, Java uses it instead of `cacerts`. Repeat steps 2 and 3 for that file, with `-cacerts` replaced by `-keystore "<java.home>\lib\security\jssecacerts"`.
4. Restart SonarQube Community Build. For the Windows service, run `Restart-Service SonarQube` in PowerShell, or use Services.

{% hint style="warning" %}
If you use a separate trust store through `sonar.web.javaAdditionalOpts` and `sonar.ce.javaAdditionalOpts` in `conf\sonar.properties`, write its path **with forward slashes**, for example `C:/sonarqube/conf/truststore.p12`. Backslashes are silently removed from paths in that file, and SonarQube Community Build would keep trusting the compromised certificates.
{% endhint %}

## Scanners <a href="#scanners" id="scanners"></a>

SonarQube Community Build can provide a Java runtime to scanners automatically (JRE provisioning). Until updated runtimes are available:

1. Run your scanners with a Java runtime cleaned as described in [ZIP installation on Linux or macOS](#zip-linux-macos) or [ZIP installation on Windows](#zip-windows).
2. Disable JRE provisioning with the analysis parameter `sonar.scanner.skipJreProvisioning=true`, or the environment variable `SONAR_SCANNER_SKIP_JRE_PROVISIONING=true`.

JRE provisioning only applies to scanners that download their Java runtime from SonarQube Community Build. The `sonarsource/sonar-scanner-cli` Docker image ships its own Java, and the scanners for Maven and Gradle run on the Java of your build. These scanners ignore the setting above: clean their Java runtime as described in [ZIP installation on Linux or macOS](#zip-linux-macos) or [ZIP installation on Windows](#zip-windows).

## After the fix is released <a href="#after-the-fix" id="after-the-fix"></a>

Remove the workaround once you run a SonarQube Community Build image or, for ZIP installations, a Java runtime that no longer trusts the affected certificates. For Docker and Kubernetes, updating to the fixed SonarQube Community Build release is the simplest option. With the Helm chart, the image tag comes from `image.tag` if you set it, and otherwise from `community.buildNumber`. Update whichever one you use to the fixed release, or remove `image.tag` and upgrade to a chart version that defaults to it. Once that release provides updated runtimes for the scanners, you can enable JRE provisioning again.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.sonarsource.com/sonarqube-community-build/server-update-and-maintenance/release-notes/emudhra-certificate-authority-compromise.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
