
Flutter 3.47.8 is a security release you should not skip. On 23 October 2026 the Dart team published advisory GHSA-frr6-h895-5pxp (severity High, CWE-295): after the eMudhra intermediate CA compromise, Flutter and Dart apps on Android, Windows and some Linux setups could be hit by a TLS man-in-the-middle attack using forged certificates. Upgrading the SDK is not enough on its own. You have to rebuild and ship new binaries. This post covers who is affected, how I checked it, and the release checklist I am using for client apps.
What happened with the eMudhra intermediate CA compromise
On 17 October 2026 a researcher reported to the Apple, Chrome, Microsoft and Mozilla root programs that the private keys of eight eMudhra intermediate CA certificates could be recovered. According to the Mozilla incident bug, six of them are TLS intermediates trusted by all four root programs and two are code signing intermediates trusted by Microsoft. Some were also cross-signed by Sectigo. The public disclosure site explains that the keys were generated with a flaw that lets anyone recover them from the certificates, and that this was possible since the certificates were created in April 2025.
With one of those keys, an attacker on the network path can issue a valid-looking certificate for any domain, including your API.
Browsers moved fast. Google says Chrome pushed a CRLSet block the same evening, Android devices received an equivalent block through Google Play services, and the Chrome Root Store will drop the emSign ECC Root CA - G3 root on 27 October 2026. The disclosure site lists blocks in Firefox and Chrome since 17 October, on Microsoft platforms since the 20th and on Apple products since the 22nd. eMudhra revoked the eight certificates on 18 October.
The problem is every client that does not use those revocation channels. That includes apps that ship their own TLS stack and CA handling, and that is where Dart's dart:io comes in.
Why Flutter apps were exposed (flutter tls vulnerability)
On Android and Windows, dart:io validates certificate chains with the BoringSSL engine compiled into the Dart runtime. The advisory states that it does not read OS-managed intermediate blocklists. So even though Android and Windows themselves blocked the eMudhra intermediates, a Flutter app making HTTPS calls through dart:io kept trusting them.
That covers more code than people expect. HttpClient, the default IOClient behind package:http, Dio's default adapter on mobile and desktop, and WebSocket from dart:io all go through the same stack.

