Policy-driven API gateway for AI agents, powered by Wallarm API Firewall.
This project keeps upstream API keys on the host and gives agents controlled access through:
- Agent-specific routes:
/<agent>/<api>/* - Agent tokens (
X-Agent-Tokenby default) - OpenAPI policy profiles (allowlist, default deny)
Result: one reusable firewall that supports different agent permissions without hardcoding provider logic.
Agent (no upstream keys)
-> Agent API Firewall (Caddy + Wallarm)
-> External APIs
Request flow:
- Route lookup by
/<agent>/<api>/* - Agent token validation
- Policy enforcement (if
specconfigured) - Upstream auth/header injection
- Forward to provider
git clone https://github.com/pvoo/agent-api-firewall.git
cd agent-api-firewall
cp .env.example .envPopulate .env:
- Upstream credentials (
INTERCOM_TOKEN,OPENAI_API_KEY, ...) - Agent tokens (
AGENT_SUPPORT_TOKEN,AGENT_SALES_TOKEN, ...)
Render and run:
./render
docker compose up -dOptional (shared host env + local config outside this repo):
FIREWALL_CONFIG_FILE=/path/to/local/config.yaml ./render
FIREWALL_ENV_FILE=/path/to/shared/.env docker compose up -dRun live tests:
./testconfig.yaml defines:
listen: proxy listen address (default::8282)network: Docker network name (default:vault)auth.header: request header used for agent tokenswallarm: firewall image and runtime tuningagents: token + API policy access per agentapis: upstream credentials/headers/query authapis.<api>.policies: named policy profiles (specoptional)apis.<api>.policies.<policy>.response_validation:BLOCK,LOG_ONLY(default), orDISABLEapis.<api>.policies.<policy>.team_scope: backward-compat team filter shorthand for Intercom-style specsapis.<api>.policies.<policy>.enum_overrides: generic enum constraint overrides for any spec schema
Renderer/runtime env options:
FIREWALL_CONFIG_FILE: use a custom config file path (default:config.yaml)FIREWALL_ENV_FILE: env file path injected into router service compose (default:.env)
Minimal example:
listen: ":8282"
network: vault
auth:
header: "X-Agent-Token"
wallarm:
image: wallarm/api-firewall:v0.9.5
server_read_buffer_size: 65536
agents:
support:
token: "${AGENT_SUPPORT_TOKEN}"
access:
- api: intercom
policy: support
sales:
token: "${AGENT_SALES_TOKEN}"
access:
- api: intercom
policy: support
apis:
intercom:
upstream: https://api.intercom.io
auth: "Bearer ${INTERCOM_TOKEN}"
headers:
Intercom-Version: "2.14"
policies:
support:
spec: specs/intercom-support.yaml
response_validation: LOG_ONLY
team_scope:
field: team_assignee_id
allowed_ids: [6979737, 7257201, 8589981]
allowed_names: [Support, Tickets, Test]If sales has access to intercom, call:
curl "http://localhost:8282/sales/intercom/me" \
-H "X-Agent-Token: $AGENT_SALES_TOKEN"No upstream key is sent by the agent. Caddy injects it from host env.
./test validates:
- Unknown routes return
404 - Missing/wrong token returns
401 - Correct token reaches authorized route
- Spec-backed policies block unknown endpoints (
403) - Cross-agent token isolation
./test uses a probe header (X-Agent-Firewall-Probe: 1) for deterministic auth checks without relying on upstream API behavior.
team_scope notes:
field: filter field required by the policy (for Intercom, usuallyteam_assignee_id)allowed_ids: integer allowlist used to render policy enum constraintsallowed_names(optional): human labels for docs/UI; defaults to stringified IDs
enum_overrides -- generic alternative to team_scope that works with any spec:
enum_overrides:
AllowedTeamId: # schema name under components.schemas
values: [6979737, 7257201] # required, sets .enum
names: [Support, Tickets] # optional, sets .x-enumNames
TeamFilterEquals.properties.field: # dots = path traversal in spec
values: [team_assignee_id]Processing order: team_scope expands first, then enum_overrides (overrides win on conflicts).
response_validation: controls how the Wallarm firewall handles API responses.
LOG_ONLY(default): log but pass through non-conforming responsesBLOCK: reject responses that don't match the specDISABLE: skip response validation entirely
specs/intercom-support.yaml:- Support-safe Intercom subset
- Team-scoped search constraints (rendered from
config.yamlteam_scope) - No bulk export/download/contact mutation
specs/openai-safe.yaml:- Models, chat completions, embeddings, responses
- Sensitive endpoints blocked by omission
.
├── config.yaml
├── .env.example
├── render
├── test
├── specs/
│ ├── intercom-support.yaml
│ └── openai-safe.yaml
├── Caddyfile # generated
└── docker-compose.yml # generated
- Default bind is localhost-only (
127.0.0.1) - Keep
.envprivate and out of version control - Prefer spec-backed policies for all sensitive APIs
- See
SECURITY.mdfor hardening guidance
MIT