ProxyZip / Docs / Architecture

Architecture

ProxyZip is a Cargo workspace. The engine is a set of small crates with one job each, and two front-ends (a CLI and a Tauri desktop app) sit on top.

                    ┌───────────────────────────────┐
                    │  apps/desktop   proxyzip-cli  │   front-ends
                    └───────────────┬───────────────┘
                                    │
  ┌────────────┬────────────┬───────┴──────┬────────────┬────────────┐
  │ proxyzip-  │ proxyzip-  │ proxyzip-    │ proxyzip-  │ proxyzip-  │
  │ export     │ sysproxy   │ certinstall  │ codec      │ rules      │
  │ HAR, cURL  │ OS proxy   │ OS trust     │ decoding   │ rule model │
  └─────┬──────┴────────────┴──────┬───────┴────────────┴─────┬──────┘
        │                          │                          │
        ▼                          ▼                          ▼
  proxyzip-core  ◀────────────  proxyzip-proxy  ─────────▶  proxyzip-tls
  Flow, store,                  tokio + hyper               CA, leaf certs,
  events                        engine                      key log

Crates

Crate Responsibility
proxyzip-core The Flow model, a bounded in-memory FlowStore, and a broadcast event bus.
proxyzip-proxy Listener, CONNECT handling, TLS interception, HTTP/1.1, HTTP/2, HTTP/3 (QUIC), WebSocket relay, upstream client.
proxyzip-tls Root CA creation and persistence, per-host leaf certificates with an LRU cache, TLS key logging.
proxyzip-rules The rule model (rules.json), request matching, and the pure functions that decide what happens to a request and rewrite it. No I/O.
proxyzip-script QuickJS runtime for user scripts and plugins: worker threads, time and memory limits, hashing helpers implemented in Rust, the plugin registry.
proxyzip-openapi OpenAPI 3 loading, example generation, mock-server generation and traffic-against-specification validation.
proxyzip-suite Replay suites: model, runner, assertions, JUnit reports and Postman import.
proxyzip-mcp Model Context Protocol server (JSON-RPC over stdio or authenticated loopback HTTP) that exposes captured traffic to AI assistants.
proxyzip-codec Content-encoding removal (gzip, deflate, Brotli, Zstandard), classification, pretty-printing, and structured viewers: Protobuf, gRPC, MessagePack, SSE, GraphQL, jq.
proxyzip-export HAR 1.2, .pzsession sessions, CSV, Postman collections and code generation in 14 languages.
proxyzip-sysproxy Enable, disable and read the operating-system proxy (networksetup, WinINet registry, gsettings and KDE).
proxyzip-certinstall Trust the CA in OS and application stores; Firefox, Android and Java setup; the iOS profile; terminal environment for every shell.
proxyzip-cli The proxyzip binary.
apps/desktop/src-tauri Tauri 2 shell: owns the proxy, exposes commands, streams events to the UI.
apps/desktop/src Svelte 5 + TypeScript + Tailwind CSS interface.

Request lifecycle

client ──TCP──▶ listener (tokio)
                  │  hyper auto-detects HTTP/1.1 or HTTP/2, upgrades enabled
                  ├─ absolute-form request ───────────────────────────────▶ forward()
                  └─ CONNECT host:port → 200 → upgraded stream
                        │ peek the first bytes
                        ├─ TLS handshake and host allowed by the SSL proxying policy
                        │     read SNI and ALPN → mint a leaf certificate (cached)
                        │     → terminate TLS (h2 only if the client offered it)
                        │     → hyper ──────────────────────────────────▶ forward()
                        ├─ plain HTTP inside the tunnel ─────────────────▶ forward()
                        └─ excluded host or unknown protocol → raw byte tunnel

forward():
  resolve target (reverse proxy listeners use their fixed target) → rules.plan(request)
  → record the request (unless "record only" excludes it)
  → block ........ synthetic status, or drop the connection
  → map local .... answer from disk
  → map remote / rewrite request / breakpoint (pause until the user decides, with edits)
  → latency and upload pacing for throttling
  → upstream hyper client (rustls, bundled + native roots, ALPN h2 / http/1.1; DNS overrides and the
    upstream proxy are applied by the connector when the TCP connection is opened)
  → record the response head
  → header-only rewrite: stream the body back through a tap that keeps a bounded copy
  → body rewrite or response breakpoint: hold the whole response, decode, edit, then send
  → optional bandwidth pacing → on completion record body and timing; on client abort mark the flow as failed
  WebSocket: HTTP/1.1-only upstream client, both sides upgraded, frames relayed and recorded

HTTP/3: Alt-Svc is learned from responses (and hidden from clients); servers that offered h3 are reached
  over QUIC (quinn + h3), falling back to TCP when the connection fails

extra listeners (started and stopped as the rules change):
  reverse proxy port ─▶ forward() with a fixed target
  reverse proxy UDP port (HTTP/3) ─▶ the same forward(), with the request body collected first
  SOCKS5 port ───────▶ handshake ─▶ the same path as CONNECT

Design decisions

  • Each side negotiates independently. The client-to-proxy protocol (HTTP/1.1 or HTTP/2) does not constrain the proxy-to-server protocol. hyper-util picks the wire version per upstream connection.
  • One leaf key for all hosts. Generating a key pair per host dominates interception latency. A single key per CA instance, with certificates cached per host, keeps the first request to a new host fast.
  • Bodies stream through. The proxy never buffers a response before forwarding it. The tap keeps a copy up to the configured limit, so event streams and large downloads behave normally.
  • Errors are flows. TLS rejection by the client, DNS failures, upstream TLS errors and client aborts all become failed flows with an explanation, because that is when you need the tool most.
  • WebSocket compression is disabled (Sec-WebSocket-Extensions is removed) so frames can be decoded and recorded.
  • UI event batching. The desktop shell coalesces store events every 60 ms. If the UI falls behind the broadcast buffer it receives a resync signal and reloads the list.
  • Rules are data, the engine is pure. proxyzip-rules turns a request description into a plan (block, map, rewrite operations, breakpoint, throttle profile) without touching the network, so every rule is covered by fast unit tests. The proxy executes the plan and is tested end to end.
  • Rules apply live. Settings hold the rule set behind an Arc, replaced atomically; a request sees one consistent snapshot. The connector reads DNS and upstream-proxy rules at connect time, so no client has to be rebuilt.
  • Breakpoints are a rendezvous. A paused flow waits on a one-shot channel held by the BreakpointHub. The UI resolves it with a decision. If the client disconnects while paused, the entry is dropped and the flow is marked failed.
  • One request path for every protocol. forward() is generic over the request body, so HTTP/1.1, HTTP/2 and HTTP/3 requests share rules, scripts, breakpoints and recording. Upstream bodies are boxed into one type so a hyper response and a QUIC stream are interchangeable.
  • Plugins are scripts with more entry points. They reuse the QuickJS sandbox and its limits; the host only adds decode and generate calls and merges hook plugins into the rule set at apply time (never into rules.json).
  • MCP is a thin layer. The server implements JSON-RPC directly and reuses the filter, codec, export and rules crates. The desktop app and proxyzip start supply the rule backend, so edits made by an assistant are applied exactly like edits made in the Tools window.
  • Mocks are rules. A mock server is part of RuleSet; the engine matches host and route and returns an action, and the proxy renders the template (proxyzip_core::template, shared with suites) and answers. A mock with its own port is another extra listener, like a reverse proxy.
  • Specifications are data. proxyzip-openapi turns a spec into the same MockServer the user could have typed, and checks flows with the JSON Schema crate after expanding $refs and translating 3.0 keywords.
  • Private by default. The proxy listens on loopback unless you enable LAN connections. The CA key is stored with owner-only permissions.

Testing

crates/proxyzip-proxy/tests/e2e.rs starts real upstream servers (HTTP, HTTPS with HTTP/2, WebSocket) and drives the proxy with real clients. New proxy behaviour should come with a test there.