Who is affected, by platform
| Platform | Affected? | Why | What to do |
|---|---|---|---|
| Android | Yes, before Flutter 3.47.8 | BoringSSL in dart:io, OS blocklist ignored | Upgrade, rebuild, publish a new release |
| Windows | Yes, before Flutter 3.47.8 | BoringSSL in dart:io, OS blocklist ignored | Upgrade, rebuild, ship a new installer |
| Linux | Only with a stale CA bundle | Uses the system ca-certificates bundle | Upgrade, rebuild, update ca-certificates |
| iOS and macOS | No | Apple Security.framework verifies the chain | Nothing in the app |
| Web | No | The browser verifies the chain | Nothing in the app |
Affected versions, from the advisory:
- Dart SDK stable before 3.13.7, beta before 3.14.0-315.3.beta
- Flutter SDK stable before 3.47.8, beta before 3.50.0-0.2.pre
What changed in Flutter 3.47.8 and Dart 3.13.7
The Dart 3.13.7 changelog lists two fixes:
- BoringSSL was updated so it explicitly distrusts the compromised eMudhra intermediates during chain verification (commit ecb2c83).
- On Android 14 and later,
dart:ionow loads root certificates from the updatable Conscrypt APEX module at/apex/com.android.conscrypt/cacertsinstead of/system/etc/security/cacerts(commit a9e26a2). That means future root changes delivered through Google Play system updates will reach Dart apps too.
The second change matters beyond this incident. Before it, a Flutter app on a fully updated Android 14 phone could still trust a root that Google had already removed.
I tested the difference on Linux
I wanted to see the fix work, not just read about it. The disclosure site runs test hosts that serve a certificate chained to one of the compromised intermediates. I wrote a small Dart script, then ran it with Dart 3.13.6 and Dart 3.13.7 on a Debian 13 box. First with the patched system trust store, then with an old Mozilla CA bundle (curl's cacert-2026-09-25.pem) to simulate an unpatched container.
import 'dart:io';
const testUrl = 'https://dv-g3b-emudhra.we-have-a-problem.com/';
Future<void> main(List<String> args) async {
SecurityContext? ctx;
if (args.isNotEmpty) {
// Simulate an unpatched trust store by loading an old CA bundle.
ctx = SecurityContext(withTrustedRoots: false)
..setTrustedCertificates(args.first);
}
final client = HttpClient(context: ctx);
try {
final res = await (await client.getUrl(Uri.parse(testUrl))).close();
await res.drain<void>();
print('VULNERABLE: connected, HTTP ${res.statusCode}');
} on HandshakeException catch (e) {
print('OK: rejected (${e.osError?.message.trim() ?? e.message})');
} finally {
client.close(force: true);
}
}Results:
| Dart SDK | Patched system store | Old CA bundle |
|---|---|---|
| 3.13.6 | Rejected (unable to get local issuer certificate) | Connected, HTTP 200 |
| 3.13.7 | Rejected (unable to get local issuer certificate) | Rejected (certificate revoked) |
This matches the advisory. On Linux, an updated ca-certificates package already protects you. With an old bundle, only the new SDK rejects the chain, because BoringSSL itself now refuses those intermediates.
How to check your Flutter and Dart version
On your machine:
flutter --version
dart --version
# Machine-readable, handy in CI logs
flutter --version --machineYou need Flutter 3.47.8 or later on stable (Dart 3.13.7).
Checking which SDK an already shipped APK or Windows build was compiled with is harder. I don't know of a documented, reliable way to read it back from the binary, so I use the build records: the CI log, the .fvmrc or pubspec in the release tag, or the release notes for that build. If you have none of these, assume an Android or Windows build made before 23 October 2026 is affected.
Upgrade to Flutter 3.47.8
For a plain Flutter install:
flutter channel stable
flutter upgrade
flutter --version # should show 3.47.8 and Dart 3.13.7If you use FVM per project:
fvm install 3.47.8
fvm use 3.47.8
fvm flutter --versionThen make old SDKs impossible to use by accident. Raising the Dart SDK lower bound in pubspec.yaml makes pub get fail on anything older than Dart 3.13.7:
environment:
sdk: ^3.13.7
flutter: 3.47.8I tested this with Dart 3.13.6 and dart pub get stops with "The current Dart SDK version is 3.13.6" and "version solving failed". That is exactly what you want on a teammate's laptop or an old CI runner.
CI: GitHub Actions and Docker images
With subosito/flutter-action, pin the version instead of floating on "latest":
- uses: actions/checkout@v6
- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: 3.47.8
cache: true
- run: flutter --version
- run: flutter build appbundle --releaseOr read it from the pubspec with flutter-version-file: pubspec.yaml. The action needs an exact version in the flutter: field for that to work, which is why the example above uses flutter: 3.47.8. If your CI pulls a pinned Flutter Docker image, bump the tag to one built on 3.47.8 and keep the flutter --version step so the log proves it. Cached SDKs are a common trap. If the cache key does not include the version, you can rebuild on the old SDK without noticing. I wrote more about release pipelines in my solo SaaS CI/CD setup.
Rebuild and release checklist
The vulnerable code is compiled into your app, so every affected binary has to be rebuilt and shipped:
- Android: bump
versioninpubspec.yaml(build number too), runflutter build appbundle --release, upload to Google Play and roll out. A staged rollout is fine, but don't leave it at 1% for weeks. - Android outside Play (APK downloads, enterprise MDM, other stores): rebuild the APK and replace it everywhere it is distributed.
- Windows: run
flutter build windows --release, then repackage and re-sign your MSIX or installer, and publish it through your update channel. - Linux desktop: run
flutter build linux --releaseand ship new packages. On servers and containers, rebuild Dart services on thedart:3.13.7Docker image and update the CA bundle in every image. - iOS, macOS and web: no rebuild needed for this issue. Ship the fix with your next normal release.
- Tell clients or your team which build number contains the fix, so support can check quickly.
For Linux containers, refresh the system bundle during the image build:
FROM dart:3.13.7 AS build
WORKDIR /app
COPY . .
RUN dart pub get && dart compile exe bin/server.dart -o /app/server
FROM debian:trixie-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /app/server /app/server
CMD ["/app/server"]If your backend is PHP, check it too. Composer's composer/ca-bundle released 1.6.0 and 1.4.16 on 20 October 2026 to distrust the compromised eMudhra intermediates. I have looked after mixed Flutter and PHP stacks before (see taking over a live Flutter and PHP app), and the backend's outbound HTTPS calls to payment or SMS providers deserve the same check. The disclosure site keeps a longer list of patched runtimes, including Node.js, Python, Go and Electron.
Does certificate pinning protect you?
Partly, and only for the hosts you pin. If your app trusts only your own server's certificate authority or public key for your API, a forged certificate from an eMudhra intermediate would not pass for that host. In dart:io you can do this with SecurityContext(withTrustedRoots: false) and setTrustedCertificatesBytes() with the CA certificates you choose, which is the same mechanism my test script uses.
But pinning has real limits:
- It covers only the hosts you configure. Third-party SDKs, analytics, payment APIs and image CDNs are not covered.
- If your provider changes CA or rotates keys and your pins don't match, old app versions stop connecting. Always pin a backup and plan the rotation.
- It is defence in depth, not a replacement for the SDK update. The 3.47.8 fix protects every connection, pinned or not.
I covered related client-side trade-offs in securing API keys in Flutter. The short version: never treat the client as fully trusted.
What about users on old app versions?
Publishing a fix does not update phones. Users with auto-update turned off can stay on the vulnerable build for months. If your app handles payments, health data or logins, add a minimum supported version check:
/// Returns true if [current] is older than [minimum] (both "major.minor.patch").
bool isBelowMinimum(String current, String minimum) {
List<int> parse(String v) =>
v.split('+').first.split('.').map((p) => int.tryParse(p) ?? 0).toList();
final a = parse(current), b = parse(minimum);
for (var i = 0; i < 3; i++) {
final x = i < a.length ? a[i] : 0, y = i < b.length ? b[i] : 0;
if (x != y) return x < y;
}
return false;
}At startup, compare PackageInfo.fromPlatform() (from package_info_plus) against a minVersion value from Firebase Remote Config or your own config endpoint. If the app is too old, show a blocking "Please update" screen that links to the store. On Android you can also use Google Play's in-app updates API with an immediate update flow.
Be honest about one limit: the old build's version check also goes over HTTPS through the same vulnerable stack. A forced update protects users from then on, but it can't protect a connection that is already being intercepted. Ship the fixed build first, then raise minVersion once it is live in the stores.
Summary
- The eMudhra intermediate CA compromise made forged certificates possible for any domain.
- Flutter apps using
dart:ioon Android and Windows ignored the OS blocklists, and Linux was exposed with stale CA bundles. - Fix: Flutter 3.47.8 or Dart 3.13.7, then rebuild and redeploy Android, Windows and Linux builds.
- iOS, macOS and web builds need no change for this issue.
- Pin the SDK version in CI, update
ca-certificatesin containers, and use a minimum version check to move users off old builds.
Frequently asked questions
What is the eMudhra intermediate CA compromise?
In October 2026 researchers showed that the private keys of eight eMudhra intermediate CA certificates could be recovered because of flawed key generation. Anyone holding those keys could issue trusted-looking TLS certificates for any domain, so browsers, operating systems and runtimes blocked the certificates.
Which Flutter version fixes the eMudhra TLS vulnerability?
Flutter 3.47.8 on the stable channel, which ships Dart 3.13.7. On beta the fix is Flutter 3.50.0-0.2.pre with Dart 3.14.0-315.3.beta.
Is my Flutter iOS app affected by the eMudhra compromise?
No. On iOS and macOS, certificate verification goes through Apple's Security.framework, and Apple blocked the compromised intermediates on 22 October 2026. Android, Windows and Linux builds with stale CA bundles are the ones affected.
Do I need to republish my Android app after upgrading Flutter?
Yes. The vulnerable TLS code is compiled into your app, so upgrading the SDK on your machine changes nothing for users. Rebuild with Flutter 3.47.8 and publish a new release to Google Play and anywhere else you distribute the APK.
Does Flutter web need an update for the eMudhra issue?
No. Flutter web apps use the browser's TLS verification, and Chrome, Firefox, Edge and Safari already block the compromised certificates. Keeping your browser up to date is enough.
Was PHP also affected by the eMudhra certificate compromise?
PHP projects that use Composer's composer/ca-bundle package should update it. Versions 1.6.0 and 1.4.16, released on 20 October 2026, distrust the compromised eMudhra intermediates.
Sources
- TLS MitM vulnerability on Android, Windows, and Linux due to compromised eMudhra intermediate CA certificates (GHSA-frr6-h895-5pxp) (Dart SDK, GitHub Security Advisories, 2026-10-23)
- Dart SDK CHANGELOG (3.13.7) (Dart, 2026-10-22)
- Roll BoringSSL (stable), commit ecb2c83 (Dart SDK on GitHub)
- [dart:io] Use Conscrypt APEX root store on Android 14+, commit a9e26a2 (Dart SDK on GitHub)
- Flutter 3.47.8 release (Flutter on GitHub, 2026-10-23)
- Flutter SDK releases feed (Linux) (Flutter)
- eMudhra Intermediate CA Compromise (we-have-a-problem.com (Dennis Jackson and contributors), 2026-10-20)
- Bug 2083449: eMudhra: Private key compromise of Intermediate Certificates (Mozilla Bugzilla, 2026-10-18)
- Protecting Chrome and Android Users from Compromised eMudhra Intermediate CA Certificates (Google, 2026-10-20)
- composer/ca-bundle 1.6.0 (Composer on GitHub, 2026-10-20)
- subosito/flutter-action README (GitHub)
- CA certificates extracted from Mozilla (curl)
Keep reading
- How to Hide API Keys in Flutter (and What to Do Instead)You can't hide an API key inside a Flutter app. Which keys are safe to ship, how to restrict them, and how to keep real secrets behind a proxy with App Check.
- Taking Over an Existing Flutter App and PHP Backend: My PlaybookHow I take over a live Flutter app with a legacy PHP backend: a first-week checklist, reproducing bugs, PHP security fixes and a safe move to token auth.
- Play Billing Library Deadline: Updating Flutter IAP AppsGoogle Play's extension for Billing Library 7 and target API 36 ends on 1 Nov 2026. Here's how to upgrade Flutter in_app_purchase and retest it.
Comments
Share a question or note about this article.
Loading comments…