# 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.

- Canonical URL: https://webofmike.com/agentgateway-egress-proxy/
- Author: Mike Moore (https://webofmike.com/about/)
- Published: 2026-10-19
- Last modified: 2026-10-19
- Tags: AI Gateways, AI Agents, Security, Platform Engineering, Kubernetes
- Cite as: Mike Moore, "agentgateway as an Egress Proxy", Web of Mike (webofmike.com), 2026-10-19. https://webofmike.com/agentgateway-egress-proxy/


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:

```text
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](https://pipelab.org/learn/ai-egress-proxy/) defines an AI egress proxy as a control on what agents send outbound. [SimEdw](https://simedw.com/2026/06/05/proxy-agents/) treats an environment-variable proxy as bypassable. [guptadeepak](https://guptadeepak.com/guides/egress-control-for-ai-agents/) 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](/agent-egress-control-bypasses/). 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](/agent-dns-egress-covert-channel/).

## agentgateway standalone CONNECT egress (v1.6.0)

Standalone docs say agentgateway can act as an [HTTP CONNECT proxy](https://agentgateway.dev/docs/standalone/latest/documentation/configuration/egress-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

| Area | Tunnel mode (S) | Route mode (S) |
| --- | --- | --- |
| Configuration | `tunnelProtocol: connect` on a bind | `frontendPolicies.connect.mode: route` |
| Match field | TLS SNI via `tcpRoutes` hostnames | CONNECT authority via route `hostnames` |
| Client auth | Not available | `basicAuth` available |
| Block signal | TLS error after the tunnel opens | `404` on CONNECT |
| TLS terminate option | Yes, for selected hostnames | No, 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](https://github.com/agentgateway/agentgateway/blob/v1.5.0/crates/agentgateway/src/http/substrate/egress.rs#L57)).

Actor-identity authorization at CONNECT ([PR #3237](https://github.com/agentgateway/agentgateway/pull/3237), merged 2026-09-01) and hostname or CIDR destination rules with deny-when-nothing-matches ([PR #3318](https://github.com/agentgateway/agentgateway/pull/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](https://github.com/agentgateway/agentgateway/releases/tag/v1.6.0) 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](https://agentgateway.dev/docs/kubernetes/main/reference/versions/) 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](https://github.com/agentgateway/agentgateway/releases/tag/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/`](/lab/agentgateway-egress-proxy/README.md). 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`](/lab/agentgateway-egress-proxy/evidence/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:

```text
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`](/lab/agentgateway-egress-proxy/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`](/lab/agentgateway-egress-proxy/egress.yaml):

```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`](/lab/agentgateway-egress-proxy/evidence/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`](/lab/agentgateway-egress-proxy/evidence/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`](/lab/agentgateway-egress-proxy/evidence/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`](/lab/agentgateway-egress-proxy/evidence/agentgateway.log)):

```text
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

| ID | Case | Mode | Grade | Evidence |
| --- | --- | --- | --- | --- |
| P1 | Binary 1.6.0, git `ea560864...`, checksum verified | S | **Tested** | `version.json` |
| P2 | Tunnel allowlist + `mode: internal` on 443 | S | **Tested** | `egress.yaml` |
| T1 / T1c | ALLOW `example.com`, `httpbin.org` → HTTP/2 200 | S | **Tested** | curl T1; log + harness notes T1c |
| T2 / T3 | DENY unmatched host and `pypi.org` → CONNECT 200, then TLS EOF | S | **Tested** | curl + `route not found` |
| Bind 443 | Docs-literal `gateways.secure.port: 443` | S | **Tested** (fails unprivileged) | `Permission denied (os error 13)` |
| P7 | Route-mode basicAuth, 407 then 404 | S | Not run | Optional |
| P8 | Kubernetes ambient egress | K | **Documented** only | Official ambient-egress guide |
| Actor egress | #3237 / #3318 / kagent policy | Substrate | Shipped in v1.6.0; **not Tested** here | Release 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](https://agentgateway.dev/docs/kubernetes/latest/integrations/istio/ambient-egress/) 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`](https://agentgateway.dev/docs/kubernetes/latest/reference/api/) can send to arbitrary destinations from the request host or TLS SNI unless you add access controls (**K**, Documented). The [backend tunnel](https://agentgateway.dev/docs/kubernetes/main/integrations/llm/providers/backend-tunnel-proxy/) 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](https://kagent.dev/docs/kagent/1.x/substrate-runtime/networking-and-egress/) 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](https://github.com/kagent-dev/kagent/releases/tag/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](https://github.com/kagent-dev/kagent/pull/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](/agent-dns-egress-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](/agentgateway-per-key-llm-budgets/) and the [Solo Enterprise cost-controls page](/llm-cost-controls-ai-gateway/).

Outbound identity is the other half of CONNECT-time authorization: [SPIFFE](/spiffe-identity-for-ai-agents/) for *who* is asking, [secretless agents](/secretless-ai-agents/) so the provider key never sits in the process. Once egress is default-deny, [shadow MCP servers](/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](/how-to-choose-an-ai-agent-gateway/) and, as a factual compare cell, on [agentgateway vs LiteLLM vs Portkey](/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](https://agentgateway.dev/docs/standalone/latest/documentation/configuration/egress-proxy/) page (tunnel vs route, IMPORTANT note, allowlist and route-mode examples), the [version table](https://agentgateway.dev/docs/kubernetes/main/reference/versions/), the [v1.6.0 release](https://github.com/agentgateway/agentgateway/releases/tag/v1.6.0) and PRs [#3237](https://github.com/agentgateway/agentgateway/pull/3237), [#3318](https://github.com/agentgateway/agentgateway/pull/3318), and [#3677](https://github.com/agentgateway/agentgateway/pull/3677), the [Kubernetes ambient-egress](https://agentgateway.dev/docs/kubernetes/latest/integrations/istio/ambient-egress/) guide, the Kubernetes [API](https://agentgateway.dev/docs/kubernetes/latest/reference/api/) `dynamicForwardProxy` warning, the [backend tunnel](https://agentgateway.dev/docs/kubernetes/main/integrations/llm/providers/backend-tunnel-proxy/) page, and the [kagent networking](https://kagent.dev/docs/kagent/1.x/substrate-runtime/networking-and-egress/) 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/`](/lab/agentgateway-egress-proxy/README.md). 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](/agent-egress-control-bypasses/) 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.*

