Security¶
What MicroProxy does to stay safe with untrusted clients and servers, and what an operator has to decide: who may reach the proxy, which servers clients may reach through it, what it trusts, and what it logs. Read this before exposing a proxy beyond your own machine. Each section links to the guide with the details.
Exposure: who can use the proxy¶
- Loopback by default. The listener accepts connections on loopback only;
--server(allow_local_only=false) listens on every interface. Put a proxy that listens on a network behind proxy authentication, a firewall, or both. - Proxy authentication checks Basic credentials or any scheme of your own, per connection or
per request, and removes
Proxy-Authorizationfrom every request before filters see it, so it is never forwarded (Proxy authentication). SOCKS5 clients authenticate with a username and password through the same authenticator (SOCKS). Script authenticators fail closed (Authenticating clients). - Concurrency limits keep one client, user or target from taking every connection:
--max-concurrent-per-clientor aConcurrencyLimiter. - PROXY protocol makes the address a header names the client's, for logs, authorization and
limits alike. Required mode (
--accept-proxy-protocol) is safe when only load balancers can reach the listener. Optional mode (--proxy-protocol-mode optional) lets any client that can connect claim any address by sending a header: use it only behind a load balancer on a trusted network, and name the load balancers with--proxy-protocol-trusted-sources, so that a header from any other peer closes its connection.%DOWNSTREAM_DIRECT_REMOTE_ADDRESS%logs the TCP peer either way (PROXY protocol). - The admin API can drain and stop the proxy. It listens on
127.0.0.1unless told otherwise, takes a token (--admin-token,--admin-token-file), answers on loopback only toHosts that are loopback names (against DNS rebinding), refusesPOSTs that carry anOrigin, and redacts secrets in/config_dumpand/server_info(Admin security).
Request parsing¶
The HTTP/1 parser rejects Transfer-Encoding together with Content-Length, conflicting
lengths, obs-fold in requests, and oversized lines and headers; header values are validated
against CR/LF injection; and Host is replaced by the absolute-form authority. The limits are
max_initial_line_length (8192), max_header_size (16384) and max_chunk_size (16384)
(Configuration properties). When the proxy answers itself, its short
plain-text body never echoes the request (Failure responses).
withLittleProxyCompatibility() does not relax any of this.
A request with both Transfer-Encoding and Content-Length is the classic request smuggling
vector: two hops that frame it differently disagree on where the next request starts. RFC 9112
section 6.1 lets a server reject it, which MicroProxy does (400), as LittleProxy does through
Netty's decoder. Some LittleProxy forks accepted such requests; for clients that send them,
--allow-chunked-length (allow_chunked_length, withAllowChunkedLength(true)) accepts them as
the RFC describes, and only when the rest of the framing is sound: chunked exactly once, as the
final coding, a Content-Length valid on its own, HTTP/1.1, and neither a CONNECT nor an
Upgrade. The Content-Length is removed
before filters see the request, the body is read by its chunked coding, and the client connection
is closed after the response, so whatever followed the body is never read as a request. Accepted
requests are flagged (FlowContext.isChunkedWithContentLength(), %MP_CHUNKED_LENGTH%, the
downstream_rq_chunked_length counter) so they can be watched. A server's response with both
headers is always framed by its transfer coding, its Content-Length dropped, as RFC 9112
section 6.3 requires of an intermediary.
Denial of service¶
Every input has a bound, and a peer that exceeds it loses its stream or connection, not the proxy:
| Area | Protections | Details |
|---|---|---|
HTTP/2 (on by default over TLS, to clients and servers; --no-http2, --no-http2-upstream) |
stream, header-list, CONTINUATION, HPACK and flow-control limits; Rapid Reset (CVE-2023-44487), PING, SETTINGS, RST_STREAM, WINDOW_UPDATE and empty-frame floods; idle and stalled-write timeouts | HTTP/2 limits |
| HTTP/3 | QUIC's stream limits and flow control, field-section and QPACK limits, unknown frame and stream floods, Rapid Reset, optional address validation with Retry | HTTP/3 limits |
| TLS | a handshake timeout (--tls-handshake-timeout, 10 s) on both sides; a ClientHello is read up to 64 KiB within that timeout |
Deciding what to intercept |
| HTTP/1 request heads | a total 30 s deadline from the first byte; expired heads receive 408 and the connection closes. Set --request-head-timeout 0 to disable |
Request parsing |
| SOCKS | the whole handshake within the TLS handshake timeout; field lengths bounded | SOCKS hardening |
| WebSockets | frames over 1 MiB stream through unbuffered; messages, joined or inflated, over 16 MiB close the connection with 1009 (decompression bombs included) | WebSockets |
| Bodies | decoding is capped at 64 MiB to defuse compression bombs; a Brotli body with bytes after its end, or a command past its meta-block, is rejected as the reference decoder does; buffered requests over the filters' limit get 413 |
Rewriting bodies |
| Transparent QUIC and CONNECT-UDP | bounded pending clients, per-IP flows, held datagrams and queues; datagrams past a bound are dropped | Transparent QUIC, CONNECT-UDP |
| QUIC message framing | messages over --quic-max-message-size reset their stream |
QUIC message framing |
| Scripts | each hook call is bounded in steps, time and allocation, and sandboxed (no files, network or processes) | Budgets and measurements |
| Idle connections | --idle-timeout (70 s) closes idle client and server connections and tunnels |
Command-line flags |
Which servers clients can reach (SSRF)¶
- Forward proxy: the client chooses the server, by design, as with any forward proxy. If
clients must not reach internal addresses through it, refuse them in a filter
(
clientToProxyRequest), a script or aBlockList, or resolve names yourself inproxyToServerResolutionStarted, which may return the address to connect to; or route everything through an upstream with chained proxies. - Reverse proxy: the client never picks the server. Absolute-form URIs are made origin-form
for the upstream,
:authoritychooses nothing, andCONNECTis refused (Reverse proxy). - Transparent proxy: the SNI or
Hostnames the server; put the proxy where only the clients it serves can reach it (Transparent mode). - TLS origination:
https://URIs sent withoutCONNECTare fetched over TLS the proxy opens (on by default). Turn it off with--no-originate-tlswhen plain-HTTP clients should reach TLS servers only through tunnels: host rules andproxyToServerAllowMitmdo not apply to these requests, and on the plain listener the request crosses the network in clear text (TLS origination).
Trust settings¶
- Upstream certificates are validated against the JDK's trust store by default, by the MITM
manager's server context when one is configured.
--mitm-trust-all(SslContexts.trustAll()) turns validation off: use it only for testing. For private CAs, add anchors withSslContexts.systemDefaultPlus(caCert)instead (MITM). - The interception CA signs certificates every client that trusts it will accept.
--mitmkeeps its key inmicroproxy-ca.p12, protected by--mitm-ca-password(defaultmicroproxy): set your own password, keep the file private, and trust its certificate only on clients you control. - TLS versions: only TLS 1.3 and 1.2 are allowed by default on every TLS connection (TLS protocol versions).
- Server names: a server that rejects the name the proxy sends (SNI) is retried without it
only with
--tls-retry-without-sni(on by default in LittleProxy compatibility mode); a filter may send another name or check the certificate against another host (proxyToServerTlsIdentity). Neither skips a check: the certificate is still validated and matched against the intended (or the filter's) host, since SNI authenticates nothing (Server TLS failures). - DNS:
--dnssecvalidates answers from the root down and never falls back to unvalidated DNS (DNSSEC). - 0-RTT: early data from HTTP/3 clients can be replayed, so it is off by default
(
--http3-0rtt); when on, methods that are not safe to repeat get425 Too Early(Session resumption and 0-RTT).
Sensitive data¶
- Request logging redacts
Authorization,Cookie,Set-CookieandProxy-Authorizationby default, butBODYlogs payloads unredacted (Request/response logging). WARC files and HAR files record traffic complete and unredacted (WARC recording, HAR files). - Tracing headers can be removed from requests sent upstream (
--strip-tracing-headers) (Removing tracing headers), and--otel-ignore-incomingmakes the proxy a trust boundary for trace context (OpenTelemetry). - Secrets on the command line are visible to other local users (
ps): use--admin-token-fileand--script-var-filerather than their inline forms.