hunting-mobile-tls-pinning-and-trust-gaps · git:20260908.e3ae18a · 2026-09-08 · sha256 2c22e8f8263130f2

hunting-mobile-tls-pinning-and-trust-gaps git:20260908.e3ae18aA

Immutable. This exact content is served forever at /api/v1/blob/2c22e8f8263130f2.

---
name: hunting-mobile-tls-pinning-and-trust-gaps
description: >-
  Hunt transport trust gaps in a mobile app, where the app accepts a network position it should reject,
  because it trusts user-added certificate authorities, allows cleartext or mixed connections, disables or
  misapplies certificate validation, or pins only some connections so an unpinned or fallback path lets a
  network attacker read or alter traffic the app treats as secure. Use when reviewing how a mobile app
  establishes and validates its network connections and whether every sensitive connection resists an
  intercepting network position. Covers user-trusted anchors, cleartext and mixed connections, disabled or
  permissive validation, and partial or bypassable pinning. The intercepting network position the app fails
  to reject is the source, the connection the app treats as trusted is the sink, and reading or altering
  supposedly secure traffic is the bug.
license: MIT
---

# Hunting mobile TLS pinning and trust gaps: when the app trusts a network it should not

A mobile app talks to its backend over connections it treats as private, and that privacy depends entirely on
the app rejecting any network position that would let someone read or alter the traffic. The app does that by
validating the server's certificate against trusted anchors, refusing cleartext, and, for its most sensitive
connections, pinning to a known certificate or key so even a trusted-but-wrong authority is rejected. The gap
is any place the app accepts a network position it should reject: it trusts certificate authorities the user
or a device profile added, it allows a cleartext or mixed connection, it disables or loosens validation for
convenience, or it pins some connections but leaves an unpinned or fallback path a network attacker can take.
The bug is not the absence of pinning everywhere; it is a sensitive connection the app treats as secure that
an intercepting network position can actually read or alter. You hunt these by listing every connection and
checking whether each resists a network attacker.

## When to use

- A mobile app makes network connections it treats as private and you can observe its trust behavior.
- The app may trust user-added anchors, allow cleartext, or loosen certificate validation.
- Some sensitive connections may be unpinned, or pinning may have a bypassable fallback.

## Scope check

Hunt transport trust gaps only on apps and networks you own or are authorized to assess, on test devices and
accounts, interposing only on a network you control and never on real users' traffic. A confirmed gap exposes
supposedly secure traffic, so keep every probe within scope. If you can't name the authorization, stop.

## The loop

1. **Establish whether each sensitive connection rejects an intercepting position first.** For each connection
   the app treats as private, determine whether it validates the certificate against trusted anchors, refuses
   cleartext, and, where required, pins to a known certificate or key, so an intercepting network position is
   rejected, or whether some trust gap lets that position succeed. This is the false-positive killer: a
   connection that validates strictly and pins where it matters cannot be read by a network attacker, so its
   presence is not a finding. Name the accepted network position before claiming interception.

2. **Enumerate the app's connections and their sensitivity.** List the connections the app makes, the data
   each carries, and which are sensitive enough that reading or altering them would matter. Note which the app
   treats as secure and relies on for authentication or private data.

3. **Check the trust anchors and validation.** Determine whether the app trusts only the system anchors it
   should, or also user-added authorities or a device profile's anchors, and whether it validates the
   certificate fully, hostname included, or disables or loosens validation. A user-trusted anchor or disabled
   validation lets an interposed certificate be accepted.

4. **Check for cleartext and mixed connections.** Determine whether any sensitive connection uses cleartext or
   falls back to it, or mixes secure and insecure content, so part of the traffic is readable regardless of
   the certificate handling.

5. **Check the pinning coverage and fallback.** For connections that pin, determine whether every sensitive
   connection is pinned or only some, and whether pinning has a fallback, a catch-all path, an unpinned
   subdomain, or a bypass, that a network attacker can steer the app onto. Partial or bypassable pinning
   leaves the unpinned path open.

