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.

On this page

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 (external link). 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 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.

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:

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.

StepCheckOn failure
1Agent token: Authorization: Bearer <placeholder> must match the agent's token401
2Target allowlist: the target must be in the agent's allowed_targets403 egress_blocked
3SSRF: the target's host must resolve only to public addresses403 ssrf_blocked
4Body inspection: prompt-injection detectors (enforce_injection) and data-leak patterns (block_dlp)403 injection_detected, 403 data_leak_blocked, 413 body_too_large
5Key from OpenBao: read from KV v2 and set in the target's auth_header502 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 (external link).
  • 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 lists what it covers and what it does not.

Performance

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

BodyDetection time per request
4 KB chat request84 µs
64 KB chat request1.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

Written from github.com/openwaf-dev/[email protected] (external link).

Last updated:

↑↓ to navigate↵ to openesc to close