Lenswire

← Lenswire

How the pipe works

Apps hit a local VPN. The proxy checks whether it can decrypt HTTPS as HTTP/1.1. If yes — MITM: open the bytes, inspect, send them on. If not — a sealed byte tunnel; you still see the connection in the UI, but not the HTTP payload.

Traffic flows from apps through a local VPN into the Lenswire proxy. The proxy either decrypts HTTPS with MITM for inspection, or relays a sealed byte tunnel when decryption is not possible. Results appear in the Lenswire UI.

iOS Packet Tunnel (utun → hev → SOCKS) · Android VpnService (TUN → tun2socks → SOCKS)

The fork

One gate. Two exits.

Before any decrypt, the proxy decides canMitm. Decrypt off, missing CA, no SNI, session bypass, or ALPN without HTTP/1.1 → runPassthrough. Otherwise → runMitm (Kotlin) / runMITM (Swift).

if (!canMitm) {
  runPassthrough(/* … */, reasonCode = )  // captureMode: tunnel
  return
}
runMitm(/* … */)  // decrypt → inspect → upstream → encrypt back

View canMitm gate · LocalProxyServer ·Android →iOS →

Same pipe. Two exits.

Open the payload — or keep it sealed

MITM calls show as HTTP/1.1 with full headers and body. Pure h2/h3 tunnels get version flags only. A successful downgrade is labeled HTTP/1.1 — the inspectable payload you actually got.

MITM path

We MITM

Terminate TLS with the app → read HTTP/1.1 → talk to the server → encrypt back.

  • HTTP/1.1

    decryptedAndroid →iOS →

    When the client offers http/1.1 (often with h2), we force that ALPN, decrypt request and response, and show full payload in the UI. Overrides land here.

  • WebSocket

    websocket_relay

    After MITM TLS, Upgrade is relayed end-to-end. Frames are not inspected; the host is not added to session bypass.

Passthrough path

We tunnel

runPassthrough relays encrypted bytes as-is. captureMode: tunnel — payload unavailable in the UI.

  • ALPN without HTTP/1.1

    alpn_no_http11Android →iOS →

    ClientHello only offers h2 or h3. We never start MITM — straight tunnel. UI shows HTTP/2 or HTTP/3, not a decrypted call.

  • QUIC

    quic

    UDP/QUIC is not terminated for MITM. Those flows stay outside the HTTP decrypt path — connection may appear, payload does not.

  • Session bypass

    mitm_bypassed

    Trust fail, pinning-style TLS reject, or non-HTTP/1.1 after handshake → that connect closes; the host stays tunnel-only until you stop VPN. UI shows the bypass cause.

  • No request after handshake

    mitm_no_request

    Timeout with no HTTP → close and bypass so retries tunnel. Empty EOF → close without bypass, so speculative CDN connects can still MITM next time.

  • Decrypt off / no CA / no SNI

    passthroughAndroid →iOS →

    MITM impossible up front. Still captured as a tunnel via runPassthrough — not a decrypted call.

Boundary in one line: decryptable payload is HTTP/1.1 after CA trust. Pure HTTP/2 and QUIC tunnel. WebSocket is relay-only (no frame inspect). Certificate pinning is not bypassed. Failures stay fail-open with a reason code.

Free and open source

MIT licensed. Capture and decrypt stay on your device.