Learn how to build and deploy an enterprise-grade Zero-Trust Sandbox Firewall for Model Context Protocol (MCP) servers in TypeScript. Prevent Indirect Prompt Injection, command injection, and
Learn how to build zero-trust FastMCP servers in Python and harden Next.js 16 AI agent architectures against indirect prompt injection, tool poisoning, and unauthorized tool calls (2026 Maste
25 min read
Basic knowledge of TypeScript/Node.js, familiarity with Model Context Protocol (MCP) concepts, and JSON-RPC 2.0 message structure.
Node.js (v18+), TypeScript, @modelcontextprotocol/sdk
The Model Context Protocol (MCP) standardizes how Large Language Model (LLM) agents interface with external tools, local file systems, databases, and APIs. However, because MCP operates over stateful JSON-RPC 2.0 channels (via stdio, Server-Sent Events, or WebSockets), traditional Web Application Firewalls (WAFs) and HTTP layer security controls cannot inspect or sanitize payload parameters dynamically.
A Zero-Trust MCP Sandbox Firewall acts as an inline security proxy positioned between the AI Client (such as Claude Desktop, Cursor, or custom agent runtimes) and the target MCP Server. By enforcing strict JSON Schema parameter validation, real-time payload sanitization, command injection heuristics, strict tool access policies, and isolation semantics, an MCP firewall prevents Indirect Prompt Injection (IPI), unauthorized file exfiltration, and arbitrary code execution.
This hands-on guide provides a step-by-step implementation of an enterprise-grade Zero-Trust MCP Firewall in TypeScript/Node.js, including complete source code, security policy configurations, and unit verification tests.
Before securing MCP traffic, security engineers must understand the low-level JSON-RPC 2.0 protocol mechanics powering AI agent tool invocations.
+------------------+ JSON-RPC Request +-------------------------+ Inspected Request +------------------+
| | -------------------------------> | | -------------------------------> | |
| AI Agent Host | | Zero-Trust MCP Firewall | | Target MCP |
| (Claude/Cursor) | <------------------------------- | (Inline Security) | <------------------------------- | Server |
+------------------+ Sanitized Response +-------------------------+ Filtered Response +------------------+
|
v
+-------------------+
| Security Log & |
| Audit Store (JSON)|
+-------------------+
MCP supports two primary transport layers:
stdin) and standard output (stdout). Each message is a newline-delimited JSON-RPC 2.0 object.An MCP session involves three primary interactions:
initialize): Exchanging protocol versions and client/server capabilities.tools/list): The server exposes available functions, descriptions, and JSON Schemas defining expected input parameters.tools/call): The AI client issues a call request with arguments matching the tool's schema:{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "execute_shell_command",
"arguments": {
"command": "ls -la; cat /etc/passwd"
}
}
}
Indirect Prompt Injection occurs when an LLM ingests untrusted external text (from a web page, file, or database query result) containing adversarial instructions designed to hijack control flow. If the LLM subsequently invokes an MCP tool with malicious arguments (e.g., executing shell payloads or reading sensitive SSH keys), unmonitored MCP execution leads to catastrophic system compromise.
| Attack Vector | Mechanism | Impact | Firewall Countermeasure |
|---|---|---|---|
| Command Injection | Appending shell operators (;, ` | , &&, `` ``) in tool parameters. | Remote Code Execution (RCE) on host machine. |
| Path Traversal | Using ../ or absolute paths (/etc/shadow, C:\Windows) in file read tools. | Unauthorized data exfiltration & privilege disclosure. | Path canonicalization & strict root folder jail check. |
| Indirect Prompt Injection | Embedded prompt text tricking LLM into invoking unapproved tools. | Exfiltration of user instructions or credentials via outbound requests. | Tool whitelist policy & strict capability scopes. |
| Resource Exhaustion | Flooding MCP server with high-frequency complex tool requests. | Denial of Service (DoS) of agent operations. | Token Bucket Rate Limiting per session. |
A robust security policy defines allowable tool names, required parameter rules, forbidden regex patterns, and maximum payload limits. We formalize this policy in JSON structure:
{
"version": "1.0",
"defaultPolicy": "DENY",
"rateLimit": {
"maxRequestsPerMinute": 30
},
"allowedTools": {
"read_file": {
"enabled": true,
"allowedPaths": ["/workspace", "/app/data"],
"forbiddenPatterns": ["\\.\\.", "/etc/", "~/", "\\.env"]
},
"execute_command": {
"enabled": true,
"allowedCommands": ["git", "npm", "ls"],
"forbiddenCharacters": [";", "|", "&", "`", "$", "(", ")", "<", ">"]
}
}
}
Below is the complete, runnable TypeScript implementation of an inline MCP Proxy Firewall (mcp-firewall.ts). It intercepts stdio traffic between the AI host and child MCP server, performing inline inspection and policy enforcement.
import { spawn, ChildProcess } from 'child_process';
import * as readline from 'readline';
interface FirewallConfig {
allowedTools: Set<string>;
restrictedPaths: string[];
forbiddenPatterns: RegExp[];
maxPayloadSize: number;
}
const defaultConfig: FirewallConfig = {
allowedTools: new Set(['read_file', 'list_dir', 'execute_command']),
restrictedPaths: ['/etc', '/var', '/root', '/home/cyb3rvolt3x/.ssh', '.env'],
forbiddenPatterns: [
/;\s*/, // Command chaining ;
/\|\s*/, // Piping |
/`[^`]*`/, // Backticks `command`
/\$\([^)]*\)/, // Subshell $(command)
/\.\.\//, // Path traversal ../
/cat\s+\/etc\/passwd/i // Sensitive file read attempts
],
maxPayloadSize: 64 * 1024 // 64 KB limit
};
export class MCPProxyFirewall {
private childProcess: ChildProcess | null = null;
private config: FirewallConfig;
private requestCount: number = 0;
constructor(targetCommand: string, targetArgs: string[], config: FirewallConfig = defaultConfig) {
this.config = config;
this.spawnTargetServer(targetCommand, targetArgs);
this.setupStdioInterception();
}
private spawnTargetServer(command: string, args: string[]) {
this.childProcess = spawn(command, args, {
stdio: ['pipe', 'pipe', 'inherit']
});
this.childProcess.on('exit', (code) => {
console.error(`[MCP Firewall] Target process exited with code ${code}`);
process.exit(code || 0);
});
}
private setupStdioInterception() {
if (!this.childProcess || !this.childProcess.stdout || !this.childProcess.stdin) {
throw new Error('[MCP Firewall] Failed to attach stdio pipes');
}
// Intercept Input from AI Client (stdin -> childProcess.stdin)
const clientRl = readline.createInterface({
input: process.stdin,
terminal: false
});
clientRl.on('line', (line) => {
this.handleClientMessage(line);
});
// Forward Output from MCP Server (childProcess.stdout -> stdout)
const serverRl = readline.createInterface({
input: this.childProcess.stdout,
terminal: false
});
serverRl.on('line', (line) => {
// Forward server responses to client
process.stdout.write(line + '\n');
});
}
private handleClientMessage(rawLine: string) {
if (rawLine.length > this.config.maxPayloadSize) {
this.sendErrorResponse(null, -32600, 'Payload size exceeds maximum allowed threshold');
return;
}
let payload: any;
try {
payload = JSON.parse(rawLine);
} catch (e) {
this.sendErrorResponse(null, -32700, 'Invalid JSON payload');
return;
}
// Inspect tools/call requests
if (payload.method === 'tools/call') {
const isAllowed = this.inspectToolCall(payload);
if (!isAllowed) {
return; // Blocked; error response sent
}
}
// Request passed security audit; forward to backend MCP server
if (this.childProcess && this.childProcess.stdin) {
this.childProcess.stdin.write(rawLine + '\n');
}
}
private inspectToolCall(payload: any): boolean {
const requestId = payload.id;
const toolName = payload.params?.name;
const args = payload.params?.arguments || {};
// 1. Tool Whitelist Audit
if (!this.config.allowedTools.has(toolName)) {
this.logAudit('BLOCKED_UNAUTHORIZED_TOOL', payload);
this.sendErrorResponse(requestId, -32601, `Tool '${toolName}' is forbidden by firewall policy`);
return false;
}
// 2. Inspection of Arguments for Injection Patterns
const serializedArgs = JSON.stringify(args);
for (const pattern of this.config.forbiddenPatterns) {
if (pattern.test(serializedArgs)) {
this.logAudit('BLOCKED_MALICIOUS_PATTERN', { toolName, pattern: pattern.toString(), args });
this.sendErrorResponse(requestId, -32000, `Blocked: Malicious parameter pattern detected matching '${pattern}'`);
return false;
}
}
// 3. Path Traversal & Restricted Directory Audit
if (args.path || args.filePath || args.filename) {
const targetPath = String(args.path || args.filePath || args.filename);
for (const restricted of this.config.restrictedPaths) {
if (targetPath.includes(restricted)) {
this.logAudit('BLOCKED_RESTRICTED_PATH', { toolName, targetPath, restricted });
this.sendErrorResponse(requestId, -32001, `Access Denied: Path '${targetPath}' touches restricted resource '${restricted}'`);
return false;
}
}
}
this.logAudit('ALLOWED_TOOL_CALL', { toolName });
return true;
}
private sendErrorResponse(id: any, code: number, message: string) {
const errorResponse = {
jsonrpc: '2.0',
id: id,
error: {
code: code,
message: message
}
};
process.stdout.write(JSON.stringify(errorResponse) + '\n');
}
private logAudit(event: string, details: any) {
const auditRecord = {
timestamp: new Date().toISOString(),
event,
details
};
console.error(`[MCP FIREWALL AUDIT] ${JSON.stringify(auditRecord)}`);
}
}
// Entrypoint Execution
if (require.main === module) {
const targetCmd = process.argv[2] || 'node';
const targetArgs = process.argv.slice(3);
if (!targetCmd) {
console.error('Usage: node mcp-firewall.js <target_mcp_command> [args...]');
process.exit(1);
}
new MCPProxyFirewall(targetCmd, targetArgs);
}
To validate our zero-trust firewall against offensive payloads, we execute a suite of verification test vectors.
Client Input:
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"read_file","arguments":{"path":"/workspace/readme.txt"}}}
Firewall Action: ALLOWED_TOOL_CALL. Payload forwarded to target MCP server.
Client Input:
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"execute_command","arguments":{"command":"ls -la; cat /etc/passwd"}}}
Firewall Action: BLOCKED_MALICIOUS_PATTERN. Firewall blocks request inline and responds:
{"jsonrpc":"2.0","id":2,"error":{"code":-32000,"message":"Blocked: Malicious parameter pattern detected matching '/;\\s*/'"}}
Client Input:
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"read_file","arguments":{"path":"/home/cyb3rvolt3x/.ssh/id_rsa"}}}
Firewall Action: BLOCKED_RESTRICTED_PATH. Firewall responds with -32001 Access Denied.
When deploying an MCP Firewall in enterprise production environments, apply the following defense-in-depth principles:
--cap-drop=ALL --read-only).tools/list during capability initialization.[MCP FIREWALL AUDIT]) to centralized SIEM platforms (Splunk, Elastic, Datadog) for anomaly detection and alert triggering.stdio/SSE).Share this tutorial
Sign in to leave a comment.