6. **Confirm and record.** Confirm by interposing on a network you control and reading or altering a sensitive
   connection the app treats as secure, on a test device with a test account. Kill the lead if every sensitive
   connection validates strictly, refuses cleartext, and pins where required with no bypassable fallback, or if
   the interception requires a trust the attacker cannot obtain. Record the connection, the accepted network
   position, the trust gap, and the interception observed, or set a `kill_reason`.

## Where transport trust leaks

- **The accepted network position is the finding.** A secure connection is expected; the bug is one the app
  treats as secure that an intercepting position can read or alter. Name the position and how it is accepted.
- **User-added anchors defeat validation.** An app that trusts user-added or profile-added authorities accepts
  a certificate an intercepting proxy presents, so validation passes against the wrong anchor.
- **Cleartext and fallback expose traffic directly.** A sensitive connection that uses or falls back to
  cleartext is readable regardless of certificate handling, and mixed content leaks the insecure part.
- **Partial pinning leaves an open path.** Pinning some connections but not others, or an unpinned subdomain
  or catch-all, lets a network attacker steer the app onto the unpinned path.
- **Bypassable pinning is not pinning.** A pin with a fallback to system validation, or one that can be
  disabled by a setting or a downgrade, does not resist an interposed trusted-but-wrong certificate.

## Worked example (a confirm and a kill)

> **Confirm.** The app pins its primary API connection but makes a sensitive secondary connection to an
> unpinned host that validates only against system and user-added anchors. Interposing on a controlled network
> with a user-added anchor lets that secondary connection be read and altered, exposing data the app treats as
> secure, on a test device. **Confirmed** transport trust gap through partial pinning and user-anchor trust,
> `high`, remediation = pin every sensitive connection to a known certificate or key, trust only system
> anchors for those connections, refuse cleartext and fallback, and remove any unpinned sensitive path.
>
> **Kill.** Every sensitive connection validates fully against system anchors only, ignores user-added
> authorities, refuses cleartext with no fallback, and pins to a known key with no bypassable path. An
> interposed certificate is rejected on every sensitive connection and no traffic is readable. **Killed**,
> `kill_reason` = "all sensitive connections validate strictly against system anchors, refuse cleartext, and
> pin without a fallback; the intercepting position is rejected everywhere it matters."

## Rationalizations to reject

- *"We use HTTPS everywhere."* -> HTTPS validates against whatever anchors the app trusts; if it trusts
  user-added authorities or loosens validation, an interposed certificate still passes.
- *"The main connection is pinned."* -> Coverage is the point; a sensitive secondary or fallback connection
  left unpinned is the path a network attacker takes, so check every connection, not the primary.
- *"Only a rooted device trusts extra anchors."* -> A device profile or a user can add an anchor without root;
  confirm the app ignores user-added anchors for sensitive connections rather than assuming they cannot exist.
- *"Cleartext is only for non-sensitive calls."* -> Confirm which data each cleartext connection carries and
  that none falls back to cleartext; a fallback exposes the traffic the pin was meant to protect.
- *"Pinning can be turned off for debugging."* -> A pin with a switch or a fallback is bypassable; confirm the
  production path cannot be downgraded onto system validation.

## Executing this in practice

You need every connection the app makes with its sensitivity, the trust anchors and validation each uses,
whether any use or fall back to cleartext, and which are pinned with what fallback. For each sensitive
connection, decide whether an intercepting network position is rejected. Reading the trust configuration and
observing the app's behavior settles most leads; interposing on a controlled network and reading or altering a
sensitive connection on a test device settles the rest.

## Related

- `auditing-tls-and-certificate-validation` - the general certificate-validation audit, of which mobile
  pinning and anchor trust are the app-side application.
- `auditing-mobile-webview-bridge-exposure` - content loaded over a connection an attacker can intercept
  reaches the WebView bridge, so a transport gap feeds that exposure.
- `hunting-mobile-secret-and-storage-exposure` - a token read from intercepted traffic joins the secret
  exposure that skill pursues from the storage side.
- `auditing-mobile-backend-and-firebase-exposure` - an intercepted or unpinned backend connection reveals the
  backend endpoints and keys that skill audits.
- [FINDING-SCHEMA.md](../../FINDING-SCHEMA.md) - source = the intercepting network position the app fails to
  reject, sink = the connection the app treats as trusted, evidence = reading or altering a sensitive
  connection on a controlled network with a test device.