OpenWAF Egress
Your AI agents never hold a provider key.
OpenWAF Egress sits between your agents and the model providers they call. Agents get a placeholder token. Every request is checked for prompt injection, data leaks and SSRF, and only then does the gateway fetch the real key from OpenBao or HashiCorp Vault, inject it and forward the request with streaming intact.
27 detectors, no model call
84 µs for a 4 KB chat request
One Go binary, image for amd64 and arm64
How it works
Five checks before the key leaves the vault
The agent keeps its usual SDK. Only the base URL and the key change.
Base URL http://127.0.0.1:8070/v1/agent/<agent>/proxy/<target> · key OWE_TOKEN_…
- 1
Agent token
401The agent sends its placeholder in
Authorization: Bearer. A missing or wrong token stops here. - 2
Target allowlist
403 egress_blockedThe target in the URL must be one of the agent’s
allowed_targets. - 3
SSRF check
403 ssrf_blockedThe target’s host must resolve only to public addresses.
- 4
Body inspection
403 injection_detected · data_leak_blockedPrompt-injection detectors and data-leak patterns run over the body, without calling a model.
- 5
Key from OpenBao
→ providerThe real key is read from KV v2, set on the outgoing request, and the response streams back to the agent.
Prompt injection
27 deterministic detectors, English and Spanish
Regular expressions aimed at instructions addressed to a model, not at topics: talking about prompts, shells or secrets does not match. A blocked request gets 403 injection_detected with the id, label and matched text of each hit.
21 pattern detectors
Overrides, jailbreaks, forged messages, tool abuse and exfiltration orders.
pi.override.enInstruction override (English)pi.override.esInstruction override (Spanish)pi.sysprompt.enSystem prompt extraction (English)pi.sysprompt.esSystem prompt extraction (Spanish)pi.jailbreak.personaJailbreak personapi.jailbreak.modeJailbreak modepi.fake_systemFake system messagepi.compliance_noticeFake compliance noticepi.concealConcealment from the userpi.chat_templateChat template tokenspi.fake_toolForged tool call or resultpi.fake_turnForged conversation turnspi.memoryPersistent memory poisoningpi.tool_invokeTool invocation orderpi.no_confirmApproval bypassagent.destructiveDestructive command orderexfil.pipe_shellRemote script piped to a shellexfil.env_secretsSecret harvestingexfil.host_dataHost data exfiltrationexfil.markdown_imageExfiltration through a rendered link or imagecost.amplificationSpend amplification order (reported, not refused)
6 detectors for hidden or encoded text
Instructions hidden from a human reader. Comments, CSS-hidden elements and encoded blobs are decoded and checked with the pattern detectors.
hidden.zero_widthHidden zero-width texthidden.tag_charsHidden Unicode tag characterspi.html_commentInstructions in an HTML commenthidden.css_textInstructions in CSS-hidden textpi.base64Encoded instructions (base64)pi.encodedEncoded instructions (percent or hex)
Detection is pattern matching: it stops known injection and exfiltration shapes, it does not prove a request is safe. The gateway refuses on every hit except cost.amplification, which it ignores. The same detector set protects AI endpoints at the OpenWAF edge.
Protections
Keys, data and destinations
Turn each check on per agent. Every refusal is a JSON error the agent can read.
- Keys injected from OpenBao or Vault
Agents carry an
OWE_TOKEN_…placeholder. After the checks, the gateway logs in with AppRole, reads the key from KV v2 (cached for 5 minutes) and sets it on the outgoing request only. - Per-agent allowlist
Each agent lists the targets it may reach in
allowed_targets. Any other configured target gets403 egress_blockedbefore the body is read. - Data-leak prevention
With
block_dlp, a body carrying a Luhn-valid card number, a PEM private key, an AWS access key ID, an OpenAI-stylesk-key or a GitHub token gets403 data_leak_blocked. - SSRF protection
Loopback, RFC 1918, CGNAT, link-local (including 169.254.169.254), multicast, reserved and their IPv6 equivalents are refused with
403 ssrf_blocked. - No redirects followed
An upstream
3xxgoes back to the agent as is, so the injected key never follows a redirect to a host outside the allowlist. - Whole bodies or nothing
Inline base64 images, audio and video (
data:URIs and the"data"fields of OpenAI, Gemini and Anthropic) are not inspected text. The rest must fit the 1 MiB window or the request gets413; nothing is forwarded uninspected or cut. Bodies are capped at 8 MiB.
Performance
Detection measured in microseconds
Time to run every detector over one request body, from the repository’s benchmarks.
- 84 µs4 KB chat request
- 1.3 ms64 KB chat request
- 97 ms64 KB built to trigger every detector (worst case)
Measured with go test -bench . ./pkg/aiagents/ on an Apple Silicon Mac (darwin/arm64). 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.
Quickstart
Running in two steps
Write a config.json with your targets and agents, then start the container and change the agent’s base URL.
- 1
Run the container on loopback
The image listens on 0.0.0.0:8070 inside the container. Publish it on 127.0.0.1 or a private network only.
docker run -d --name openwaf-egress -p 127.0.0.1:8070:8070 \ -v "$PWD/config.json:/app/config.json:ro" \ ghcr.io/openwaf-dev/egress:0.1.1 - 2
Point the SDK at the gateway
Any OpenAI-compatible SDK works: change the base URL and give it the placeholder token.
import OpenAI from "openai"; const client = new OpenAI({ baseURL: "http://127.0.0.1:8070/v1/agent/my-support-agent/proxy/openrouter", // placeholder; the real key never reaches the agent apiKey: "OWE_TOKEN_SUPPORT_AGENT", });
Next: the quickstart (OpenBao secret, configuration and a first request) and every configuration field.
Security notes
Where it stops
What to do when you deploy it, and what it does not claim.
Before you deploy
- The gateway speaks plain HTTP and agent tokens are static: keep it on loopback or a private network, or put TLS in front of it. Never expose it to the internet.
- Replace the example token
OWE_TOKEN_SUPPORT_AGENTwith a long random value per agent: the example one is public. - Give the gateway’s AppRole read access to the agent keys only.
- Detection raises the cost of known prompt-injection and exfiltration shapes; it does not prove a request is safe.
- Report vulnerabilities to [email protected].
FAQ
OpenWAF Egress questions
Does the agent ever see the provider key?
No. The agent only holds its OWE_TOKEN_… placeholder. The gateway reads the real key from OpenBao or Vault after the checks pass and sets it on the outgoing request. The provider’s response goes back to the agent; the key does not.
Does it call a model to detect prompt injection?
No. The detectors are deterministic regular expressions in English and Spanish, and each one only runs when one of its keywords is present. Detection takes 84 µs for a 4 KB chat request on an Apple Silicon Mac.
Which providers does it work with?
Any HTTP API you define as a target: a base URL, a host and the header that takes the key. The examples cover OpenRouter, OpenAI, Anthropic (x-api-key), Gemini (x-goog-api-key) and Hugging Face.
Does streaming still work?
Yes. The response is copied back to the agent as it arrives and flushed after every read, so streamed tokens reach the agent as the provider sends them.
What happens to large requests with images?
Inline base64 images, audio and video, as data: URIs or as the JSON data fields of OpenAI, Gemini and Anthropic, are left out of the inspected text and forwarded unchanged. The rest of the text must fit 1 MiB or the request gets 413, and no body may exceed 8 MiB.
Is it part of OpenWAF Cloud?
It is a separate open-source project under Apache-2.0 that you run yourself, next to your agents. It uses the same detector set as the AI-endpoint protection of the OpenWAF edge.
Take the keys away from your agents.
OpenWAF Egress is free and open source under Apache-2.0. Run the container next to your agents and point their SDK at it.


