Tracer Proxy

Download on the App Store

How on-device network inspection works on iOS

By Ryan · Published 2026-07-28

An App Store app can read the decrypted HTTPS traffic of every other app on the same iPhone. It needs two things Apple ships and documents: a NetworkExtension provider that publishes a device-wide proxy setting, and a root certificate you install and trust yourself. No jailbreak, no tethered Mac, no server in the middle. What follows is the mechanism at the level you'd need to judge one — taken from Tracer's implementation, including the parts that are ugly — and the places it stops working. If you only want to run a capture rather than build one, how to see network requests on your iPhone is the practical version. This is also the methodology page for our teardowns.


Two iOS facilities, stacked

On-device network inspection is a local HTTP proxy plus a locally generated certificate authority. The proxy receives traffic because a NetworkExtension provider publishes NEProxySettings pointing at loopback; it can decrypt what arrives because the phone trusts a root whose signing key is on the phone. Neither half is a private API.

app (URLSession / CFNetwork / WebKit)
  -> system proxy setting published by the tunnel
  -> a listener on 127.0.0.1, inside the extension process
  -> TLS terminated with a leaf minted for the requested host
  -> a second TLS connection, opened by the extension, to the real origin

Everything below is a consequence of that shape. The most important one: the boundary of what an inspector can see is clients that honour proxy settings, not packets.

The VPN is a delivery vehicle for one setting

The VPN profile iOS asks you to allow does not tunnel your packets anywhere. On a device with no MDM enrollment, publishing NEProxySettings from a NetworkExtension provider is the only way to set an HTTP proxy that applies device-wide, including on cellular — so the tunnel exists to carry that setting and nothing else. Tracer's provider claims a single route inside 198.18.0.0/15, the range RFC 2544 reserves for benchmarking, matches every hostname by declaring the empty string as a match domain — the empty string being a suffix of every hostname — and never reads packetFlow at all.

Interception starts at the CONNECT line, not at SNI

A proxy-aware client names the HTTPS host it wants in cleartext, before any TLS happens, as a CONNECT request: CONNECT api.example.com:443 HTTP/1.1. That request line is where an on-device inspector learns the hostname. It never has to parse the SNI extension of a ClientHello, which is the part people expect to be the hard bit.

The proxy answers HTTP/1.1 200 Connection Established, and then — instead of relaying bytes — takes the connection over itself: a TLS server presenting a leaf certificate for that host, an HTTP/1.1 parser behind it, and a fresh TLS connection opened upstream to the real origin. Three consequences show up in every exchange you capture:

The certificate the phone is handed, in full

The inspector mints a fresh leaf certificate for each hostname at the moment of the first CONNECT to that host, signs it with the on-device root, and caches it for the rest of the session. Here is one, generated on 2026-07-28 by compiling Tracer's certificate encoder outside the app — with a software key standing in for the Secure Enclave so the bytes could be dumped — and read back with openssl x509 -text:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1785261337366 (0x19fa9de2b16)
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN=Tracer CA 4f2a91bc, O=Tracer, C=US
        Validity
            Not Before: Jul 28 17:50:37 2026 GMT
            Not After : Aug  4 17:55:37 2026 GMT
        Subject: CN=api.example.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Subject Alternative Name:
                DNS:api.example.com

That is 414 bytes on the wire. The root above it is 432. What's absent is more interesting than what's present: no Authority Key Identifier, no CRL distribution point, no OCSP responder, no Certificate Transparency SCTs. iOS builds the chain by matching the leaf's issuer name against the installed root byte for byte, there is nothing to revoke against, and Apple's CT policy applies to publicly trusted CAs — not to a root the user installed. A certificate a browser would reject instantly on the public web is accepted here because the trust decision was made by hand, once, in Settings.

The rest of the shape, per RFC 5280: one SAN, exactly the host from the CONNECT line, with no wildcards and one certificate per hostname; seven-day validity backdated five minutes to absorb clock skew; a serial number that's milliseconds since the epoch, so it tells you when the leaf was minted. Every leaf on one install shares a public key — only the subject and SAN differ. The root above them is CN=Tracer CA <8 hex digits>, O=Tracer, C=US, CA:TRUE, valid ten years, with a hex suffix that's random per install, so no two users share a subject name or a key.

The signing key is in the Secure Enclave. The key the TLS server uses is not.

Tracer's CA private key is a P-256 key generated inside the Secure Enclave and marked non-exportable. The leaf certificates it signs use a different key: an ordinary CryptoKit P-256 key stored as PEM in a keychain item shared between the app and the extension.

That split isn't a shortcut, it's forced. The Secure Enclave signs digests on request; it does not hand a private key to anything, and a TLS server needs actual key bytes to complete a handshake. So the enclave key does the one job that has to be unforgeable — signing certificates — and a software key does the job that requires possession.

The security property survives the split intact. Someone who extracts the leaf key from the keychain gets a key pair no phone trusts, because every certificate presented during interception has to carry a signature the CA key produced, and that key cannot be copied off the device. Not by an attacker, not by us. It's stored AfterFirstUnlockThisDeviceOnly, so it never appears in a backup and never migrates to a new phone.

iOS won't tell an app whether its own root is trusted, so you ask sideways

There is no iOS API that lists the root certificates a user has installed, and none that reports whether a given root has full trust enabled. An app that needs to know has to find out by experiment: mint a throwaway leaf under its own CA and ask the system trust evaluator whether this device accepts it.

