Mudocs logo mu docs // next

overview

SECURE NETWORKING

Big-picture explanations, quick reference material, and definitive orientation docs.

Context Rail

Owner: docs Repo: aoiflux/mutant Ref: main Audience: all Source: docs/SECURE_NETWORKING.md

On This Page

Secure Networking & Traffic Interception

Mutant ships a networking toolkit in its standard library so that tools such as TLS clients/servers and mitmproxy-style interception proxies can be written entirely in Mutant source. See the Capability Reference for the full network builtin table.

The toolkit is split into three layers:

  1. Stream sockets & TLS sessions — connect, listen, accept, read, write, and upgrade connections to TLS (client or server side).
  2. X.509 certificate authority — generate a CA and mint / sign leaf certificates on demand (the core primitive an interception proxy needs).
  3. HTTP message inspection — parse and rebuild HTTP requests and responses, either from a string or directly off a live connection with correct framing.

All fallible builtins follow the language convention of returning (result, err); destructure with let value, err = ....

Two builtins additionally report recoverable conditions inside the returned hash instead of through err, so that a polling loop is not forced to abort:

  • net_conn_read — I/O problems (including read timeouts) land in the result's error field, with end-of-stream in eof. Its err return only fires for bad arguments or an unknown handle, so always check both: if (err) { ... } else if (msg["error"] != "") { ... }.
  • net_accept — an expired accept timeout is a successful call returning ok=false, timeout=true with err null. Genuine accept failures set both the hash's error field and err.

Everything else reports failure through err alone.


1. Sockets and TLS sessions

Connections and listeners are referenced by an integer handle. Always close them with net_conn_close / net_listen_close when done.

Builtin Signature Returns
net_connect (address, timeoutMs) connection handle
net_tls_connect (address, timeoutMs, options?) connection handle
net_conn_write (handle, data, timeout_ms?) bytes written (write deadline: default 30s, or timeout_ms; <=0 blocks forever)
net_conn_read (handle, maxBytes, timeoutMs) {data, bytes, eof, error} (maxBytes ≤ 32 MiB)
net_conn_info (handle) addresses + negotiated TLS session
net_conn_close (handle) bool
net_listen (address) listener handle
net_tls_listen (address, certPem, keyPem, options?) listener handle
net_accept (listener, timeoutMs) {ok, handle, remote_addr, timeout, error}
net_listen_close (handle) bool
net_tls_upgrade_server (handle, certPem, keyPem, options?) handshake info
net_tls_upgrade_client (handle, options?) handshake info

Client TLS options (net_tls_connect, net_tls_upgrade_client): server_name, insecure (skip verification), alpn (array), min_version ("1.0".."1.3"), ca_cert (PEM roots to pin), client_cert + client_key (PEM, for mutual TLS).

Server TLS options (net_tls_listen, net_tls_upgrade_server): alpn, min_version, client_ca (PEM; requires and verifies client certs for mutual TLS).

handshake_timeout_ms (default 15000) applies to the two upgrade builtins only, which drive the handshake themselves. net_tls_connect bounds its handshake with its own timeoutMs dial timeout, and net_tls_listen handshakes lazily on first use of the accepted connection.

net_accept with timeoutMs <= 0 blocks; a positive timeout lets a loop poll without aborting (ok=false, timeout=true on expiry).

Example: verified TLS client

let conn, err = net_connect("example.com:443", 5000);
let opts = {"server_name": "example.com", "min_version": "1.2"};
let info, err = net_tls_upgrade_client(conn, opts);
putln("negotiated ", info["tls_version"]);       // e.g. TLS1.3

2. Certificate authority

Builtin Signature Returns
tls_generate_ca (options?) {cert_pem, key_pem, serial}
tls_generate_cert (options?) {cert_pem, key_pem, serial} (self-signed leaf)
tls_sign_cert (caCertPem, caKeyPem, options?) {cert_pem, key_pem, serial} (CA-signed leaf)

Options: common_name, organization, dns_names (array), ip_addresses (array), days. Keys are ECDSA P-256; certificates are PEM-encoded.

tls_sign_cert is what makes interception possible: mint a leaf certificate for whatever host the client asked for, signed by a CA the client already trusts.

let ca, err = tls_generate_ca({"common_name": "Mutant Dev CA"});
let ca_cert = ca["cert_pem"];
let ca_key = ca["key_pem"];
let leaf_opts = {"common_name": "example.com", "dns_names": ["example.com"]};
let leaf, err = tls_sign_cert(ca_cert, ca_key, leaf_opts);

3. HTTP message inspection

Builtin Signature Returns
http_parse_request (raw) {method, url, path, host, proto, query, headers, body}
http_parse_response (raw) {status, status_text, proto, headers, body}
http_build_request (request) raw request string (adds Content-Length when a body is present and none was supplied)
http_build_response (response) raw response string (adds Content-Length)
http_conn_read_request (handle, timeoutMs) parsed request off a live socket
http_conn_read_response (handle, timeoutMs) parsed response off a live socket

The http_conn_read_* builtins read exactly one message with correct Content-Length / chunked framing (bodies are capped at 32 MiB). Byte reads (net_conn_read) and framed reads share the same buffered stream per handle, so they can be mixed safely on one connection.


Putting it together: an interception proxy

The CONNECT interception flow (see examples/network/mitmproxy.mut):

                    client                     mutant proxy                    upstream
GET/CONNECT  ───────────────────▶  net_accept + http_conn_read_request
                                    │  (CONNECT host:443)
200 Established  ◀──────────────────┤  net_conn_write
                                    │  tls_sign_cert(ca, host)
   TLS handshake  ◀────────────────▶  net_tls_upgrade_server(client, leaf)
real request (encrypted) ─────────▶  http_conn_read_request   ── inspect ──▶
                                    │              net_connect + net_tls_upgrade_client
                                    │              http_build_request ─────────▶  origin
                                    │              http_conn_read_response ◀─────  origin
response  ◀─────────────────────────┤  http_build_response + net_conn_write

Run it and point a client's HTTP+HTTPS proxy at 127.0.0.1:8080, trusting the CA certificate it prints at startup.


Language gotchas (pre-existing, not specific to these builtins)

Writing multi-step network programs surfaces three parser/VM quirks worth knowing; the examples are written to avoid them:

  1. No ; after a for (...) { } block. A trailing semicolon there is parsed as an empty statement and fails (no prefix parse function for ;).
  2. Don't pass array/hash literals as call arguments alongside other arguments — bind the literal to a let first. Passing a composite literal inline can corrupt an earlier argument. Safe: let opts = {...}; f(a, b, opts);
  3. Avoid top-level return. Wrap program logic in a function and call it; return inside a function behaves correctly, but a top-level return misbehaves.

Related Reading