Skip to content

feat: add opt-in HTTPS MITM to the network interceptor#63

Open
tgockel wants to merge 1 commit into
trunkfrom
0064-network-interceptor-mitm
Open

feat: add opt-in HTTPS MITM to the network interceptor#63
tgockel wants to merge 1 commit into
trunkfrom
0064-network-interceptor-mitm

Conversation

@tgockel
Copy link
Copy Markdown
Owner

@tgockel tgockel commented May 18, 2026

Add an opt-in MITM mode that composes with the existing audit and filter modes. When enabled, the interceptor mints a per-session ECDSA P-256 CA, writes the public cert and 0600 private key under <session_dir>/tls/, installs the cert into the container trust store (Debian, Red Hat, and standalone PEM plus four language env vars), terminates incoming TLS on the configured HTTPS-bearing ports with per-host leaf certificates, re-encrypts upstream against the host's native trust anchors, and emits one audit record per HTTP request carrying method, URL, and status. ALPN advertises both http/1.1 and h2 in each direction so HTTP/2 clients work transparently; hyper 1.x serves both protocols.

MITM is strictly opt-in. Disabled, the audit and filter behavior from 0060 stays bit-for-bit identical -- the new optional audit fields skip serialization, the CA is never generated, and HTTPS still passes through opaquely. HTTPS connections without SNI (IP-literal destinations) also pass through opaquely; the interceptor declines to mint a leaf cert without a name to bind it to, and the audit record stays the 0060 shape.

URL-aware policy reuses the existing allow / deny lists with an optional path glob (inline-table form only). Entries with path are skipped by the TCP-open decision and only fire after MITM has parsed the request line. A path entry without [network.mitm].enable = true is a schema error.

Body capture is off by default. When enabled, request and response bodies are base64-encoded into outrig.request_body_b64 / outrig.response_body_b64, capped per body (default 64 KiB), and the truncation status lands in outrig.body_truncated. Bodies past the cap still pass on the wire; only the captured copy is truncated.

Repo configs may flip [network.mitm].enable to opt out (or in if the global config has not), but ports, capture-bodies, and max-body-bytes stay global-only -- mirroring the existing decision that the policy lists are not part of the repo surface. The session CA's private key is removed during NetworkInterceptor::shutdown; the public certificate remains so audit-log readers can verify recorded handshakes after the session ends. CA validity is 30 days.

Add an opt-in MITM mode that composes with the existing audit and filter
modes. When enabled, the interceptor mints a per-session ECDSA P-256 CA,
writes the public cert and 0600 private key under <session_dir>/tls/,
installs the cert into the container trust store (Debian, Red Hat, and
standalone PEM plus four language env vars), terminates incoming TLS on
the configured HTTPS-bearing ports with per-host leaf certificates,
re-encrypts upstream against the host's native trust anchors, and emits
one audit record per HTTP request carrying method, URL, and status.
ALPN advertises both http/1.1 and h2 in each direction so HTTP/2 clients
work transparently; hyper 1.x serves both protocols.

MITM is strictly opt-in. Disabled, the audit and filter behavior from
0060 stays bit-for-bit identical -- the new optional audit fields skip
serialization, the CA is never generated, and HTTPS still passes through
opaquely. HTTPS connections without SNI (IP-literal destinations) also
pass through opaquely; the interceptor declines to mint a leaf cert
without a name to bind it to, and the audit record stays the 0060 shape.

URL-aware policy reuses the existing allow / deny lists with an optional
path glob (inline-table form only). Entries with path are skipped by the
TCP-open decision and only fire after MITM has parsed the request line.
A path entry without [network.mitm].enable = true is a schema error.

Body capture is off by default. When enabled, request and response
bodies are base64-encoded into outrig.request_body_b64 /
outrig.response_body_b64, capped per body (default 64 KiB), and the
truncation status lands in outrig.body_truncated. Bodies past the cap
still pass on the wire; only the captured copy is truncated.

Repo configs may flip [network.mitm].enable to opt out (or in if the
global config has not), but ports, capture-bodies, and max-body-bytes
stay global-only -- mirroring the existing decision that the policy
lists are not part of the repo surface. The session CA's private key is
removed during NetworkInterceptor::shutdown; the public certificate
remains so audit-log readers can verify recorded handshakes after the
session ends. CA validity is 30 days.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant