One YAML schema, with env-var overrides.
DFENS is configured from a single YAML file, layered over built-in defaults, with any value overridable from the environment. The image ships a minimal working config so an out-of-the-box container runs with sensible defaults.
How configuration loads
Three layers are merged, later winning over earlier:
- Built-in defaults. Every key has one, so a config file may set only what it changes.
- YAML file. Passed with
serve --config <path>. The image's entrypoint points this at/etc/ai-firewall/config.yaml. - Environment variables. Override any key (see below).
Unknown or misspelled YAML keys are silently ignored rather than rejected. Double-check key names against this page when a setting seems to have no effect.
A representative config.yaml:
server: listen: ":8080" state_dir: "/data" # must be writable — see deployment rules: dir: "/etc/ai-firewall/rules.d" registry_path: "/etc/ai-firewall/registry.yaml" engine: block_response: error # or "soft" audit: sink: stdout # stdout | file | both auth: mode: passthrough # or "vault" providers: openai: enabled: true anthropic: enabled: true metrics: enabled: true listen: ":9090" # MUST differ from server.listen admin: token_env: "AI_FIREWALL_ADMIN_TOKEN" healthz_path: "/health"
Environment-variable overrides
Any key maps to an environment variable: prefix AI_FIREWALL_, and a
double underscore (__) for each level of nesting. The double
underscore is needed because key names themselves contain single underscores.
| Config key | Environment variable |
|---|---|
server.listen | AI_FIREWALL_SERVER__LISTEN |
server.max_request_body_bytes | AI_FIREWALL_SERVER__MAX_REQUEST_BODY_BYTES |
auth.mode | AI_FIREWALL_AUTH__MODE |
providers.openai.upstream | AI_FIREWALL_PROVIDERS__OPENAI__UPSTREAM |
admin.healthz_path | AI_FIREWALL_ADMIN__HEALTHZ_PATH |
AI_FIREWALL_ADMIN_TOKEN (single underscore) is not a config
override. It is the value whose variable name is what
admin.token_env points at. To override the token's variable name you'd set
AI_FIREWALL_ADMIN__TOKEN_ENV (double underscore). Keep the two straight:
token_env names the variable; the named variable holds the secret.
server
| Key | Default | Purpose |
|---|---|---|
listen | :8080 | Proxy + inspect listen address. |
read_timeout | 30s | Request read timeout. |
write_timeout | 600s | Response write timeout (long, for streaming). |
idle_timeout | 120s | Keep-alive idle timeout. |
max_request_body_bytes | 1048576 | 1 MiB cap; larger bodies get 413. |
state_dir | /var/lib/ai-firewall | Writable dir for the audit sequence counter, ruleset cache, and disable overlay. |
state_dir must be writable by the process. In containers with a read-only
root filesystem, set it to a mounted writable path (the deployment examples use
/data). If it is empty or unwritable, the audit sequencer generates a new
host identity each start instead of persisting one.
auth (upstream)
auth.mode controls how DFENS authenticates to the upstream provider.
This is separate from inbound client authentication below.
passthrough(default). The caller sends its own upstream credential (e.g.Authorization: Bearer …) and DFENS forwards it untouched. Keys stay with the caller.vault. DFENS injects the provider credential itself, read from the environment at request time, so callers never hold the key. Provider defaults:OPENAI_API_KEY,ANTHROPIC_API_KEY,GEMINI_API_KEY,AZURE_API_KEY(or Entra ID), and the AWS credential chain for Bedrock (SigV4). Each variable name is configurable underauth.vault.
Client authentication (inbound)
DFENS can require callers to authenticate to the proxy before any request reaches
a provider route or /v1/inspect. It's controlled by
admin.client_token_env, the name of an environment variable holding the token:
admin: client_token_env: "AI_FIREWALL_CLIENT_TOKEN" # names the env var… # …and the value lives in the environment, out of the config file export AI_FIREWALL_CLIENT_TOKEN=$(openssl rand -hex 32)
Callers then present the token in a dedicated header:
curl -X POST http://dfens.internal:8080/v1/inspect \
-H "X-AI-Firewall-Key: $AI_FIREWALL_CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content":"hi"}'
- Off by default, backward compatible. When the token resolves to empty, the data plane accepts unauthenticated traffic. Enabling it is opt-in.
- Works in any upstream mode. Independent of
auth.mode; you can require a client token in passthrough mode too. - Never leaked upstream. DFENS strips
X-AI-Firewall-Keybefore forwarding, so it never reaches the model provider (it uses a dedicated header rather thanAuthorizationprecisely to avoid colliding with the upstream key). - Per-tenant. Each tenant can carry its own token via
tenants.<name>.client_token_env, falling back to the global one. A tenant's token is rejected on another tenant's traffic. - Audited & rate-limited. Failed attempts emit an
ingress.authaudit event (with a token fingerprint, never the raw token) and are rate-limited per IP viaadmin.client_auth_rate_limit_per_minute(default 60); successful traffic is never throttled.
/health and the /admin/* control plane are not affected by client
auth: health is always unauthenticated, and admin has its own separate token.
providers
Each provider block controls whether its adapter is active and where clean traffic is
forwarded. Enabling a provider requires an absolute scheme://host upstream.
| Provider | enabled | upstream default |
|---|---|---|
openai | true | https://api.openai.com |
anthropic | true | https://api.anthropic.com |
gemini | true | https://generativelanguage.googleapis.com |
azure | false | — (set your resource endpoint) |
bedrock | false | — (set endpoint + region) |
Per-provider upstream_timeout defaults to 60s for the enabled three.
rules & engine
| Key | Default | Purpose |
|---|---|---|
rules.dir | /etc/ai-firewall/rules.d | Directory of .conf rule files. |
rules.registry_path | /etc/ai-firewall/registry.yaml | Rule-ID registry. |
engine.block_response | error | error (403) or soft (200 synthetic turn). |
engine.block_message | — | Assistant text returned in soft mode. |
engine.dos_action | block | What to do when the per-request evaluation budget is exceeded. |
The image bakes in the default rule set and registry. To run your own rules, mount a
directory over rules.dir (see Deployment).
audit
Every block decision emits a structured JSONL audit event naming the firing rule, its severity, phase, and matched snippet.
audit.sink:stdout(default),file, orboth.audit.file_path:/var/log/ai-firewall/audit.jsonlwhen the sink writes to a file.
stdout is the right default in containers: your existing log pipeline
(Fluent Bit, Vector, Loki, Cloud Logging) is the durable record.
metrics
DFENS exposes Prometheus metrics on a separate listener so the metrics surface is never served on the same port as proxy traffic.
| Key | Default |
|---|---|
metrics.enabled | true |
metrics.listen | :9090 |
metrics.path | /metrics |
metrics.listen must differ from server.listen. The process
refuses to start otherwise. This keeps the metrics endpoint off the proxy port by
construction.
admin
The control-plane endpoints (/admin/*: disable/enable a rule at runtime,
list state) are token-authenticated and off by default.
| Key | Default | Purpose |
|---|---|---|
admin.token_env | AI_FIREWALL_ADMIN_TOKEN | Name of the env var holding the admin bearer token. |
admin.client_token_env | AI_FIREWALL_CLIENT_TOKEN | Env var for the inbound client token (data-plane auth). |
admin.client_auth_rate_limit_per_minute | 60 | Per-IP limit on failed client-auth attempts. |
admin.healthz_path | /health | Health endpoint path. |
admin.rate_limit_per_minute | 30 | Per-IP rate limit on admin calls. |
If the token variable is unset, every /admin/* route returns 404
rather than 401: an unconfigured admin surface is invisible, not just closed.
Set the token only where you need runtime rule control, and treat it as a high-value secret.
Next: Deployment. Docker, Compose, and Kubernetes.