Missing TLS certificate pinning in a Flutter HTTP client

ID

dart.network.android_certificate_pinning

Severity

low

Remediation Complexity

trivial

Remediation Risk

medium

Remediation Effort

medium

Resource

Network

Language

Dart

Description

A Flutter HTTP client (HttpClient, Dio) is constructed in a file that contains no certificate-pinning telltale. Flutter does not use the OS proxy / native trust store on most targets, so relying on system roots alone leaves the app trivially MitM-able by anyone with a device-trusted CA.

Rationale

Cossack Labs and Talsec both emphasize that TLS pinning on Flutter must happen at the Dart layer. Native Android / iOS network-security config does not apply to traffic that Flutter generates through dart:io HttpClient or package:dio (which is the vast majority of Flutter HTTP traffic). On a device the user has rooted, on a corporate device with a deployed MitM CA, or after malware installs a root CA, every default-trusted certificate validates — and Flutter has no way to flag the anomaly without explicit pinning.

This rule fires when a file constructs HttpClient() or Dio() / Dio($OPTS) but contains none of: - setTrustedCertificatesBytes / setTrustedCertificates - CertificatePinningInterceptor (dio_certificate_pinning) - HttpCertificatePinning.check (http_certificate_pinning) - SslPinningPlugin.check (ssl_pinning_plugin) - withTrustedRoots: false (the SecurityContext form) - useCertificateChain

Confidence is LOW because legitimate codebases may build clients here and pin in a different file (e.g. a shared HTTP-factory module). The finding is best triaged at policy level: confirm pinning happens somewhere in the project tree.

The following code illustrates a vulnerable pattern detected by this rule:

class NoPinning {
  HttpClient http() {
    // VULNERABLE: Missing TLS certificate pinning in a Flutter HTTP client
    return HttpClient();
  }
}

Remediation

Add Dart-layer pinning at the HTTP factory point — typically once, in a central location:

// Vulnerable: no pinning
final dio = Dio();
final hc = HttpClient();

// Fixed: pinned SecurityContext
final ctx = SecurityContext(withTrustedRoots: false)
  ..setTrustedCertificatesBytes(rootCaBytes);
final hc = HttpClient(context: ctx);

// Fixed: dio_certificate_pinning interceptor
dio.interceptors.add(CertificatePinningInterceptor(
  allowedSHAFingerprints: ['7HIpactk...'],
));

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved