Topic: mcp server stdio injection
MCP server stdio injection — JSON-RPC channel injection attacks and mitigations
The MCP stdio transport is line-delimited JSON-RPC: each message is a single JSON object terminated by a newline (\n). The MCP SDK handles this framing correctly for well-formed inputs. The risk is what happens when a tool argument or tool response contains bytes that can break or manipulate the framing: null bytes that truncate JSON string parsing, embedded newlines that split a single message into two frames, and oversized messages that exhaust the receiving process's input buffer. These are not theoretical — they arise naturally when MCP servers process file contents, terminal output, or user-supplied text and pass it through the stdio channel without sanitization.
Pattern 1: embedded newline injection via manual JSON assembly
The most common stdio injection pattern is manual string concatenation to build JSON-RPC responses. When a tool's output contains a newline character and the server assembles the response via string concatenation instead of JSON.stringify, the newline appears literally in the output stream and splits the message frame.
// WRONG: manual JSON assembly — newlines in content break the frame
const fileContent = await fs.readFile(path, 'utf8');
// If fileContent contains newlines (it almost certainly does),
// this splits the frame:
process.stdout.write(
'{"jsonrpc":"2.0","id":' + requestId + ',"result":{"content":"' + fileContent + '"}}\n'
);
// The line break inside fileContent becomes a literal \n in the output stream,
// creating a spurious new frame that the MCP client tries to parse as JSON.
// CORRECT: always use JSON.stringify — it escapes newlines to \\n
const response = {
jsonrpc: '2.0',
id: requestId,
result: {
content: [{ type: 'text', text: fileContent }]
}
};
process.stdout.write(JSON.stringify(response) + '\n');
// JSON.stringify converts internal newlines to \\n — safe in any frame
The fix is always JSON.stringify — never string concatenation for JSON assembly. This is basic JSON hygiene but it is the most common stdio injection source in corpus servers that implement custom serialization or build tool responses in ad-hoc ways.
Pattern 2: null byte injection in binary content
JSON is a text format, but MCP servers frequently process binary content: file reads on binary files, base64-decoded data, terminal output with escape sequences. A null byte (\x00) inside a JSON string is technically valid in JSON (it should be encoded as