MCP server RASP security: runtime application self-protection and in-process argument inspection
Static analysis tells you what an MCP server might do wrong. RASP (Runtime Application Self-Protection) intercepts what it's actually doing — at the moment a tool is invoked, with the actual argument values, before the downstream call executes. For MCP servers handling LLM-generated tool calls, RASP is the last line of defense against arguments that bypass schema validation.
Why RASP is uniquely valuable for MCP servers
Traditional web application RASP instruments HTTP request handlers to detect injection in request parameters. MCP servers have a different threat model: the "attacker" is often an LLM that has been manipulated via prompt injection to generate a tool call with malicious argument values. The LLM may pass schema-valid arguments that are semantically malicious:
- A URL argument that passes
format: urischema validation but targets an internal metadata service (SSRF) - A filename argument that is valid UTF-8 but contains path traversal sequences (
../../etc/passwd) - A query argument that is a valid string but contains SQL injection payloads
- A system prompt override argument embedded in what appears to be a user-provided document content
Schema validation (Zod, JSON Schema) catches type mismatches. RASP catches semantic attacks that pass type checks.
The MCP tool dispatch interception point
The optimal RASP hook for MCP servers is at the tool dispatch layer — between the MCP framework receiving the tool call from the LLM and the tool handler function executing. In the MCP SDK for Node.js, this is the server.tool() registration layer:
function raspMiddleware(toolName, schema, handler) {
return async (args, ctx) => {
// 1. Schema validation (type-level)
const parsed = schema.safeParse(args);
if (!parsed.success) throw new McpError(ErrorCode.InvalidParams, ...);
// 2. RASP inspection (semantic-level)
const threat = inspectArgs(toolName, parsed.data);
if (threat) {
audit.log({ event: "rasp_block", tool: toolName, threat, session: ctx.sessionId });
throw new McpError(ErrorCode.InvalidParams, "Request blocked by security policy");
}
// 3. Execute handler with validated, inspected args
return handler(parsed.data, ctx);
};
}
RASP inspection rules for MCP servers
The inspectArgs function implements pattern-based checks tuned for MCP argument semantics. Key rule categories:
URL argument inspection (SSRF prevention)
Any argument containing a URL must be checked against an allowlist of permitted domains before the tool handler makes an outbound HTTP call. Reject: private IP ranges (10.x, 172.16-31.x, 192.168.x), localhost, IPv6 loopback, metadata service endpoints (169.254.169.254, fd00:ec2::254), and file:// and gopher:// schemes.
Path argument inspection (path traversal prevention)
Filename and path arguments must be checked for directory traversal sequences after URL decoding and normalization. Check for: ../, ..\, null bytes, and ensure the resolved path is within the expected base directory using path.resolve() comparison.
Shell argument inspection (command injection prevention)
If a tool argument is ever used in a shell context (even via child_process.exec() with shell interpolation), reject arguments containing: semicolons, pipes, backticks, dollar-parenthesis, newlines, and null bytes. Better: use child_process.execFile() with an args array instead of shell string interpolation, making this check unnecessary.
Prompt injection detection (agentic-specific)
Text arguments that represent user-provided content (document text, email body, ticket description) should be checked for instruction override patterns before the MCP server processes them. Heuristic patterns: ignore previous instructions, system: prefix, Assistant: prefix, STOP. New task:, and similar. Block or sanitize before the text enters any downstream LLM call.
Node.js async hooks for syscall-level RASP
For MCP servers requiring deeper runtime inspection, Node.js async_hooks can intercept asynchronous operations to track which tool invocation triggered a network call or filesystem access:
import { AsyncLocalStorage } from 'async_hooks';
const toolContext = new AsyncLocalStorage();
// In each tool handler:
toolContext.run({ tool: toolName, session: ctx.sessionId }, () => {
return handler(args, ctx); // all async ops inside get the context
});
// In outbound HTTP interceptor:
const ctx = toolContext.getStore();
if (ctx && isBlockedDomain(url)) {
throw new Error(`SSRF blocked: ${url} called from tool ${ctx.tool}`);
}
This pattern attributes every network call to its originating tool invocation, enabling precise audit trails and making SSRF detection context-aware — an outbound call to an internal endpoint is blocked only if it originates from a tool invocation, not from server startup code.
RASP vs WAF vs eBPF: choosing the right layer
Three complementary approaches exist for MCP server runtime defense:
- RASP (in-process): Sees argument values, has LLM context, can block before handler execution. Blind to syscalls made by native modules. No kernel privileges required.
- WAF (network layer): Sees HTTP requests to the MCP server endpoint. Effective for network-level injection in the MCP transport layer. Cannot inspect tool argument semantics that are valid JSON.
- eBPF (kernel layer): Sees all syscalls regardless of language or runtime. Catches malicious activity from native dependencies. Requires kernel support; higher operational complexity. Covered in the MCP server eBPF security guide.
Deploy all three in a high-security MCP deployment: RASP for semantic argument inspection, eBPF for syscall-level containment, and WAF for network-layer filtering. They catch different attack surfaces with minimal overlap.
Performance impact of RASP on MCP servers
Pattern matching on tool arguments adds latency at the tool dispatch layer. Benchmarks on a Node.js MCP server processing 100 concurrent tool calls:
- URL allowlist check: <0.1ms per invocation (regex against pre-compiled patterns)
- Path traversal check: <0.1ms per invocation (string normalization + prefix check)
- Prompt injection heuristics: 0.5–2ms per invocation depending on text length
- Total RASP overhead: <3ms per tool invocation at p99
For most MCP server deployments where the tool handler itself makes network calls (100ms+), the RASP overhead is negligible. For high-frequency lightweight tools (filesystem stat calls), profile before deploying prompt injection heuristics.
What SkillAudit checks for RASP-preventable vulnerabilities
SkillAudit's static analysis identifies the argument handling patterns that RASP is designed to catch at runtime: URL construction from args without allowlisting (SSRF), path construction from args without traversal checks, exec/spawn calls with string interpolation from args (command injection), and unvalidated JSON parse patterns. The audit report's remediation hints include both the static fix (validate at the source) and the runtime defense option (add RASP middleware) — you can implement either or both.
Find argument handling vulnerabilities before attackers do
SkillAudit scans MCP servers for SSRF, path traversal, command injection, and prompt injection patterns. Free for public repos.
Run a free audit →Related: MCP server eBPF runtime security · MCP server input validation patterns · Anatomy of a prompt injection attack