OpenWAF Egress reads one JSON file at startup (-config, config.json by default; the image passes -config /app/config.json). Changes need a restart. This page lists every field of pkg/egress/config.go in v0.1.1 and its default; a field you leave out takes the default.
Full example
The config.example.json of the repository, which is also the file built into the image:
{
"listen_addr": "0.0.0.0:8070",
"openbao": {
"addr": "https://vault.example.com:8200",
"ca_file": "",
"approle_file": "",
"kv_mount": "secret"
},
"targets": {
"openrouter": {
"base_url": "https://openrouter.ai/api/v1",
"host": "openrouter.ai",
"auth_header": "Authorization",
"auth_prefix": "Bearer "
},
"openai": {
"base_url": "https://api.openai.com/v1",
"host": "api.openai.com",
"auth_header": "Authorization",
"auth_prefix": "Bearer "
},
"anthropic": {
"base_url": "https://api.anthropic.com/v1",
"host": "api.anthropic.com",
"auth_header": "x-api-key",
"auth_prefix": ""
},
"huggingface": {
"base_url": "https://router.huggingface.co/v1",
"host": "router.huggingface.co",
"auth_header": "Authorization",
"auth_prefix": "Bearer "
}
},
"agents": {
"my-support-agent": {
"token": "OWE_TOKEN_SUPPORT_AGENT",
"allowed_targets": ["openrouter", "openai"],
"credential_mapping": {
"openrouter": { "path": "agents/support", "key": "OPENROUTER_API_KEY" },
"openai": { "path": "agents/support", "key": "OPENAI_API_KEY" }
},
"enforce_injection": true,
"block_dlp": true
}
}
}OWE_TOKEN_SUPPORT_AGENT is public: replace it with a long random value per agent.
The README adds a Gemini target, whose key goes in x-goog-api-key:
"gemini": {
"base_url": "https://generativelanguage.googleapis.com",
"host": "generativelanguage.googleapis.com",
"auth_header": "x-goog-api-key"
}Top level
| Field | Type | Default | What it does |
|---|---|---|---|
listen_addr | string | 127.0.0.1:8070 | Address and port the gateway listens on. The -addr flag overrides it. Inside a container use 0.0.0.0:8070 and publish the port on loopback or a private network only. |
openbao | object | (empty) | Connection to OpenBao or HashiCorp Vault. Without openbao.addr the gateway logs credential injection disabled and injects no keys. |
targets | object | (none) | Upstream APIs by alias. The alias is the <target> of the URL. |
agents | object | (none) | Agent policies by name. The name is the <agent> of the URL. |
openbao
| Field | Type | Default | What it does |
|---|---|---|---|
addr | string | (none) | Base URL of OpenBao or Vault, e.g. https://openbao.example.com:8200. Required for key injection. |
ca_file | string | system roots | PEM file with the CA that signed the server certificate. The connection requires TLS 1.3. |
approle_file | string | (none) | JSON file with {"role_id": "…", "secret_id": "…"}. Read at startup when role_id or secret_id is not set inline. |
role_id | string | (none) | AppRole role ID, inline. |
secret_id | string | (none) | AppRole secret ID, inline. Prefer approle_file so the secret stays out of the main configuration. |
kv_mount | string | kv | Mount of the KV v2 engine. A secret is read from GET /v1/<kv_mount>/data/<path>. Vault's default mount is secret. |
The gateway logs in at POST /v1/auth/approle/login and renews the token two minutes before its lease ends (one hour when the server returns no lease). Each secret is cached for five minutes, so a rotated key reaches the gateway within five minutes. Requests to OpenBao time out after ten seconds.
targets.<alias>
| Field | Type | Default | What it does |
|---|---|---|---|
base_url | string | (none) | Where requests go. The rest of the agent's path and its query string are appended: /v1/agent/a/proxy/openrouter/chat/completions → <base_url>/chat/completions. |
host | string | (none) | Host header sent upstream, and the name the SSRF check resolves. The connection itself goes to the host of base_url: set both to the same name. |
auth_header | string | Authorization | Header that receives the key. Anthropic uses x-api-key, Gemini x-goog-api-key. Keys in the query string (?key=) are not injected: use the header. |
auth_prefix | string | Bearer when auth_header is Authorization, otherwise empty | Text put before the key. An empty value with Authorization becomes Bearer. |
agents.<name>
| Field | Type | Default | What it does |
|---|---|---|---|
token | string | (none) | The agent's placeholder. When set, every request must carry Authorization: Bearer <token>, and that header is removed before forwarding. When empty, anyone who knows the agent's name can use it: always set one. |
allowed_targets | list of strings | [] | Target aliases this agent may call. "*" allows every target. With an empty list every request gets 403 egress_blocked. |
credential_mapping | object | {} | Target alias → {"path": "…", "key": "…"}: the secret path under kv_mount and the field inside it. A target without a mapping gets no injected key. |
enforce_injection | boolean | false | Run the prompt-injection detectors over the body; a hit gets 403 injection_detected. cost.amplification hits are ignored. |
block_dlp | boolean | false | Scan the body for card numbers, private keys and API keys; a hit gets 403 data_leak_blocked. |
With enforce_injection or block_dlp on, the text of the body must fit the 1 MiB inspection window. Inline media does not count: data:image/…;base64,… (also audio and video) URIs and JSON "data" members of at least 1 KiB of bare base64 (OpenAI input_audio, Gemini inlineData, Anthropic base64 sources) are forwarded but not inspected.
Command-line flags
| Flag | Default | What it does |
|---|---|---|
-config | config.json | Path to the configuration file. |
-addr | (none) | Overrides listen_addr. |
-version | Prints the version and exits. |
Fixed limits
These are constants in v0.1.1, not configuration:
| Limit | Value |
|---|---|
| Request body | 8 MiB (413 body_too_large) |
Inspection window, with enforce_injection or block_dlp | 1 MiB of text; data: media URIs and JSON "data" members of ≥ 1 KiB base64 excluded (413 body_too_large) |
| Detections returned per request | 16, each snippet at most 160 bytes |
| Secret cache | 5 minutes |
| OpenBao requests | 10 s timeout; 3 s for the /health check |
| Upstream | 10 s TLS handshake, 5 min until the provider's response headers, 15 min per exchange (streamed body included) |
| Gateway server | 10 s to read request headers, 120 s to read the request, 15 min to write the response, 120 s idle |
| Redirects | never followed: the 3xx goes back to the agent |
Written from github.com/openwaf-dev/[email protected] (external link).