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...'],
));
References
-
OWASP Top 10 2025 - A04 : Cryptographic Failures.
-
OWASP Top 10 2025 - A07 : Authentication Failures.
-
Cossack Labs — Flutter application security considerations — Documents that Flutter ignores OS proxy / native TLS; pinning must be at the Dart layer
-
Talsec — OWASP Mobile Top 10 for Flutter M5 — Insecure communication for Flutter and Dart, with pinning guidance
-
package:dio_certificate_pinning (pub.dev) — Dio interceptor that pins by SHA-256 of the leaf certificate
-
package:ssl_pinning_plugin (pub.dev) — Cross-platform plugin to verify a server’s cert against a list of allowed fingerprints