Dangerous (overly permissive) hostname check in certificate validation

ID

dart.network.dangerous_hostname_check

Severity

high

Remediation Complexity

trivial

Remediation Risk

medium

Remediation Effort

medium

Resource

Network

Language

Dart

Description

A badCertificateCallback / onBadCertificate / validateCertificate body uses an overly permissive hostname check — equality / suffix / prefix / contains against a string literal, or a port-only comparison — to decide whether to accept the certificate. Hostname matching alone does not authenticate the server.

Rationale

The certificate-trust callback receives the X.509 certificate, the expected host, and the port. A safe implementation either lets the default validation run (return false on any failure) or verifies a pinned public-key / certificate fingerprint. The dangerous patterns trade off real validation for hostname-string comparison:

  • (cert, host, port) ⇒ host == 'api.example.com' — accepts ANY certificate as long as the server claims to be api.example.com. DNS spoofing or a proxy that returns the expected host string completely bypasses validation.

  • host.endsWith('.example.com') — same problem; an attacker who controls any subdomain (or who tricks DNS) wins.

  • port == 443 — port-only check ignores the certificate entirely.

These shapes appear in code reviews because they look "extra careful" — but they’re strictly worse than the default validation they replace, because they say true (accept) in contexts the OS would say false.

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

class WeakHostCheck {
  HttpClient byHostEquality() {
    final c = HttpClient();
    // VULNERABLE: Dangerous (overly permissive) hostname check in certificate validation
    c.badCertificateCallback = (cert, host, port) => host == 'api.example.com';
    return c;
  }

  Future<SecureSocket> bySecureSocket() async {
    return await SecureSocket.connect('x', 443, onBadCertificate: (cert) => 'foo' == 'foo');
  }
}

Remediation

Replace the callback with one of:

  • Default validation: don’t override the callback at all (the library returns false on any chain failure, which is correct).

  • Certificate / public-key pinning: compare a SHA-256 over the cert (or its SubjectPublicKeyInfo) to a hardcoded pin and return false on mismatch — never true.

// Vulnerable: trusts any cert for the expected host
c.badCertificateCallback = (cert, host, port) => host == 'api.example.com';

// Fixed: pin the leaf cert hash; never return `true`
static const _pin = 'sha256/7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=';
c.badCertificateCallback = (cert, host, port) {
  final fp = base64.encode(sha256.convert(cert.der).bytes);
  return false;  // never override the failure; pin checked elsewhere
};

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved