ZeroProxy is an isolation web proxy written in Rust, JavaScript, and Go. It runs untrusted websites inside a virtualized browser tab while terminating TLS and framing HTTP/1.1 and HTTP/2 on the client inside a WebAssembly Service Worker kernel. The Go server acts as an untrusted byte relay that forwards encrypted streams to a SOCKS5 upstream (such as Tor with per-tab stream isolation) or a direct network connection.
ZeroProxy is structured as a set of embeddable Go packages, Rust transformation and transport crates, and a frozen browser runtime surface, backed by a standalone CLI server (cmd/zeroproxy-server).
Client-side rewrite, policy, and TLS state live in the browser tab and Service Worker. The Go relay forwards opaque byte streams without holding target URL keys or terminating target TLS sessions.
flowchart LR
Page["Target Page (Membrane + OXC Rewriter)"] --> SW["Service Worker (Request Classifier)"]
SW --> Kernel["Rust WASM Kernel (rustls + HTTP/1.1 & HTTP/2)"]
Kernel --> Pipe["WebSocket + yamux Multiplexer"]
Pipe --> Relay["Go Relay Server (pkg/httphost)"]
Relay --> Egress["Tor SOCKS5 or Direct Target"]
- Client-side route encryption: The launcher encrypts the target URL into
/zp/p/<ciphertext>#k=<key>&server=.... The AES-256-CBC and HMAC-SHA256 keys are derived with HKDF from the#k=URL fragment, which the browser never sends over HTTP. - Service Worker request interception: The Service Worker classifies every fetch and routes target traffic into the Rust WebAssembly transport kernel (
zp-kernel.wasm), which negotiates TLS viarustlsand speaks HTTP/1.1 or HTTP/2 over a multiplexedyamuxWebSocket tunnel (/zp/ws-pipe). - HTML, CSS, and JS transformation: Before responses reach the page,
zp-enginerewrites HTML documents (lol_html), CSS stylesheets (swc_css_parser), and JavaScript ASTs (oxc) so accesses towindow,location,document, and dynamic code evaluation pass through the page membrane. - Per-site storage and circuit isolation: The browser membrane partitions
localStorage,sessionStorage, IndexedDB, Cache Storage, BroadcastChannel, OPFS, and cookies by virtual origin. When paired with Tor, each tab authenticates to SOCKS5 with distinct credentials so tabs do not share circuits.
Each layer exposes an explicit public contract so host applications can embed the Go relay, the Rust rewrite and dialer engines, or the browser runtime independently.
pkg/httphost: Mounts the/zp/*HTTP handler and/zp/ws-piperelay into anynet/httpserver.NewServer(cfg Config) (*Server, error)validatesTransport(TransportAuto,TransportSOCKS5,TransportDirect) andTargetPolicy(TargetPolicyDefault,TargetPolicyAllowPrivate,TargetPolicyBlockPrivate) at construction time and returns a*Serverimplementinghttp.Handlerandio.Closer.pkg/wtproxy: Optional HTTP/3 WebTransport datagram and stream relay mounted at/zp/wt. Constructed viawtproxy.New(cfg Config) (*Listener, error).pkg/rtcgw: Optional ephemeral-credential TURN server (rtcgw.NewTURNServer) and501 RTC_GATEWAY_UNAVAILABLEsignaling stub (rtcgw.Handler) for relay-only WebRTC media.
package main
import (
"log"
"net/http"
"github.com/gosuda/zeroproxy/pkg/httphost"
)
func main() {
srv, err := httphost.NewServer(httphost.Config{
WebDir: "dist/web",
Transport: httphost.TransportSOCKS5,
SocksAddr: "127.0.0.1:9050",
TargetPolicy: httphost.TargetPolicyBlockPrivate,
})
if err != nil {
log.Fatal(err)
}
defer srv.Close()
log.Fatal(http.ListenAndServe(":8080", srv))
}crates/zp-engine: Host-targetable facade over JavaScript AST rewriting, streaming HTML transformation, CSS rewriting, and source map composition. Uses a single typedScriptKindenum (Classic,Module,Inline,Eval,EventHandler) acrossTransformEngine::rewrite_script,rewrite_script_patches_json,compose_source_map, andcompose_source_map_chained; string parsing stays at the WebAssembly boundary viaparse_script_kind.crates/zp-css: Rewritesurl(...)and@importspecifiers in stylesheets viarewrite_css(source, base_url, control_prefix, proxy_origin) -> Result<CssRewriteOutput, CssError>.crates/zp-kernel-bundle: Browser transport kernel. Its host-portable dialer contract lives inzp_kernel_bundle::dial(Dialer,AsyncIo,TargetAddr,DialError,set_dialer,clear_dialer,injected_dialer) and compiles on non-WASM targets; the defaultRelayDialerinkernel::transport::dialeriswasm32-only.crates/zp-page-rt: Zero-dependencyno_stdWebAssembly runtime (zp-rt.wasm) backing synchronous URL interning and classification in the page membrane. Packs a 4-bit class and 28-bit handle ((class << 28) | handle) inbulk_intern_and_classify.
use zp_engine::{DocCtx, ScriptKind, TransformEngine};
let mut engine = TransformEngine::new();
let ctx = DocCtx::new("https://example.com/index.html", "http://proxy.localhost:8080");
let rewritten_js = engine.rewrite_script(
"const href = window.location.href;",
ScriptKind::Module,
&ctx,
)?;
let rewritten_html = engine.transform_html(
"<!doctype html><a href=\"/docs\">Docs</a>",
&ctx,
)?;
# Ok::<(), zp_engine::EngineError>(())web/zp-core.jsandweb/zp-core.d.ts: FreezeglobalThis.ZPas the public browser runtime contract (URL classification, share URL encryption, binary v2 envelope encoding/decoding, and host bridge installation viaZP.configureBridge). Internal helpers are passed to first-party bundles throughglobalThis.__zp_internal, which consumers capture into a closure and delete fromglobalThis.window.__zp_realm(ZPRealmHandle): Frozen per-document handle created byweb/membrane/exposingtabId,entryId,getVirtualURL(),getBaseURL(),setBridge(bridge), anddispose().
// Install an optional host bridge in the page realm
window.__zp_realm.setBridge({
classifyUrl(rawUrl, baseUrl) {
return null; // Return null to fall back to the default classifier
},
onNavigate(nextVirtualUrl) {
console.log("Virtual navigation:", nextVirtualUrl);
},
});All proxy traffic and static assets stay under /zp/*. Retired legacy routes (/zp/api/fetch, /p/*, /sw.js, /index.html, /__zp/*, /zp/assets/*, /zp/api/sourcemap, /zp/api/rtc/signal) return 403 POLICY_BLOCKED.
| Path | Method | Purpose |
|---|---|---|
/zp/, /zp/index.html |
GET |
Launcher UI (web/index.html). |
/zp/sw.js |
GET |
Service Worker script (Service-Worker-Allowed: /zp/). |
/zp/Asset/<name> |
GET |
Allowlisted static and WASM assets (zp-core.js, runtime-prelude.js, worker-prelude.js, zp-page-bundle.js, zp-bundle.js, zp-bundle.wasm, zp-kernel.js, zp-kernel.wasm, zp-rt.wasm, favicon.ico, manifest.webmanifest). |
/zp/p/<opaque> |
GET |
Encrypted target navigation route handled by the Service Worker; returns a minimal bootstrap shell on cold load. |
/zp/ws-pipe |
GET (Upgrade: websocket) |
Multiplexed yamux + SOCKS5 byte pipe (Sec-WebSocket-Protocol: zp-yamux-v1). |
/zp/api/v2/fetch |
POST, GET |
Direct-mode fetch fallback using the v2 binary envelope ([u32 LE head length][JSON head][raw body], application/zp-envelope). Disabled in relay mode. |
/zp/api/sync-fetch, /poll, /result |
GET, POST |
Synchronous XHR rendezvous between page worker, Go server, and Service Worker. |
/zp/api/sourcemaps |
GET |
Chained source map lookup (?u=<target>&k=<kind>). |
/zp/wt |
CONNECT (HTTP/3) |
Optional WebTransport relay endpoint. |
/zp/rtc/turn-credentials |
GET |
Issues short-lived HMAC-SHA1 TURN credentials when the TURN gateway is enabled. |
/zp/rtc/offer |
* |
Reserved WHIP/WHEP stub (501 RTC_GATEWAY_UNAVAILABLE). |
/zp/healthz |
GET |
Health probe returning {"ok":true}. |
- Rust with the
wasm32-unknown-unknowntarget defined inrust-toolchain.toml. wasm-bindgen-climatching thewasm-bindgenversion pinned inCargo.lock.- Go matching
go.mod, Node.js LTS, and npm. - A Chromium or Firefox browser with Service Worker and WebAssembly support.
- Tor (optional), required when running with anonymous egress and per-tab stream isolation.
Warning
Confirm that Rust, wasm-bindgen-cli, and Go are installed before running npm run build on a machine that already has a working dist/ directory. The build script cleans dist/ before compiling and will remove your runnable bundle if a required compiler is missing.
npm ci
npm run buildThis compiles the Go server binary and WebAssembly bundles into dist/:
dist/zeroproxy-server Go relay server (.exe on Windows)
dist/web/ Built browser assets served to clients
dist/web/Asset/zp-bundle.wasm Service Worker HTML/CSS/JS rewrite bundle
dist/web/Asset/zp-kernel.wasm Lazily loaded transport kernel (yamux, SOCKS5, rustls, HTTP)
dist/web/Asset/zp-page-bundle.js Page-realm dynamic script rewriter (WASM inlined)
dist/web/Asset/zp-rt.wasm Raw WASM runtime for synchronous URL policy checks
Caution
Do not use -socks internal when you need network anonymity. In internal mode, the Go relay dials target servers directly from the host machine's IP address. Use an external Tor SOCKS5 listener with IsolateSOCKSAuth whenever anonymity matters.
Important
Always pass -web dist/web to the server. Passing the source -web web directory causes HTTP 503 errors because the compiled WebAssembly bundles exist only under dist/web/Asset/.
Option A: Local compatibility test mode (direct connection, no Tor)
./dist/zeroproxy-server -web dist/web -addr :8080 -socks internalOption B: Anonymous browsing through Tor
Start a local Tor SOCKS5 listener with stream isolation enabled:
mkdir -p /tmp/zeroproxy-tor
tor --SocksPort "127.0.0.1:9050 IsolateSOCKSAuth" --DataDirectory /tmp/zeroproxy-torOnce Tor prints Bootstrapped 100% (done), start the relay:
./dist/zeroproxy-server -web dist/web -addr :8080 -socks 127.0.0.1:9050Open http://proxy.localhost:8080/zp/ in your browser, enter a target URL, and press Open. Using proxy.localhost keeps the launcher, the Service Worker, and all encrypted target routes on a single secure-context origin.
- No direct network egress: Content Security Policy headers and membrane hooks route all HTTP fetches, XMLHttpRequests, WebSockets, EventSources, beacons, and subresources through the Service Worker and relay tunnel. Optional WebTransport (
/zp/wt) and WebRTC TURN relays are disabled by default. - No access to the real browser realm: Target scripts never receive the real
window,Location, or an uncontained iframe realm. Cross-origin frames and popups receive filtered membrane wrappers that enforce HTML cross-origin access rules across virtual origins. - Per-site storage partitioning: Even though proxied tabs share the physical
proxy.localhostbrowser origin, the membrane namespaces cookies,localStorage,sessionStorage, IndexedDB, Cache Storage, BroadcastChannel, and Origin Private File System directories by virtual origin. - Fail-closed script and HTML transformation: Every executable script passes through the Rust OXC AST rewriter before running. If a script or HTML document cannot be rewritten safely, ZeroProxy blocks execution and renders an isolated error page.
- Client-held URL keys: Target URLs are encrypted with AES-256-CBC and authenticated with HMAC-SHA256 using HKDF-derived keys kept in the client-only URL
#fragment.
On machines with limited RAM, prefer pushing branches to the GitHub Actions CI workflow rather than running parallel local Rust builds and headless browsers.
| Check | Command | What It Verifies |
|---|---|---|
| JavaScript unit and policy suite | npm run test:js |
Node.js checks for URL policy, Service Worker routing, globalThis.ZP contract, and membrane invariants. |
| Rust workspace tests | cargo test --workspace |
Unit and property tests for OXC AST rewriting, HTML/CSS transforms, dialer contracts, share URL encryption, and codecs. |
| Go server and host tests | go test ./... |
pkg/httphost, pkg/wtproxy, pkg/rtcgw, relay bridging, SOCKS5 framing, CSP headers, and route guards. |
| WASM bundle integration | npm run test:wasm:ci |
Contract checks against prebuilt WebAssembly artifacts in dist/. |
| Browser end-to-end suite | npm run test:e2e:ci |
Real Chromium E2E tests against dist/, including the containment escape matrix and native-vs-proxy differential checks. |
| TLS ClientHello check | npm run check:chrome-hello |
Verifies that the Rust kernel's TLS ClientHello matches the installed Chrome browser's extension set and trust_anchors payload. |
| Real-site smoke test (opt-in) | npm run dogfood:real-site |
Launches a fresh Chromium profile and loads live external sites through the proxy (test/e2e/real-site-regression.test.js). |
| Path | Responsibility |
|---|---|
pkg/httphost, pkg/wtproxy, pkg/rtcgw |
Public embeddable Go packages for the /zp/* HTTP host, WebTransport relay, and WebRTC TURN gateway. |
cmd/zeroproxy-server, internal/ |
Standalone Go CLI server and internal HTTP, WebSocket, SOCKS5, WebTransport, and TURN implementations. |
crates/zp-engine |
Unified Rust facade over script rewriting, streaming HTML transformation, CSS rewriting, and source map composition. |
crates/zp-bundle |
Service Worker WebAssembly exports (zp-bundle.wasm) wrapping zp-engine and shared policy helpers. |
crates/zp-kernel-bundle |
Browser transport kernel (zp-kernel.wasm: yamux, SOCKS5, rustls, HTTP/1.1, HTTP/2, WebSockets) and host-portable dial module. |
crates/zp-rewriter, crates/zp-htmltx, crates/zp-css |
OXC JavaScript AST rewriter, streaming lol_html HTML transformer, and SWC CSS URL rewriter. |
crates/zp-shared, crates/zp-transport-codec |
CSP builders, share URL encryption, error codes, and SOCKS5/HTTP/1.1 wire codecs. |
crates/zp-page-bundle, crates/zp-page-rt, web/zp-rt.js |
Page-realm dynamic script rewriter and synchronous raw-WASM URL policy runtime (zp-rt.wasm). |
web/zp-core.js, web/zp-core.d.ts, web/index.html |
Frozen globalThis.ZP runtime surface, TypeScript definitions, binary v2 envelope codec, and launcher UI. |
web/sw.js |
Service Worker entry point for request classification, tab context, rewrite caching, and kernel dispatch. |
web/membrane/, web/runtime-prelude.js, web/worker-prelude.js |
Virtual browser membrane modules and window.__zp_realm (ZPRealmHandle) for page and worker realms. |
scripts/build.mjs, scripts/test.mjs |
Cross-language build pipeline and test runner scripts. |
test/js, test/e2e, test/browser |
Node contract tests, Puppeteer E2E suites, and interactive browser diagnostic runners. |
| Document | Scope |
|---|---|
CLAUDE.md |
Architecture overview, crate responsibilities, build and test commands, and contributor workflow rules. |
AGENTS.md |
Code quality and documentation standards for contributors and automated agents. |
docs/architecture.md |
Layers, component boundaries, the /zp/* wire contract, the rewriter and WebAssembly ABIs, and embedding interfaces. |
docs/containment-gates.md |
Verification baseline, containment gate evidence, and intentional divergences from native browsers. |
docs/errata.md |
Containment and compatibility risk inventory: closed items and remaining residuals. |
docs/engineering-guide.md |
Engineering scope, real-site survey status, and browser diagnostic workflows. |
docs/rollout.md |
Operator dogfood checklist, runtime monitoring signals, and rollback procedure. |
.ai/design/website-compat-refactor.md |
Website compatibility design, responsibility boundaries, and active refactor tracks. |
.ai/trap-notebook/README.md & .ai/trap-notebook/INDEX.md |
Indexed reference of browser, AST rewriter, Service Worker, and TLS traps. |
test/browser/README.md |
Cross-site browser diagnostic probes covering isolation, fidelity, console noise, and prototype shapes. |
test/browser/hole-matrix/README.md |
Containment escape matrix runner and two-axis verdict classifier for subresource URLs. |