# OpenWAF Egress

> A zero-trust outbound gateway for AI agents that checks every request and injects the provider key, self-hosted or hosted by OpenWAF Cloud.

OpenWAF Egress is a gateway between your AI agents and the model providers they call. Agents hold a placeholder token (`OWE_TOKEN_…`) instead of the provider key. The gateway checks every request and only then reads the real key from OpenBao or HashiCorp Vault, sets it on the outgoing request and forwards it, with streaming intact.

It is open source under the Apache License 2.0: [github.com/openwaf-dev/egress](https://github.com/openwaf-dev/egress). The image `ghcr.io/openwaf-dev/egress` is built for linux/amd64 and linux/arm64, and each release also ships Linux and macOS binaries with `SHA256SUMS`.

## Self-hosted or hosted

The same gateway runs in two ways:

- **Self-hosted:** you run the container next to your agents, with your own configuration file and your own OpenBao or Vault. The [quickstart](https://developers.openwaf.dev/egress/quickstart/) and the pages that follow it describe this mode.
- **Hosted by OpenWAF Cloud:** your agents call `https://egress.openwaf.dev`; you manage agents, provider keys and blocked requests from the dashboard, the API or the MCP server, and OpenWAF stores the keys. It is in early access. See [get started with hosted egress](https://developers.openwaf.dev/egress/cloud/get-started/).

## Why a gateway

An agent that holds a provider key can leak it: through a prompt injection that asks it to print its environment, a tool call that sends data out, or a compromised dependency in its container. With OpenWAF Egress the key never enters the agent's process. A stolen placeholder only works through the gateway, for the targets that agent is allowed, and only for requests that pass the checks.

## How a request flows

The agent keeps its SDK. Only the base URL and the key change:

```text
baseURL = http://127.0.0.1:8070/v1/agent/<agent>/proxy/<target>
apiKey  = OWE_TOKEN_…
```

Each request goes through five steps, in this order. The first one that fails answers with an error and nothing is sent upstream.

| Step | Check | On failure |
|---|---|---|
| 1 | Agent token: `Authorization: Bearer <placeholder>` must match the agent's `token` | `401` |
| 2 | Target allowlist: the target must be in the agent's `allowed_targets` | `403 egress_blocked` |
| 3 | SSRF: the target's `host` must resolve only to public addresses | `403 ssrf_blocked` |
| 4 | Body inspection: prompt-injection detectors (`enforce_injection`) and data-leak patterns (`block_dlp`) | `403 injection_detected`, `403 data_leak_blocked`, `413 body_too_large` |
| 5 | Key from OpenBao: read from KV v2 and set in the target's `auth_header` | `502 credential_resolution_failed` |

Then the gateway forwards the request to the target's `base_url` and streams the response back. Upstream redirects are never followed: a `3xx` goes back to the agent as it is.

## Routes

- `/v1/agent/<agent>/proxy/<target>/<path>`: the agent is named in the URL. This is the route to use.
- `/v1/proxy/<target>/<path>`: the agent is the one whose `token` matches the `Authorization` header, or the one named in an `X-OpenWAF-Agent` header (the gateway removes that header before forwarding).
- `/health` and `/v1/health`: status of the gateway and of its OpenBao connection, as JSON.

`<path>` and the query string are appended to the target's `base_url`. With `base_url` `https://openrouter.ai/api/v1`, a request to `/v1/agent/my-support-agent/proxy/openrouter/chat/completions` goes to `https://openrouter.ai/api/v1/chat/completions`.

## What it detects

- **Prompt injection**: 27 deterministic detectors in English and Spanish, 21 pattern detectors and 6 for hidden or encoded text. No model call. The full list is on the [product page](https://openwaf.dev/egress/#detectors).
- **Data leaks**: Luhn-valid payment card numbers, PEM private keys, AWS access key IDs, OpenAI-style `sk-` keys and GitHub tokens.
- **SSRF**: loopback, RFC 1918, CGNAT, link-local (including `169.254.169.254`), multicast and reserved ranges, and their IPv6 equivalents.

Detection is pattern matching: it stops known injection and exfiltration shapes, it does not prove a request is safe. The [security model](https://developers.openwaf.dev/egress/security-model/) lists what it covers and what it does not.

## Performance

Measured with `go test -bench . ./pkg/aiagents/` on an Apple Silicon Mac (darwin/arm64):

| Body | Detection time per request |
|---|---|
| 4 KB chat request | 84 µs |
| 64 KB chat request | 1.3 ms |
| 64 KB built to trigger every detector (worst case) | 97 ms |

Each detector only runs when one of its keywords is present, so ordinary traffic skips almost all of them. The static binary is about 7 MB.

## Next steps

- [Quickstart](https://developers.openwaf.dev/egress/quickstart/): store a key in OpenBao, write the configuration, run the container and send a first request.
- [Configuration reference](https://developers.openwaf.dev/egress/configuration/): every field of the self-hosted gateway and its default.
- [Troubleshooting](https://developers.openwaf.dev/egress/troubleshooting/): every error the gateway returns.
- [Hosted egress](https://developers.openwaf.dev/egress/cloud/get-started/): create an agent at egress.openwaf.dev from the dashboard, the API or MCP.


Written from [github.com/openwaf-dev/egress@v0.1.1](https://github.com/openwaf-dev/egress/tree/v0.1.1).
