mix-fetch
mix-fetch is a replacement for fetch (opens in a new tab) that routes HTTP(S) requests through the Nym mixnet. The call signature is identical; underneath, the request is tunnelled through a WASM Nym client to a Network Requester (a Nym service provider, typically operated by an Exit Gateway), which decodes a SOCKS5-shaped connect request and opens a TCP connection to the destination. TLS runs end-to-end between the WASM bundle and the destination server.
Available for browsers and Node.js, with the WASM core shared between both.
┌────────────────────────────────────────────────────────────────────┐
│ Your app (browser or Node.js) │
│ └─ mixFetch('https://...') (fetch() replacement) │
│ └─ Go-WASM HTTP/TLS client (embedded Mozilla CA bundle) │
│ └─ Rust-WASM SOCKS5 framing + Nym mixnet transport │
│ └─ WebSocket → entry gateway │
│ └─ mixnet (Sphinx, 3 mix hops by default) │
│ └─ Network Requester decodes SOCKS5 │
│ request, opens TCP to dest │
│ └─ destination server │
│ (TLS handshake here) │
└────────────────────────────────────────────────────────────────────┘Two WASM modules sit in the bundle: a Go module that handles HTTP and TLS (Go's crypto/tls compiled to WASM, with an embedded Mozilla root CA list), and a Rust module that handles SOCKS5 request framing and the Nym mixnet client itself. They communicate via JS bindings.
Because TLS terminates at the destination (not at the Network Requester or any node before it), every hop from the entry gateway onwards only sees TLS ciphertext for HTTPS targets. This is the same trust model as a normal HTTPS request through a SOCKS proxy.
The "SOCKS5" framing here is Nym's binary Socks5Request format wrapped in Sphinx packets, not RFC 1928 SOCKS5 over a TCP socket. The Network Requester decodes it on the mixnet side and proxies onwards as regular TCP.
Runtime and platform support
Browser
The WASM core runs in a Web Worker and needs:
- WebSocket support, for the entry-gateway connection
- WebAssembly
- A CSP that permits
wss://connections andworker-src 'self'(orblob:for the*-full-fatvariants, which load workers as inline blobs)
Mixed-content rules apply: target URLs must be HTTPS.
Node.js
The same WASM core runs in a worker_threads worker. The ws package polyfills WebSocket, and a Node-flavoured comlink adapter (mix-fetch-node/src/node-adapter.ts) bridges worker_threads to the same Worker-like API surface.
Installation
Browser variants
| Variant | Package | When to use |
|---|---|---|
| ESM | @nymproject/mix-fetch | Modern project, you can configure your bundler |
| ESM full-fat | @nymproject/mix-fetch-full-fat | Modern project, can't configure your bundler |
| CommonJS | @nymproject/mix-fetch-commonjs | Legacy project, you can configure your bundler |
| CommonJS full-fat | @nymproject/mix-fetch-commonjs-full-fat | Legacy project, can't configure your bundler |
Node.js variant
| Variant | Package | When to use |
|---|---|---|
| CommonJS | @nymproject/mix-fetch-node-commonjs | Node.js (currently the only published Node variant) |
The standard browser variants need your bundler to handle WASM and web workers (see Bundling). The *-full-fat variants inline both as Base64 so no bundler configuration is needed.
The *-full-fat variants are large (~18 MB), since they inline ~10 MB of WASM (Go runtime + Rust core) and the web-worker source as Base64. Prefer a standard variant if bundle size matters.
# Browser
npm install @nymproject/mix-fetch-full-fat
# Node.js
npm install @nymproject/mix-fetch-node-commonjsmixFetch caps concurrent connections at 10 per destination host (Go http.Transport's MaxConnsPerHost, see wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go:214). Keep-alive is disabled, so each request opens a fresh TCP connection through the mixnet; extra concurrent requests to the same host queue until a slot frees. Different hosts are independent.
Playground and examples
See the interactive playground for a working mixFetch example you can run in the browser.
The first call bootstraps the WASM Nym client (gateway handshake, key generation, cover traffic). Subsequent calls reuse the active client; the Rust side holds it in a OnceLock singleton, so there is one client per page (or per Node process).
When to use mix-fetch
| mix-fetch | WASM Mixnet Client | smolmix | Plain fetch (no mixnet) | |
|---|---|---|---|---|
| Runtime | Browser, Node.js | Browser | Native (Rust) | Anywhere |
| Architecture | Proxy (Network Requester → destination) | E2E (both sides Nym) | Proxy | Direct |
| API shape | fetch() replacement | Send/recv text or binary messages | TcpStream / UdpSocket | fetch() |
| HTTP support | Yes | No | Yes (via hyper over TcpStream) | Yes |
| Sender unlinkability | Strong (mixnet) | Strong (mixnet) | Strong (mixnet) | None |
| Concurrency | 10 per host | Unlimited | Unlimited | Unlimited |
Security model
Use HTTPS targets. Plaintext HTTP requests are visible to the Network Requester and to any router between it and the destination.
What's protected
| Segment | Mixnet encryption | What's visible |
|---|---|---|
| App → entry gateway | Sphinx (layered) over a WebSocket | Entry gateway sees your IP, not the destination |
| Inside the mixnet | Sphinx (layered) | Each node only knows previous / next hop |
| Network Requester | Sphinx removed; SOCKS5 connect request decoded | The Requester sees destination hostname + port; payload is application-layer TLS |
| Network Requester → destination | None (regular TCP) | TLS handshake + ciphertext (with HTTPS targets); cleartext (with HTTP targets) |
Why mix-fetch ships its own CA store
The browser's TLS stack and CA store aren't accessible from JavaScript or from a WASM SOCKS client; on Node, the TLS stack lives outside the Web Worker that hosts the mixnet client. mix-fetch therefore performs TLS itself, inside the WASM bundle, against the destination server. The bundle ships with an embedded Mozilla root CA list (refreshed from curl.se's bundle (opens in a new tab), verified by SHA-256 in wasm/mix-fetch/go-mix-conn/scripts/update-root-certs.sh) and an in-WASM TLS implementation (Go's crypto/tls, configured at wasm/mix-fetch/go-mix-conn/internal/sslhelpers/ssl_helper.go). The mixnet path sees encrypted TLS ciphertext, not plaintext.
mix-fetch handles the TLS layer for you: HTTPS targets are protected end-to-end between the WASM bundle and the destination, as if a browser had initiated the TLS handshake directly. Plaintext HTTP targets remain visible to the Network Requester and to any router beyond it. See Exit Gateway Services for what the exit can and cannot observe.
API reference
Generated reference: typedoc output.