Back to Blog
Architecture2026-06-26

The Invisible Front Door: Why We Choose Transparent TLS MITM Over Generic Proxies

Examining the architectural trade-offs of intercepting AI agent traffic, handling SPDY upgrades, and extracting intent from opaque protocols.

When designing an infrastructure gateway to govern AI agent access, the default industry playbook says: build a centralized reverse proxy, add an authentication middleware, and instruct your clients to point their tools at the new endpoint.

We reject this approach entirely for Iddio.

Instead, we build a local daemon that transparently Man-In-The-Middle (MITM) intercepts HTTPS traffic directly out of the tools the agent already runs. A single, one-time iddio shell-init step exports the standard HTTPS_PROXY/HTTP_PROXY variables and a merged CA bundle into the shell. After that, we never ask the agent to edit kubectl’s kubeconfig, point at a bespoke gateway endpoint, or mint a new set of credentials — the agent’s tools keep reading their own config files and using their own identities. Only the transport is redirected.

Here is why we choose transparent TLS interception over generic API gateways, and the protocol-level mechanics required to make it work.

The Friction of Explicit Proxies

Generic reverse proxies introduce unacceptable friction for automated agents. If we require an agent — or its kubeconfig, or its AWS profile — to point at a bespoke gateway endpoint with its own hostname, its own API shape, and its own credentials, we immediately break assumptions built into native cloud tools and give the agent a second identity to manage.

Agents expect to interact with standard API endpoints. They rely on local configuration files (~/.kube/config, ~/.aws/credentials) that they generate or manage dynamically. When a security tool demands changes to those files, it creates brittle dependencies and introduces failure modes into the agent’s workflow.

Our core thesis is that security infrastructure must stay invisible at the tool-configuration layer, even though it is very much present at the transport layer. Every CLI already knows how to honor HTTPS_PROXY and a custom CA bundle — that’s ordinary, widely supported plumbing, not a gateway-specific integration to build against. We lean on it: shell-init sets both once, and the daemon intercepts only the hosts a policy actually cares about. The agent brings its native identity, runs its standard CLI tools unmodified, and never learns the daemon exists until a request hits a policy it doesn’t like.

The Mechanics of Local Interception

To achieve this invisibility, the Iddio daemon installs a local Certificate Authority (CA) on first run. It stores the CA and its private key securely under ~/.iddio/ with 0600/0700 permissions.

We do not intercept everything. Operators define explicit inspect: host lists in their YAML policy (e.g., *.eks.amazonaws.com). When the daemon sees an outbound connection matching a tracked host, it presents a leaf certificate signed by the local CA and terminates the TLS connection. Anything not explicitly listed in the policy passes through as a blind TCP tunnel—never decrypted, never audited.

This design gives us deep visibility where required, without breaking the trust model of unrelated external services.

Surviving Protocol Upgrades

Real-world infrastructure tools do not limit themselves to simple request-response cycles. Consider kubectl exec or kubectl attach. These commands turn the initial HTTP/1.1 request into a persistent, bidirectional byte stream via an HTTP Connection: Upgrade handshake — historically SPDY, increasingly WebSocket. HTTP/2 explicitly forbids the hop-by-hop headers that negotiate this kind of upgrade, so a proxy that speaks HTTP/2 on the MITM leg breaks kubectl exec outright.

We sidestep the problem rather than detect it mid-stream: our MITM leg to the client only ever speaks HTTP/1.1 (HTTP/2 support there is a deliberate later-tier item, not yet built), so there’s no per-connection negotiation to get wrong. The proxy also isn’t built on top of a net/http.Server — it drives its own read loop directly against the raw TLS connection, so there’s no http.Hijacker to invoke. What it does have to handle is the Connection: Upgrade request itself, and it treats any such request the same way regardless of the negotiated protocol: it classifies and policy-checks the request like any other, dials the upstream itself, forwards the request with its Upgrade/Connection headers intact, relays the upstream’s 101 Switching Protocols response back to the client, and only then splices the two raw connections together until either side closes. We trade per-frame visibility for protocol correctness once the tunnel is open — the tunnel’s open/close is still audited, the bytes inside it are not.

Unmasking Intent: Header and Body-Aware Classification

Terminating TLS provides access to the HTTP request, but the HTTP method and path rarely tell the full story. A naive proxy sees a POST request and categorizes it as a modification.

Cloud APIs are deliberately opaque. AWS, for example, often multiplexes entirely different operations over a single endpoint using POST /. The actual intent is hidden inside the X-Amz-Target header or the request body. A request to describe instances looks identical on the wire to a request to delete a KMS key if you only look at the method and path.

Our classifiers inspect the decrypted payload to recover the real operation. A header-aware refiner extracts the AWS action and maps it against our four-tier risk model (observe, modify, sensitive, break-glass). If the operation is a credential-bearing service like IAM or STS, we floor the classification to break-glass. If the operation is effectively irreversible—such as ScheduleKeyDeletion—we match it against our operations: hard-deny selector, outright refusing the request regardless of any allow rules.

Conclusion

Building a generic reverse proxy is the easy path. It avoids the complexities of certificate generation, raw connection splicing, and protocol-specific payload inspection.

However, generic proxies force the governed workload to adapt to the security tool. By choosing transparent TLS MITM proxying, we push the complexity into the daemon. We parse the Kubernetes API grammar, manually splice SPDY/WebSocket upgrade tunnels, and extract hidden intent from opaque headers.

The result is a gateway that governs without friction. Set up once, the agent remains unaware of the daemon’s presence until it attempts an operation that policy forbids, proving that the best security infrastructure is the kind you configure once and then never think about again.

Try It Yourself

Iddio is open source. Deploy a zero-trust command proxy for your AI agents in minutes.