agent-trust-firewall
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Agent Trust Firewall
A firewall for AI agents. It sits between an agent and the API (or other agent) it is calling, proves who is actually asking, scores how dangerous the request is, and blocks the bad ones before they execute.
Live: https://agentwall-pbl.vercel.app
The problem
AI agents now book, pay and run code on their own. When one is compromised — a leaked key, a poisoned prompt — its requests look exactly like a healthy agent's: same credentials, same endpoints, no alarm. An API key proves someone once had a secret; it does not prove that this request came from the agent you think it did, that the agent is allowed to do this, or that the request is not a replay of yesterday's.
Related MCP server: BlastRadius MCP
What it does
Every intercepted call must clear four checks before it runs:
Sealed in transit — the agent encrypts the call with a fresh key each time (X25519 → HKDF-SHA256 → XChaCha20-Poly1305). Fields it marks sensitive are encrypted to the destination and pass through the firewall unreadable.
Identity, proved not claimed — a Schnorr zero-knowledge proof of key possession. The key is never transmitted, and the proof is bound to one single-use challenge and one specific request, so it cannot be replayed or moved onto a different call.
Authority, minimally disclosed — a Merkle inclusion proof for the one capability this call needs. The firewall never learns the rest of the agent's grant set.
Risk, scored and explained — seven signals (identity, context, behaviour, rate, payload, reputation, live threat intel) produce a 0–100 score in which every point traces back to a named reason in plain language.
The outcome is ALLOW, FLAG (hold for a human) or BLOCK, and every decision is written to a SHA-256 hash-chained audit log.
Hard rules are not tunable. Unknown agent, revoked key, unverified proof, replayed challenge and ungranted capability block at every configuration. A confident prompt-injection or credential-leak detection forces at least a FLAG whatever the weighted score says.
Quick start
Requires Node.js 22.6 or newer (the verify, mcp and seed scripts run
TypeScript directly with --experimental-strip-types). Production runs on
Node 24.
npm install
npm run dev # http://localhost:3000It runs with no configuration at all: without DATABASE_URL it uses an
in-process store and seeds a demo fleet of six agents with half an hour of real
traffic on boot. /api/health tells you which store is live.
Using it
The console
Page | What it is for |
| What ATF is, with a live decision running in the hero |
| Live traffic: every decision, its score, and why |
| Run an adversary (forged identity, replay, revoked key, capability escalation, rate flood, off-baseline behaviour, prompt injection, credential exfiltration) through the real decision path and watch the verdict |
| The enrolled fleet, with live external threat intel per origin address |
| Move signal weights and thresholds and preview what that would have done to recent traffic. Sandboxed to your browser — it never changes anyone else's scoring, and cannot disable a hard rule |
| The hash-chained audit log and a live tamper check |
| Every term in plain language |
Calling it as an agent
An agent integrates with the SDK in src/lib/atf/client.ts. The flow, all
over plain HTTP:
import { generateKeyPair } from "./src/lib/atf/identity/schnorr.ts";
import { generateEncryptionKeyPair, open } from "./src/lib/atf/crypto/e2ee.ts";
import { signCall, sealCall } from "./src/lib/atf/client.ts";
const BASE = "http://localhost:3000";
const post = (path: string, body: unknown) =>
fetch(BASE + path, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
}).then((r) => r.json());
// 1. Enrol once. Only public keys are sent.
const signing = generateKeyPair();
const encryption = generateEncryptionKeyPair();
const capabilities = ["calendar:createEvent", "search:query"];
await post("/api/agents/register", {
agentId: "my-agent",
displayName: "My agent",
signingPublicKey: signing.publicKey,
encryptionPublicKey: encryption.publicKey,
capabilities,
});
// 2. Per call: get a single-use challenge, prove, seal, submit.
const { publicKey: gatewayKey } = await fetch(BASE + "/api/gateway/pubkey").then((r) => r.json());
const { nonce } = await post("/api/agents/challenge", { agentId: "my-agent" });
const call = signCall(
{ agentId: "my-agent", signingSecretKey: signing.secretKey, capabilities },
{
tool: "calendar.createEvent",
destination: "calendar.example.com",
capability: "calendar:createEvent",
args: { title: "Design review" },
nonce,
},
);
const { sealed } = await post("/api/gateway/intercept", {
envelope: sealCall(gatewayKey, call),
replyTo: encryption.publicKey, // the verdict comes back sealed to you
});
const verdict = open(encryption.secretKey, sealed); // { verdict: "ALLOW", score, explanation, ... }Capabilities are namespace:action and must match the tool exactly
(calendar:createEvent authorises calendar.createEvent) or by wildcard
(calendar:*). Holding calendar:write does not authorise
calendar.createEvent; that is a capability-escalation and is blocked.
Submitting { "call": … } instead of { "envelope": … } is accepted for
integrators who terminate encryption elsewhere; the response reports
transport: "plaintext" rather than implying protection that was not applied.
A BLOCK on the plaintext path returns HTTP 403.
API
Route | Method | Purpose |
| GET | Liveness, storage backend, audit chain validity |
| GET | Enrolled fleet with intel |
| POST | Enrol an agent (public keys + capabilities) |
| POST | Issue a single-use, agent-bound challenge |
| GET | The gateway's X25519 public key |
| POST | Submit a call for a decision |
| GET | Recent decisions and a summary |
| GET | Audit log; chain verification |
| GET / PUT / DELETE | Read, set or clear your risk-config sandbox |
| POST | Re-score recent traffic against a proposed config |
| GET / POST | List or run attack-range scenarios |
| POST | Show a destination-sealed field passing through unread |
| POST | Threat intel for a public IPv4 address |
Every route applies, in order, an origin check (cross-site browser requests are refused), a per-client rate limit, and Zod schema validation of the body.
The MCP server
npm run mcpA real Model Context Protocol server over stdio (JSON-RPC 2.0, one message per
line). Every tools/call is routed through the same decide() path as the HTTP
gateway; a blocked call returns a structured refusal with isError: true and
the upstream tool is never invoked. To use it from an MCP client, point the
client at:
{
"command": "node",
"args": ["--experimental-strip-types", "src/lib/atf/mcp/server.ts"],
"cwd": "/path/to/agent-trust-firewall"
}The server fronts three demo tools (search.query, payments.charge,
storage.read) and runs as a demo agent holding a subset of capabilities, so
you can see one call allowed and another blocked. It keeps its own in-process
store.
Architecture
Three separable layers. The risk engine knows nothing about HTTP, and nothing
in src/lib/atf/ imports Next.js.
agent ──sealed envelope──▶ /api/gateway/intercept (or MCP tools/call)
│
▼
src/lib/atf/interceptor/decide.ts
open envelope → verify identity → spend nonce → prove capability
→ build context → score → apply policy → audit
│
▼
ALLOW / FLAG / BLOCK + hash-chained audit recordEvery step is fail-closed: an error anywhere produces a BLOCK with a stated reason, never a silent pass.
Path | Role |
| Schnorr NIZK, Merkle capability proofs, single-use nonces, agent registry |
| Sealed envelopes and destination-sealed fields |
| One file per signal, plus a pure weighted engine |
| The decision path, hard rules and escalations |
| GreyNoise, Shodan InternetDB, AbuseIPDB, OTX — cached, SSRF-guarded |
| SHA-256 hash chain; |
| Storage port with in-memory and Postgres implementations |
| The MCP stdio server |
| Agent-side SDK |
| Origin check, rate limit, body validation, opaque errors |
| Security headers and a per-request CSP nonce |
| Next.js App Router pages and API routes |
claude.md has the long-form architecture and security model.
Configuration
Nothing is required. Every variable is optional and server-side only — never
prefix any of them with NEXT_PUBLIC_. See .env.example.
Variable | Effect when set |
| Durable Postgres (Neon) storage instead of the in-process store |
| Derives the demo fleet's keys deterministically. Required with a shared database, so every instance signs with the keys that are enrolled |
| Pins the gateway's X25519 keypair (64 hex chars). Set it on any multi-instance deployment — otherwise each instance has its own key and an envelope sealed to one cannot be opened by another |
| Adds AbuseIPDB to threat intel |
| Adds AlienVault OTX to threat intel |
Generate the two secrets with openssl rand -hex 32.
Deployment (Vercel)
vercel link # once
vercel integration add neon # provisions Postgres, injects DATABASE_URL
vercel env add ATF_DEMO_SEED production
vercel env add ATF_GATEWAY_SECRET_KEY production
vercel env pull .env.local
npm run seed # once per fresh database: ~500 statements, ~45s
vercel --prodSeeding is a deployment step rather than something the app does on demand.
Every seeded call is a real challenge, a real Schnorr proof and a real scoring
pass; running that inside a request would put tens of seconds of database
round-trips on a cold start. npm run seed is safe to re-run — it does nothing
if the store already holds traffic. With Postgres configured, the app registers
the demo fleet on boot but never seeds traffic itself.
vercel.json pins the region to bom1. Every page renders dynamically so the
CSP can carry a per-request nonce.
Verifying the claims
npm run lint
npm run typecheck
npm run test # 172 tests: crypto, signals, engine, interceptor, audit, MCP, guards, scenarios, OSINT
npm run verify # 25 security invariants, printed one per line
npm run build
npm audit --omit=dev # 0 vulnerabilities in shipped dependenciesnpm run verify checks the load-bearing properties directly, outside the test
runner: an unenrolled agent is blocked, a replayed proof is blocked, a proof
cannot be moved onto a different request, a hard rule cannot be tuned away at
any configuration, the firewall cannot open a destination-sealed field, an
altered or deleted audit record is detected, and no argument value or secret key
is ever retained.
A full npm audit (including dev tooling) currently reports advisories in
braces, reached only through eslint-config-next at lint time. No patched
release exists yet; none of it ships in the application.
Security
No secret leaves the server. The gateway's X25519 secret lives only in
gateway.ts; agent secret keys never enter the system, and the protocol has no field that could carry one.The audit log stores digests, not payloads.
npm run verifyasserts this with a canary value.Opaque errors. Unexpected failures return a reference id, and every envelope failure returns one identical message, so a prober gets no decryption oracle.
Strict CSP. Nonce-based
script-srcwith nounsafe-inlineand no external origins, plus HSTS,frame-ancestors 'none',nosniff,no-referrer, COOP/CORP.SSRF-guarded intel. Lookups accept public IPv4 only — private, loopback, link-local (including cloud metadata), CGNAT and reserved ranges are refused.
Bounded work. Argument traversal, Merkle path length, request bodies (512 KB), the in-memory store and rate-limit buckets are all capped.
See security.md for the posture and how to report a vulnerability,
docs/security.md for the threat model, and docs/decisions/ for why Schnorr
rather than zkSNARKs, and why hard rules sit outside the score.
Troubleshooting
Symptom | Cause and fix |
|
|
Console is empty on a Postgres deployment | The database has not been seeded. Run |
Attack-range calls intermittently fail with |
|
An agent gets |
|
A legitimate call is blocked with | The proved capability does not match the tool. Grant |
| Each challenge is single-use and expires. Request a fresh one per call |
| A browser on another origin called a state-changing route. Call from the same origin or from a server (no |
| Per-client, per-route rate limit. Wait a minute |
Intel rows say synthetic only | Providers were unreachable or timed out (4s). That is reported, never shown as a clean result |
| Node is older than 22.6. Upgrade Node |
Honest scope
The demo fleet is synthetic, but its keys, proofs, verdicts and origin addresses are real — the addresses are live cloud hosts and the intelligence shown about them is fetched at request time. Rate limiting is per instance. Payload inspection is heuristic, which is why it escalates to FLAG rather than blocking. And a firewall must read a request to score it, so ATF is a trusted endpoint for the outer envelope; only destination-sealed fields are opaque to it.
License
MIT — see LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Zero-trust gateway for AI agents: score tool calls, verify agent cards, enforce policy, audit.
Context and control for any AI. Agent identity, scoped access, and proof across tools.
Deterministic runtime safety for AI agents: scan PII, gate tool actions, verify LLM output.
Security gateway for AI agents: policy, approval, and audited execution, no secrets shared.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceDeterministic policy enforcement for AI agent tool calls. It evaluates every tool call against user-defined rules before execution, with no LLM in the authorization path.4MIT
- AlicenseAqualityBmaintenanceEnables agents to score, inspect, and gate tool calls before they execute, resolving shell commands the way a shell would in order to block destructive actions and detect multi-step attack sequences. It also redacts credentials and PII in either direction, requires signed single-use approval tokens for high-impact actions, and keeps a tamper-evident audit trail.611 npmApache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to securely reach enterprise tools and other agents through a governed gateway with pinned tool definitions, delegated authorization, content inspection, human approval, and signed audit trails.Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to safely invoke enterprise tools such as Supabase PostgreSQL and the Notion API through a zero-trust gateway that performs server-side policy and risk evaluation, cryptographically binds approval tickets to exact parameters, and requires out-of-band human approval for destructive operations. It also scrubs credentials from logs and responses while providing audit trails, rate limiting, and a web console for reviewing pending authorizations.Apache 2.0