Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/klzgrad/naiveproxy/llms.txt

Use this file to discover all available pages before exploring further.

NaïveProxy is built on a core insight: rather than designing a new protocol that attempts to look like browser traffic, it is browser traffic — generated by the same Chromium network stack that ships inside Chrome. This page explains how each layer of the design contributes to censorship resistance, and where the boundaries of that protection lie.

Architecture

NaïveProxy is a two-component system designed to place an undetectable proxy inside ordinary HTTPS communication.
[Browser → Naïve client] ⟶ Censor ⟶ [Frontend → Naïve server] ⟶ Internet
The Naïve client runs on the user’s device. It acts as a local SOCKS5 (or HTTP/transparent redirect) proxy and uses Chromium’s network stack to open HTTP/2 CONNECT tunnels to the frontend server — producing exactly the same wire-level behavior as Chrome browsing a website. The frontend server runs on a remote host and has two jobs:
  1. Serve real web content to any visitor that does not present valid credentials, making the server look like an ordinary website to censors and active probers.
  2. Route authenticated requests to the Naïve server backend, which operates as a forward proxy and applies the padding protocol to tunneled streams.
In practice, Caddy with the NaïveProxy forwardproxy fork combines both roles into a single process. HAProxy can also serve as the frontend with the Naïve server running separately.

TLS Fingerprinting Defense

One of the most reliable ways to identify proxy software is by profiling the TLS ClientHello message — the opening handshake that every TLS client sends before any encrypted data is exchanged. Each TLS library has a distinctive fingerprint: a specific ordering of cipher suites, a characteristic set of extensions, particular elliptic curve preferences. NaïveProxy defeats this entirely by reusing Chrome’s BoringSSL. The client binary is a stripped-down Chromium build. Its TLS ClientHello is generated by the same code path, with the same parameters, compiled from the same source as the Chrome release it tracks. There is no synthetic fingerprint — a censor observing the handshake sees a genuine Chrome client.
This defense is only as strong as the currency of the binary. Always use the latest NaïveProxy release so your TLS fingerprint matches the Chrome version currently in widespread use. See Introduction for download links.

Traffic Classification Defense

Even without inspecting the TLS handshake, deep packet inspection systems can classify traffic by its statistical properties: the timing, direction, and size distribution of packets within a session. Proxy protocols often produce distinctive patterns — a single long-lived stream, unusual request sizes, or a burst of small packets at session start. NaïveProxy mitigates this through HTTP/2 multiplexing, which is Chrome’s native connection model. Multiple logical streams share a single TCP connection, producing the same interleaved packet patterns as a browser with multiple tabs open. The initial preamble — the sequence of SETTINGS, WINDOW_UPDATE, and HEADERS frames at connection start — matches Chrome’s exact behavior. The padding protocol (described below) further flattens the distinctive length spikes produced by the initial proxy handshakes, blending them into the background of normal browser traffic.

Active Probing Defense

A sophisticated censor does not rely only on passive observation. It may send its own probe requests to a suspected proxy server to confirm its function — a technique called active probing. A server that responds to unauthenticated requests with proxy behavior, or with an unusual error, reveals itself. NaïveProxy defeats active probing through application fronting: the frontend server is a real web server that responds to normal HTTP(S) requests with real web content. Only requests that include a valid HTTP Authorization header are routed to the Naïve proxy backend. Everything else receives an ordinary web response. The probe_resistance directive in Caddy’s forward_proxy block enables this behavior. A censor sending probe requests without credentials sees a legitimate website — there is no detectable proxy surface.

Length-Based Traffic Analysis Defense

Even with multiplexing and fingerprint matching, the initial frames of a new CONNECT tunnel have a predictable structure: the TLS handshake, the HTTP/2 SETTINGS exchange, and the CONNECT request itself produce characteristic length spikes that can distinguish proxy sessions from normal browsing. NaïveProxy addresses this with a padding protocol applied to the first 8 reads and writes of each CONNECT tunnel. See the full padding protocol specification for the complete struct layout and design rationale. The three padding mechanisms are:

Proxy Payload Padding

The first 8 reads and writes of every bidirectional CONNECT stream are padded. Each padded write carries the original data length (2 bytes), a random padding size byte, the original data, and zero-fill padding:
struct PaddedData {
  uint8_t original_data_size_high;  // original_data_size / 256
  uint8_t original_data_size_low;  // original_data_size % 256
  uint8_t padding_size;
  uint8_t original_data[original_data_size];
  uint8_t zeros[padding_size];
};
The value of 8 is chosen to cover the full initial handshake sequence for both client and server (TLS exchange, H2 SETTINGS, HEADERS, SETTINGS ACK). Subsequent reads and writes are unpadded to minimize performance overhead, since later packet lengths are considered less informative to classifiers.

H2 HEADERS Frame Padding

The CONNECT request and response HEADERS frames are short by normal browser standards and would stand out by length alone. A padding header is added to each — filled with pseudo-random symbols that resist Huffman compression and HPACK indexing. The padding length is randomly distributed in [16, 32] bytes for requests and [30, 62] bytes for responses, bringing frame sizes into a range typical of real browser requests.

H2 RST_STREAM Frame Padding

NaïveProxy tends to generate more RST_STREAM frames per session than a typical browser, which is an unusual behavioral signal. To mask this, an END_STREAM DATA frame with total length uniformly distributed in [48, 72] bytes is prepended to each RST_STREAM frame, making the combined frame appear to be a HEADERS frame to passive observers.

Opt-In Padding Protocol

The padding protocol is negotiated via the presence of a padding header in the CONNECT request and response. This design preserves interoperability in both directions:
  • A NaïveProxy client connecting to a standard HTTP/2 proxy (one that does not understand the padding protocol) simply receives no padding header in the response and falls back to unpadded operation.
  • A NaïveProxy server receiving a CONNECT from a standard HTTP/2 client (for example, a regular Chrome browser) sees no padding header in the request and serves it without padding.
This means you can use NaïveProxy selectively within a larger infrastructure without breaking non-NaïveProxy clients or servers.
Fast Open limitation: The first CONNECT request to a server cannot use Fast Open (sending payload before the server response). At that point, the client does not yet know whether the server supports the padding protocol — it learns this from the first response. Sending unpadded payload speculatively could reveal the session’s true nature if the server does not support padding, so Fast Open is deferred until the server’s capability is confirmed.

Post-Quantum Key Agreement

NaïveProxy enables X25519Kyber768 post-quantum key agreement by default, matching Chrome’s current behavior. This protects against future “harvest now, decrypt later” attacks where an adversary records encrypted traffic today intending to decrypt it once quantum computers become available. If you need to disable post-quantum key exchange — for example, because it causes compatibility issues with certain network middleboxes — you can pass the --no-post-quantum flag to the naive binary.
If your network path performs poorly over TCP — high latency, packet loss, or aggressive traffic shaping — try switching to the QUIC transport by using quic:// instead of https:// in your proxy URI. QUIC (HTTP/3) provides its own congestion control and loss recovery independent of TCP, which can significantly improve performance on degraded connections. See the Quickstart for configuration details.

Build docs developers (and LLMs) love