Agentic Firewall v2
MCP Policy Gateway & Independent OWASP Red-Team Security Benchmark
Inspired by Anthropic's Security Research
The Problem
Recent security research revealed a critical vulnerability in AI coding environments: autonomous AI agents can be compromised when they connect to unverified tools and execute requests without human oversight. The attack succeeded because there was no governance layer to validate intent before execution.
The Solution
Agentic Firewall v2 is a runtime security middleware and red-team benchmark harness designed to secure Model Context Protocol (MCP) applications. It acts as an intercepting proxy between an MCP client (AI agent) and any MCP server — enforcing least-privilege tool policies, pinned session identity, output guard canary scanning, and a counter-based circuit breaker.
Why This Matters
In an era where AI agents are becoming increasingly autonomous, security can't be an afterthought. Agentic Firewall provides the governance layer needed to safely leverage AI assistance without sacrificing control — validating every tool call, logging every decision, and suspending sessions that exhibit rogue behavior.
1. System Architecture
The gateway acts as an intercepting proxy between the MCP Client (Agent) and the MCP Server. It intercepts and filters tools/call JSON-RPC requests, while passing other messages (initialize, etc.) through safely.
Supported Transports & Connection Modes
- Client-to-Gateway Transports:
- HTTP/SSE: Standard Model Context Protocol SSE stream (e.g.
GET /sse?identity=alice). - WebSocket: Full-duplex connection via
ws://to/ws?identity=aliceWebSocket endpoint.
- HTTP/SSE: Standard Model Context Protocol SSE stream (e.g.
- Gateway-to-Server Connection Modes:
- Stdio Subprocess Proxy (Local): Spawns the backend MCP server as a local subprocess directly (e.g. SQLite, Git, or filesystem servers), proxying communication through standard input/output pipes (
stdin/stdout). Configure usingFW_REAL_SERVER_CMD. - HTTP/SSE Proxy (Remote): Proxies JSON-RPC messages over HTTP/SSE network endpoints to an external backend server. Configure using
FW_REAL_SERVER_URL.
- Stdio Subprocess Proxy (Local): Spawns the backend MCP server as a local subprocess directly (e.g. SQLite, Git, or filesystem servers), proxying communication through standard input/output pipes (
┌────────────────────────────────────────────────────────────────┐
│ MCP Client (AI Agent) │
└───────────┬──────────────────────────────────────▲────────────┘
│ 1. HTTP/SSE or WebSocket connection │ 4. Events / Responses
▼ │
┌───────────────────────────────────────────────────────────────┐
│ POLICY GATEWAY │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ → Pinned Session Identity Verification │ │
│ │ → Pydantic Policy Engine (per-identity allow-list) │ │
│ │ → Argument Constraint Enforcement (sandbox paths etc.) │ │
│ │ → Output Guard Canary Scanner (data egress detection) │ │
│ │ → Counter-Based Circuit Breaker (session suspension) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ AUDIT LOG ↓ │
│ src/gateway/gateway_audit.log (JSONL) │
└───────────┬──────────────────────────────────────▲────────────┘
│ 2. Local Subprocess Stdio (stdin) │ 3. Stdout / SSE events
│ or Remote SSE GET /sse │
▼ │
┌────────────────────────────────────────────────────────────────┐
│ BACKEND MCP SERVER │
│ shell exec · file read/write · outbound HTTP │
└────────────────────────────────────────────────────────────────┘
Core Protection Layers
- Least-Privilege Pydantic Policies: Evaluates incoming tool requests against a strict
identity -> allowed_tools -> arg_constraintsschema. Out-of-bounds paths or unallowed tools are blocked instantly. - Pinned Session Identity Verification: Binds the identity verified at session startup to all incoming messages on that session ID, completely mitigating session identity pollution and parameter tampering attacks.
- Outbound Output Guard Canary Scanner: Scans all tool response texts for sensitive canary patterns—such as Linux shadow databases, private SSH keys, cloud tokens, and system INI files—blocking leaks at the proxy level.
- Stateful Circuit Breaker: Tracks security flags per session. If a session triggers the Output Guard 3 consecutive times, the gateway suspends the session and rejects all subsequent execution requests.
- JSON Lines Audit Logging: Logs every execution step, request details, and security decision in a structured JSON Lines format to
src/gateway/gateway_audit.log.
2. Directory Structure
The codebase is organized into a modular, clean directory layout:
src/
gateway/
mcp_gateway.py # Gateway server router (HTTP/SSE and WebSockets)
state.py # Log configurations, circuit breaker & session state
transports.py # Subprocess piping and stream reader loops
mock_server.py # Mock tool execution fallback simulation
policy_v2.json # Pydantic Policy Schemes Configuration
toy_server/
toy_server.py # Target MCP Vulnerability Testbed Server
benchmarking/
attack_harness.py # Red-Team OWASP benchmark runner
security/
policy_engine.py # Pydantic Policy Enforcement Engine
output_guard.py # Canary scanner output filter
tests/
test_gateway.py # Gateway integration tests
test_output_guard.py # Output guard unit tests
test_policy.py # Policy unit tests
test_proxy.py # Gateway-to-Backend proxy tests
test_transports.py # SSE & WebSocket connection transport tests
legacy/
mcp_governor.py # Legacy Phase 0 controller
policy.json # Legacy Phase 0 policy
test.py # Legacy Phase 0 test script
3. OWASP ASI Red-Team Benchmark Results
The gateway was evaluated against an independent red-team harness executing 17 attack scenarios mapped to OWASP's Top 10 for Agentic Applications (published Dec 2025). The benchmark compares standalone server performance (Baseline) against gateway-protected execution.
Scoring Table:
| OWASP Category | Attack ID | Description | Standalone (Baseline) | Gateway (Protected) | Outcome |
|---|---|---|---|---|---|
| ASI02: Tool Misuse | 1 | Absolute Path Traversal via read_file |
Exploited | Blocked | PASS |
| ASI02: Tool Misuse | 2 | Relative Path Traversal via read_file |
Exploited | Blocked | PASS |
| ASI02: Tool Misuse | 3 | Unauthorized File Modification via write_file |
Exploited | Blocked | PASS |
| ASI02: Tool Misuse | 4 | SQL Injection on query_database |
Exploited | Blocked | PASS |
| ASI03: Privilege Abuse | 5 | Bob attempts to run execute_command |
Exploited | Blocked | PASS |
| ASI03: Privilege Abuse | 6 | Unregistered identity charlie calls read_file |
Exploited | Blocked | PASS |
| ASI04: Supply Chain & SSRF | 7 | SSRF / Cloud Metadata Leak via fetch_url |
Blocked | Blocked | PASS |
| ASI04: Supply Chain & SSRF | 8 | External Data Exfiltration via fetch_url |
Exploited | Blocked | PASS |
| ASI05: Unexpected Code Exec | 9 | Command Shell Injection via execute_command |
Exploited | Blocked | PASS |
| ASI05: Unexpected Code Exec | 10 | Command Chaining via execute_command |
Exploited | Blocked | PASS |
| ASI05: Unexpected Code Exec | 11 | Execution of Dropped Script via execute_command |
Exploited | Blocked | PASS |
| ASI06: Context Poisoning | 12 | Indirect Prompt Injection - file contains canary | Exploited | Blocked | PASS |
| ASI06: Context Poisoning | 13 | Indirect Prompt Injection - SSRF contains canary | Exploited | Blocked | PASS |
| ASI06: Context Poisoning | 14 | Indirect Prompt Injection - Database contains canary | Exploited | Blocked | PASS |
| ASI10: Rogue Agents | 15 | Circuit Breaker Suspension after 3 flags | Exploited | Blocked | PASS |
| ASI10: Rogue Agents | 16 | Unauthorized Flood lockouts | Exploited | Blocked | PASS |
| ASI10: Rogue Agents | 17 | Session identity pollution check | Exploited | Blocked | PASS |
Summary Score: 17/17 attacks caught (100%)
4. Documented Limitations
The Agentic Firewall v2 focuses on system-level tool execution boundaries and data egress protection. It does not defend against the following OWASP categories:
- ASI01: Goal Hijacking: Reasoning-layer manipulation (such as complex chain-of-thought hijacking) must be mitigated by LLM system prompt engineering, context pruning, or model-side evals, not a proxy gateway.
- ASI07: Inter-Agent Communication: The gateway is scoped strictly to single-agent-to-server topologies. It does not validate or block collaborative agent-to-agent message payloads.
- ASI08: Cascading Failures: Defending against chained agent operation failures requires transactional rollbacks across state boundaries, which lies outside the firewall scope.
- ASI09: Human-Agent Trust Exploitation: Deceptive agent behavior targeting human users falls under client UI design constraints.
5. Getting Started
Installation
Clone the repository and install dependencies using uv (recommended):
uv venv
.venv\Scripts\activate
uv pip install fastapi uvicorn pydantic httpx pytest pytest-asyncio anyio
Running Tests
Execute the pytest suites:
$env:PYTHONPATH=".;src"
uv run pytest src
Running the Red-Team Benchmark
Run the OWASP attack harness comparing baseline and protected servers:
# PowerShell
$env:PYTHONPATH=".;src"
uv run src/benchmarking/attack_harness.py
# Git Bash
PYTHONPATH=".;src" uv run src/benchmarking/attack_harness.py
6. Manual Testing & Verification
You can manually inspect proxy routing, path-traversal blocking, and session pollution prevention using standard shell clients or the official MCP Inspector.
Scenario Startup
Start the target vulnerabilities testbed and policy gateway:
- Start Toy Server (Terminal 1):
- PowerShell:
$env:PYTHONPATH=".;src"; uv run uvicorn src.toy_server.toy_server:app --port 8000 - Git Bash:
PYTHONPATH=".;src" uv run uvicorn src.toy_server.toy_server:app --port 8000
- PowerShell:
- Start Gateway Server (Terminal 2):
- PowerShell:
$env:PYTHONPATH=".;src"; $env:FW_REAL_SERVER_URL="http://127.0.0.1:8000"; uv run uvicorn src.gateway.mcp_gateway:app --port 8001 - Git Bash:
PYTHONPATH=".;src" FW_REAL_SERVER_URL="http://127.0.0.1:8000" uv run uvicorn src.gateway.mcp_gateway:app --port 8001
- PowerShell:
Option A: Testing via Git Bash Command Line
Open a client session stream in one window, and send requests in another:
- Open SSE client connection (Terminal 3):
curl "http://127.0.0.1:8001/sse?identity=alice&session_id=session_abc" - Execute Tool Calls (Terminal 4):
- Scenario 1: Authorized File Read (Allow):
curl -X POST "http://127.0.0.1:8001/message?session_id=session_abc&identity=alice" -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "read_file", "arguments": {"path": "src/gateway/policy_v2.json"}}}' - Scenario 2: Path Traversal (Block):
curl -X POST "http://127.0.0.1:8001/message?session_id=session_abc&identity=alice" -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "read_file", "arguments": {"path": "C:\\Windows\\win.ini"}}}' - Scenario 3: Session Identity Pollution (Block):
curl -X POST "http://127.0.0.1:8001/message?session_id=session_abc&identity=bob" -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "read_file", "arguments": {"path": "src/gateway/policy_v2.json"}}}'
- Scenario 1: Authorized File Read (Allow):
- Verify logs on disk:
- Open the active audit logs at src/gateway/gateway_audit.log to see the JSON-structured decision logs.
Option B: Interactive Verification via MCP Inspector
The official MCP Inspector acts as a web client UI over SSE to view and trigger tool executions:
- Launch the Inspector:
npx -y @modelcontextprotocol/inspector http://127.0.0.1:8001/sse?identity=alice - Connect to SSE:
- In the top-left sidebar of the web page, change Transport Type from
STDIOtoSSE. - Ensure the target URL is set to
http://127.0.0.1:8001/sse?identity=alice. - Click Connect.
- In the top-left sidebar of the web page, change Transport Type from
- Trigger Tool Call Evals:
- Under the Tools tab, you will only see tools allowed for
alice(least-privilege tool list filtering). - Call
read_filewith pathsrc/gateway/policy_v2.jsonto verify successful execution. - Call
read_filewith path/etc/passwdto observe the immediateARG_CONSTRAINT_VIOLATIONsecurity block.
- Under the Tools tab, you will only see tools allowed for
Option C: Local Stdio Subprocess Proxying
This mode spawns the backend MCP server as a local subprocess, communicating directly via stdin/stdout pipes.
[!NOTE] Since the gateway communicates with the subprocess via stdio pipes, the command target must be a valid stdio-based MCP JSON-RPC server (not an HTTP/SSE app like
toy_server.py).
-
Create Stdio Test Server (
stdio_server.py) in your project root:# stdio_server.py import sys import json for line in sys.stdin: line = line.strip() if not line: continue try: msg = json.loads(line) except Exception: continue msg_id = msg.get("id") method = msg.get("method") if method == "tools/list": resp = { "jsonrpc": "2.0", "id": msg_id, "result": { "tools": [ {"name": "read_file", "description": "Read file content"}, {"name": "execute_command", "description": "Run shell command"} ] } } elif method == "tools/call": tool_name = msg["params"]["name"] args = msg["params"].get("arguments", {}) path = args.get("path", "") resp = { "jsonrpc": "2.0", "id": msg_id, "result": { "content": [{"type": "text", "text": f"Successfully executed {tool_name} with path: {path}"}] } } else: resp = {"jsonrpc": "2.0", "id": msg_id, "result": {}} sys.stdout.write(json.dumps(resp) + "\n") sys.stdout.flush() -
Start Gateway Server (spawning the stdio subprocess):
- PowerShell:
$env:PYTHONPATH=".;src" $env:FW_REAL_SERVER_CMD='["python", "stdio_server.py"]' uv run uvicorn src.gateway.mcp_gateway:app --port 8001 - Git Bash:
PYTHONPATH=".;src" FW_REAL_SERVER_CMD='["python", "stdio_server.py"]' uv run uvicorn src.gateway.mcp_gateway:app --port 8001
- PowerShell:
Option D: Bidirectional WebSocket Client Connection
You can test the entire pipeline (Gateway policy filters + Stdio subprocess execution) over full-duplex WebSockets:
-
Connect via WebSocket client (e.g.
wscat):npx wscat -c "ws://127.0.0.1:8001/ws?identity=alice&session_id=session_ws"Expected connection response:
Connected (press CTRL+C to quit) < event: endpoint data: /message?session_id=session_ws&identity=alice -
Test 1: Request tool list (Filters out
execute_commandfor identityalice): Send:{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}Response from Gateway (showing filtered tools list):
< event: message data: {"jsonrpc": "2.0", "id": 1, "result": {"tools": [{"name": "read_file", "description": "Read file content"}]}} -
Test 2: Call Allowed Tool: Send:
{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "read_file", "arguments": {"path": "src/gateway/policy_v2.json"}}}Response from Gateway (execution proxies through subprocess successfully):
< event: message data: {"jsonrpc": "2.0", "id": 2, "result": {"content": [{"type": "text", "text": "Successfully executed read_file with path: src/gateway/policy_v2.json"}]}} -
Test 3: Sandbox Traversal Attempt (Blocked by Policy Engine): Send:
{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "read_file", "arguments": {"path": "C:\\Windows\\win.ini"}}}Response from Gateway (intercepted and blocked before reaching subprocess):
< event: message data: {"jsonrpc": "2.0", "id": 3, "error": {"code": -32602, "message": "Security Policy Violation: Path 'C:\\Windows\\win.ini' is outside sandbox 'D:/Coding'"}}
No comments yet
Be the first to share your take.