Tracer mints one for a synthetic hostname that never touches the network, builds a SecPolicyCreateSSL policy for that same name, calls SecTrustSetNetworkFetchAllowed(trust, false) so a revocation fetch can't block the check, and runs SecTrustEvaluateWithError. The trust store is device-wide and honours the Certificate Trust Settings toggle, so an app-side evaluation faithfully predicts what the tunnel's TLS server will see. Three states come back: notInstalled, installedNotTrusted — which covers both "never installed" and "installed but full trust is off", because iOS gives no way to tell those apart — and trusted.

The same probe runs inside the tunnel, once per session, against the first real leaf it mints, and writes the verdict to the system log. That turns the useless bug report — "it doesn't work" — into one of two specific answers: the device trusts a certificate the app just rejected, so the app pins; or the device rejects it too, so the trust setup is broken.

Getting the root onto the phone has its own piece of iOS trivia. The system only starts the "Profile Downloaded" flow when a .mobileconfig arrives over HTTP in Safari, so the app runs a loopback listener on an ephemeral port, serves the profile as Content-Type: application/x-apple-aspen-config with a com.apple.security.root payload, and opens that http://127.0.0.1:… URL in an in-app Safari view. Installing and trusting are then two separate decisions in two different places in Settings — the step people skip, and the reason the probe above exists.

What ends up in the record, and where each field comes from

Every field an inspector shows you is derived from bytes that crossed the connection, not from anything the app declared about itself. In Tracer, per exchange:

A capture isn't complete by default: Apple system hosts and Tracer's own traffic are hidden so they don't drown the session, and both are toggles. The second filter is mostly a hostname list, with one case it can't be — Tracer's subscription SDK talks to RevenueCat and so do plenty of apps you'd want to inspect, so those requests are told apart after decryption by a header the calling app sets to identify itself. Turn the first toggle off before drawing conclusions about iOS system behaviour.

Where certificate pinning actually stops you

A pinned app compares the certificate it was handed against a copy it ships with, and rejects the inspector's leaf during the handshake. You get the hostname, the IP, byte counts and a connection marked failed, with zero exchanges under it — and the app usually shows an error until you stop capturing. Pinning doesn't conceal traffic; it declines to produce any.

Pinning is per-host, not per-app, and most apps that pin, pin their own API and leave their analytics and advertising SDKs on the system trust store — those still decrypt in the same session. There is no way around a pin from any proxy, on a phone or a desktop; SSL Kill Switch, Frida and a re-signed IPA all need a jailbreak.

Three things that never reach the proxy at all

Proxy-based inspection sees HTTP conversations from proxy-aware clients. That covers URLSession, CFNetwork and WebKit — most of what an iOS app does — and it structurally excludes the following.

The same trick as Charles, with the trust boundary moved

On-device inspection and desktop interception are the same operation performed at a different point: terminate TLS with a certificate the client trusts, read the plaintext, re-encrypt upstream. Charles, Proxyman, mitmproxy, Burp and Tracer all do exactly this. Moving the middle onto the phone changes three things.

The comparison of the five on-device inspectors covers which of them make which trade.

Four questions to ask of any inspector you install

Trusting a root certificate grants whoever holds the matching private key the ability to impersonate any website to your phone, for as long as the root stays trusted. That grant is the thing to reason about — not the VPN icon in the status bar — and four questions settle it. Tracer's answers are below each one, and the same four are worth putting to anything else you install.

FAQ

How does an iPhone app decrypt HTTPS without a jailbreak?

It terminates TLS itself using a certificate authority whose root you installed and trusted. A NetworkExtension provider publishes a device-wide proxy setting pointing at a listener inside the extension; that listener answers each CONNECT, presents a leaf certificate it mints for the requested hostname, and opens its own TLS connection to the real server. Both halves — NEProxySettings and a user-installed root — are documented iOS features available to App Store apps.

Does on-device network inspection capture packets?

No. A proxy-based inspector sees HTTP requests and responses from proxy-aware clients, not packets. DNS lookups, QUIC and HTTP/3, push notifications over APNs, TLS handshake bytes and retransmissions never reach it. Packet capture on iPhone still requires a Mac running rvictl with Wireshark or tcpdump.

Can an iOS app see network traffic from other apps?

Yes, if those apps use proxy-aware networking. Proxy settings published by a NetworkExtension provider apply to the whole device, so URLSession, CFNetwork and WebKit traffic from every app is routed through the inspector while its tunnel is running. Apps that bring their own QUIC stack or open raw sockets bypass it.

How do you tell certificate pinning apart from a broken certificate install?

Ask the device to evaluate a certificate the inspector just minted. If this device trusts that leaf and an app still rejected it, the app is pinning; if the device rejects it too, the root is not installed or full trust was never enabled. iOS provides no API to enumerate user-installed roots, so a trust evaluation against a throwaway leaf is the only way to get the answer.

Is it safe to install a root certificate on your iPhone?

It is safe only to the extent that the private key is unreachable and the traffic stays on the device. A trusted root is the ability to impersonate any website to your phone for as long as it stays trusted, so the questions that matter are where the key was generated, whether the same key ships to every user, whether captured bodies leave the device, and how you remove the profile afterwards.


None of this is exotic — it's a proxy, a certificate, and one setting that iOS will only accept from a NetworkExtension. Judge any inspector you install on the three things that follow from that: where its CA key was generated, whether the bytes it captures leave the phone, and whether its tunnel is scoped to a capture. Tracer is the implementation described above — the enclave-held CA, the session-scoped tunnel, the trust probe — and it runs on the phone, which is the one thing a desktop proxy can't do. Once the requests are in front of you, the teardowns are what tends to be in them.