agentgateway as an Egress Proxy

agentgateway v1.6.0 HTTP CONNECT egress proxy for AI agents: hostname allowlist, default deny. Graded standalone proof; K ambient honest. Solo.io disclosure.

AI agents need outbound access to models, tools, registries, and APIs. An unrestricted sandbox will use every channel it can find. The durable pattern is a default-deny egress proxy the workload cannot route around, authorizing destinations at CONNECT time rather than trusting a field the agent controls.

This page is the site’s graded agentgateway proof. Open-source agentgateway v1.6.0 can act as an HTTP CONNECT egress proxy in standalone mode: hostname allowlist, default deny, config, curls, and logs. Kubernetes ambient egress is a different documented recipe, not the same YAML. This is not the four-bypass attack lab, not the DNS covert-channel write-up, and not a four-control control-plane overview.

Disclosure: I work at Solo.io, which created agentgateway and sells Solo Enterprise for agentgateway. Everything below is open-source agentgateway unless it is labelled Solo Enterprise, and every claim links to the project’s own docs or release notes as of 2026-10-05.

What an AI agent egress proxy is

An AI agent egress proxy is a forward proxy on the only outbound path an agent can use. It defaults to deny. It allowlists destinations the agent may reach, usually by hostname at CONNECT or TLS SNI time. It logs the decision. Environment-variable proxy settings alone are not enough, because the agent can unset them. The network must leave no other route.

That is a destination control, not a request-body control. The proxy authorizes where the agent may go, not what it sends once the tunnel is open.

The shape is one hop:

agent
  |   no other outbound route
  v
agentgateway  (HTTP CONNECT)
  |   hostname allowlist, default deny
  +--> allowed hosts only
  x    everything else

Public write-ups already name the problem. PipeLab defines an AI egress proxy as a control on what agents send outbound. SimEdw treats an environment-variable proxy as bypassable. guptadeepak states the rule as default-deny at a proxy the agent cannot route around. Those are public definitions of the problem. This page is the agentgateway recipe.

Why env-var proxies fail (and what this page proves instead)

HTTPS_PROXY is configuration the workload can change. Unset it, rewrite /etc/hosts, put a write in a GET, or point an exempt name at a blocked address, and an in-band proxy never sees the request.

I already walked four of those techniques in AI Agent Egress Control: 4 Bypasses, 1 Fix . That lab’s gateway is a Python CONNECT stand-in (themsquared/agent-egress-control), not agentgateway. It shows why the decision has to happen at CONNECT time, on a path the agent cannot leave. It is not agentgateway evidence. This page is the other half: shipped standalone v1.6.0, allowlisted hosts connect, unmatched hosts do not, and the log records both.

HTTP egress is also not DNS egress. A CONNECT proxy never sees a query that never becomes HTTP. That channel is Your Agent Egress Proxy Never Saw the DNS Query .

agentgateway standalone CONNECT egress (v1.6.0)

Standalone docs say agentgateway can act as an HTTP CONNECT proxy for outbound traffic (S, Documented). Clients set HTTPS_PROXY and send CONNECT :443. What happens next depends on the CONNECT handling mode.

Tunnel mode

Set tunnelProtocol: connect on a bind. Destinations match TLS SNI through tcpRoutes hostnames. Traffic can stay encrypted end to end. HTTP policies do not apply to the CONNECT request, and client authentication is not available on that path. A blocked destination fails with a TLS error after the tunnel is established. Tunnel mode can also terminate TLS for selected hostnames when you add HTTPS listeners.

The official IMPORTANT note is the failure mode you must expect:

The CONNECT tunnel is established before the destination is checked, so the client sees a TLS error rather than a refusal from the proxy. To refuse a blocked destination at the proxy instead, use route mode, described in Require client authentication.

The docs-literal allowlist uses tcpRoutes[].hostnames (for example pypi.org and files.pythonhosted.org) with backends: [{dynamic: {}}]. An unmatched hostname has no route. The example deny to example.com is a TLS error, not an HTTP 404.

Route mode

Set frontendPolicies.connect.mode: route. Destinations match the CONNECT authority through route hostnames. HTTP policies apply. basicAuth is available. A blocked destination returns 404 to CONNECT. Route mode does not terminate TLS in this pattern. The docs show 407 Proxy Authentication Required without credentials, and 404 Not Found on CONNECT for a non-allowlisted host even with valid proxy credentials. curl hides those proxy statuses unless you pass --verbose.

Mode comparison

AreaTunnel mode (S)Route mode (S)
ConfigurationtunnelProtocol: connect on a bindfrontendPolicies.connect.mode: route
Match fieldTLS SNI via tcpRoutes hostnamesCONNECT authority via route hostnames
Client authNot availablebasicAuth available
Block signalTLS error after the tunnel opens404 on CONNECT
TLS terminate optionYes, for selected hostnamesNo, in this pattern

Neither mode replaces the other. Tunnel mode gives SNI-based control and optional TLS terminate. Route mode gives HTTP policies, including client authentication, and a clean 404 at the proxy. Marketing’s proof below is tunnel mode. Route-mode basicAuth (407 then 404) was not run.

What shipped when (v1.5.0 to v1.6.0)

Grade the timeline, not a slogan.

In released v1.5.0, substrateEgress only adds actor metadata and enforces nothing. The v1.5.0 source at egress.rs still carried // TODO: implement egress policy in Substrate (Substrate, source on tag v1.5.0 ).

Actor-identity authorization at CONNECT (PR #3237 , merged 2026-09-01) and hostname or CIDR destination rules with deny-when-nothing-matches (PR #3318 , merged 2026-09-09) first appeared in the v1.6.0 prereleases. They shipped in stable v1.6.0 on 2026-10-02. The v1.6.0 release notes list #3237, #3318, and #3677 (“Fix Substrate egress after the ateom/actor identity split”). #3237 authorizes actor identities before accepting HTTP, TLS, or opaque TCP CONNECT, rejects unknown, stale, or non-running actors, and fails closed when the control plane is unavailable.

That is Substrate code in the same release as the standalone binary Marketing ran. It is not a claim that this page tested Actor egress on Substrate or kagent.

The version table dates agentgateway 1.6.x as 02 Oct 2026, with Kubernetes 1.32 to 1.37, Gateway API 1.4 to 1.6, and MCP 2026-07-28 (S+K, Documented). GitHub published v1.6.0 on 2026-10-02 (prerelease: false), with linux-amd64 and darwin-arm64 binaries and cr.agentgateway.dev/agentgateway:v1.6.0 (S+K, release notes).

Standalone CONNECT hostname allowlisting is the S recipe this proof runs. It is not the Kubernetes ambient waypoint, and it is not the Substrate Actor policy.

Proof: allowlist and default deny

Marketing ran the open-source standalone binary on 2026-10-05. I am publishing their artifacts under /lab/agentgateway-egress-proxy/ . Scope: OSS standalone v1.6.0 only. Not claimed: Substrate or kagent runtime. No Docker.

Mode: tunnel CONNECT (binds[].tunnelProtocol: connect). Allowlist: example.com and httpbin.org. Unmatched host: no tcpRoute, so after CONNECT 200 the client sees a TLS error (unexpected eof while reading), not HTTP 404. 404 is route mode. Quote the IMPORTANT note above before you treat that TLS error as a surprise.

Version (P1, Tested)

The binary reported version: 1.6.0 and git_revision: ea5608642b9d5c94c5baf5fd9f2f9849b808e963 (version.json ). Asset: agentgateway-linux-amd64 from the v1.6.0 release. Checksum verified: 496ed27e6a96b55a9ad7cabe5c2be7172511f44a3cb113e533d22bf93a7f079d. Test date: 2026-10-05 09:11:12 PDT.

Bind-443 caveat (document this honestly)

The docs-literal pattern gateways.secure.port: 443 opens an OS listener. On an unprivileged box it failed:

Error: failed to start bind listener(s): bind bind/443: bind listener for [::]:443: Permission denied (os error 13)

That is evidence/bind443-error.txt .

The working pattern is on the same docs page, under “Inspect all HTTPS traffic”: a binds entry for port 443 with mode: internal. That is routing-only. It does not open an OS socket on privileged 443. CONNECT *:443 re-enters the SNI / tcpRoutes allowlist. Semantics stay tunnel allowlist plus default deny.

Working config (P2, Tested)

Committed at egress.yaml :

binds:
- port: 3000
  tunnelProtocol: connect
  listeners: []
- port: 443
  mode: internal
  listeners:
  - name: public-egress
    hostname: "*"
    protocol: TLS
    tcpRoutes:
    - name: public-egress-allowlist
      hostnames:
      - example.com
      - httpbin.org
      backends:
      - dynamic: {}

Proxy: http://127.0.0.1:3000.

Allow (P3, Tested)

T1 https://example.com/ via CONNECT: destination HTTP/2 200, curl exit 0 (t1-allow-example.com.txt ). The first hop in that capture is the proxy CONNECT HTTP/1.1 200 OK; the second is the origin HTTP/2 200.

T1c https://httpbin.org/: the gateway log records route=default/public-egress-allowlist endpoint=httpbin.org:443 tls.sni=httpbin.org. The harness notes record HTTP/2 200 and curl exit 0. The T1c curl file was not in the artifact pack; the log line and harness notes are the evidence.

Deny (P4, Tested)

T2 https://never-allowlisted-egress-test.example/: CONNECT HTTP/1.1 200 OK, then curl: (35) TLS connect error: error:0A000126:SSL routines::unexpected eof while reading (t2-deny-never-allowlisted.txt ).

T3 https://pypi.org/ (not on this harness allowlist): the same CONNECT 200, then curl exit 35 unexpected EOF (t3-deny-pypi.org.txt ). The official tunnel-mode example allowlists pypi.org. This harness does not, so a docs-example host is denied when it is absent from tcpRoutes.

Gateway log, same run (agentgateway.log ):

info   request ... listener=public-egress route=default/public-egress-allowlist endpoint=example.com:443 tls.sni=example.com
error  request ... listener=public-egress tls.sni=never-allowlisted-egress-test.example error="route not found"
error  request ... listener=public-egress tls.sni=pypi.org error="route not found"
info   request ... listener=public-egress route=default/public-egress-allowlist endpoint=httpbin.org:443 tls.sni=httpbin.org

Allow is a route hit. Deny is route not found after the CONNECT tunnel is already established, matching the official IMPORTANT note. A proxy refusal instead of a TLS error is route mode. Marketing did not run that optional case (P7).

Grade table

IDCaseModeGradeEvidence
P1Binary 1.6.0, git ea560864..., checksum verifiedSTestedversion.json
P2Tunnel allowlist + mode: internal on 443STestedegress.yaml
T1 / T1cALLOW example.com, httpbin.org → HTTP/2 200STestedcurl T1; log + harness notes T1c
T2 / T3DENY unmatched host and pypi.org → CONNECT 200, then TLS EOFSTestedcurl + route not found
Bind 443Docs-literal gateways.secure.port: 443STested (fails unprivileged)Permission denied (os error 13)
P7Route-mode basicAuth, 407 then 404SNot runOptional
P8Kubernetes ambient egressKDocumented onlyOfficial ambient-egress guide
Actor egress#3237 / #3318 / kagent policySubstrateShipped in v1.6.0; not Tested hereRelease notes + kagent docs

Standalone CONNECT allowlist plus default deny is Tested. That cell is not Kubernetes ambient egress, and it is not a Substrate Actor.

Kubernetes: what the docs cover

Do not paste the standalone YAML into a cluster and call it the same control. Mark the recipe.

Ambient egress waypoint (K, Documented)

The ambient egress guide is a different path. An Istio ambient client sends to an external host. ztunnel forwards over HBONE to an agentgateway-managed waypoint (:15008 plus an internal HTTP listener). You model the destination with a MESH_EXTERNAL ServiceEntry, an AgentgatewayBackend, and an HTTPRoute. The demo destination is httpbin.org, and the route adds x-agw-waypoint: true. An optional NetworkPolicy limits client egress to DNS, istio-system, and the waypoint on :15008.

The client does not set HTTPS_PROXY and does not send CONNECT to agentgateway. Istio steers matching egress through the waypoint. That is not the standalone tcpRoutes hostname allowlist. I am not claiming the same match field, the same block signal, or a one-click twin of egress.yaml. Marketing did not run this path (P8). Documented is the grade.

Two nearby Kubernetes pages are easy to conflate and are not this control. dynamicForwardProxy can send to arbitrary destinations from the request host or TLS SNI unless you add access controls (K, Documented). The backend tunnel policy is the gateway CONNECTing through an upstream HTTP proxy for JWKS or provider access: gateway-as-client, not agent to agentgateway as a CONNECT allowlist.

Substrate / kagent Actor egress (Substrate / K, Documented)

kagent’s networking and egress docs describe a third recipe. An Actor reaches the network only through the egress gateway. Policy is default-deny. Rules are hostname, CIDR, or all. The gateway terminates TLS so hostname rules apply to encrypted traffic; opaque TCP matches by address. kagent builds the allowlist from the AgentTemplate. Related to #3237 and #3318, which shipped in stable v1.6.0. Code shipped. Runtime behaviour on this page is not a Tested cell.

kagent v1.0.0-alpha15 is an alpha release. It adds a reusable EgressPolicy resource that an Agent or SandboxTemplate selects through spec.egressPolicyRefs (PR 3177 ). The gateway injects the outbound auth header from a Kubernetes Secret selected by secretKeyRef, so the agent never holds the token. The same release also adds token passthrough for remote MCP servers.

What this does not cover

HTTP CONNECT allowlisting is one channel. DNS as a covert channel is another; that post’s NetworkPolicy pins the resolver.

Body inspection and “AI firewall” claims are how some peer pages define a broader product. They are not what this CONNECT allowlist proves.

Per-key dollar and token spend caps are a different control; see the open-source standalone per-key budgets and the Solo Enterprise cost-controls page .

Outbound identity is the other half of CONNECT-time authorization: SPIFFE for who is asking, secretless agents so the provider key never sits in the process. Once egress is default-deny, shadow MCP servers that are not on the list are unreachable.

CONNECT egress as a scorecard row is already on how to choose an AI agent gateway and, as a factual compare cell, on agentgateway vs LiteLLM vs Portkey . I am not re-scoring seven controls here.

Methodology and disclosure

Last checked 2026-10-05, agentgateway docs labelled 1.6 (latest), releases through v1.6.0.

I fetched the standalone egress-proxy page (tunnel vs route, IMPORTANT note, allowlist and route-mode examples), the version table , the v1.6.0 release and PRs #3237 , #3318 , and #3677 , the Kubernetes ambient-egress guide, the Kubernetes API dynamicForwardProxy warning, the backend tunnel page, and the kagent networking docs. v1.5.0 substrateEgress is cited from source on that tag.

Proof artifacts are Marketing’s 2026-10-05 standalone run (P1 to P6 PASS). Copies live at /lab/agentgateway-egress-proxy/ . Tunnel CONNECT; bind-443 required mode: internal. I do not cite that harness as Substrate or kagent. P7 (route-mode basicAuth) and P8 (Kubernetes ambient) were not run.

The bypasses post used a Python CONNECT stand-in, not agentgateway. I do not treat it as agentgateway evidence. Nothing on this page is labelled Solo Enterprise. Every agentgateway claim above is marked S, K, Substrate, or S+K. Standalone CONNECT is not the ambient waypoint, and neither is the Substrate Actor policy.

Disclosure: I work at Solo.io, which created agentgateway and sells Solo Enterprise for agentgateway. Everything below is open-source agentgateway unless it is labelled Solo Enterprise, and every claim links to the project’s own docs or release notes as of 2026-10-05.

Frequently asked questions

What is an AI agent egress proxy?

An AI agent egress proxy is a forward proxy on the only outbound path an agent can use. It defaults to deny, allowlists destinations the agent may reach (usually by hostname at CONNECT or TLS SNI time), and logs the decision. Environment-variable proxy settings alone are not enough, because the agent can unset them; the network must leave no other route.

How does agentgateway work as an egress proxy?

In standalone mode, agentgateway accepts HTTP CONNECT on a bind. In tunnel mode it matches destinations by TLS SNI through tcpRoutes hostnames and leaves traffic encrypted end to end; a blocked destination surfaces as a TLS error because the tunnel is established before the destination check. In route mode it matches the CONNECT authority through route hostnames, can require proxy authentication, and returns 404 on CONNECT for a destination with no route. Clients point HTTPS_PROXY at agentgateway.

What egress controls shipped in agentgateway v1.6.0?

In v1.5.0, substrateEgress only attached actor metadata and did not enforce destinations. Actor-identity authorization at CONNECT time and hostname or CIDR destination rules with default deny first appeared in the v1.6.0 prereleases and shipped in stable v1.6.0 on 2026-10-02. Standalone CONNECT hostname allowlisting is documented for current releases; Kubernetes documents an Istio ambient egress waypoint pattern that is a different recipe from the standalone CONNECT YAML.