Skip to content

Fix access violation on unparseable JSON-RPC requests - #17

Merged
cmgeuze merged 1 commit into
GDKsoftware:mainfrom
eivindbakkestuen:fix/malformed-request-nil-av
Jul 14, 2026
Merged

cmgeuze merged 1 commit into
GDKsoftware:mainfrom
eivindbakkestuen:fix/malformed-request-nil-av

Conversation

@eivindbakkestuen

Copy link
Copy Markdown

Problem

Any request whose body fails JSON parsing crashes the server with an access violation instead of returning a JSON-RPC error:

Access violation at address ... in module 'MCPServer.exe'. Read of address 0000000000000010

Easy ways to trigger it:

  • plain garbage on stdin: this is not json {{{
  • hand-built JSON containing a raw Windows path — in "C:\ProgramData\Oops" the \P is an invalid JSON escape
  • a UTF-8 BOM in front of the first message (some Windows pipelines add one)
  • a request line truncated by a dying pipe/proxy

Correctly escaped arguments (e.g. "C:\\ProgramData\\Data") were never affected — this only concerns bodies that TJSONObject.ParseJSONValue rejects.

Root cause

In TMCPJsonRpcProcessor.ProcessRequest, ParseJSONRequest raises before JSONRequest is assigned, and the except handler then calls ExtractRequestID(JSONRequest) with JSONRequest = nil. TJSONObject.GetValue on a nil instance reads offset $10 — confirmed by symbolicating the AV address against a detailed map file.

The transport loop catches the escaped AV so the demo server survives, but hosts that embed more machinery can fare much worse: we found this in a NexusDB-based MCP server where the embedded database engine's process-wide exception hook treats any access violation as fatal and suspends all database operations until the process is restarted. A malformed request should never cost more than an error response.

Fix

  • ExtractRequestID: return TValue.Empty when the request object is nil.
  • ProcessRequest: report JSONRPC_PARSE_ERROR (-32700, per JSON-RPC 2.0) instead of -32603 when the body never parsed.
  • TMCPStdioTransport.Run: build the transport-level error response with TJSONObject instead of string concatenation that only escaped quotes — the old code emitted invalid JSON whenever the exception message contained a backslash (e.g. a Windows path) or a control character.

Verification

Win64, stdio transport, both builds from this same base revision (419b512):

Input Before After
{"...,"message":"C:\ProgramData\Oops"} (invalid escape) Access violation ... Read of address 0000000000000010 {"code":-32700,"message":"Invalid JSON"}
this is not json {{{ same access violation {"code":-32700,"message":"Invalid JSON"}
{"...,"message":"C:\\ProgramData\\OZZIE\\Data"} (valid) echoed correctly echoed correctly (unchanged)
next request after a malformed one served served

🤖 Generated with Claude Code

When TJSONObject.ParseJSONValue rejects a request body, ParseJSONRequest
raises before JSONRequest is assigned, and the except handler in
ProcessRequest then called ExtractRequestID(nil). The nil dereference
surfaces as "Access violation ... Read of address 0000000000000010"
(TJSONObject.GetValue on a nil instance) for any syntactically invalid
request - a stray BOM, a truncated line, or hand-built JSON containing
raw Windows paths (in "C:\ProgramData\..." the \P is an invalid escape).
In servers that embed a database engine the escaped AV is even worse
than the failed request: it can trip process-wide fatal-error handling.

- ExtractRequestID: return TValue.Empty for a nil request object
- ProcessRequest: report JSONRPC_PARSE_ERROR (-32700) per JSON-RPC 2.0
  when the body never parsed, instead of -32603
- StdioTransport: build the transport-level error response with
  TJSONObject instead of string concatenation that only escaped quotes,
  which emitted invalid JSON whenever the exception message contained a
  backslash or control character

Verified on Win64 (stdio transport): before, invalid escapes and plain
garbage each produced the access violation; after, both return
{"code":-32700,"message":"Invalid JSON"} and the server keeps serving.
Correctly escaped backslashes in arguments were unaffected before and
after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cmgeuze

cmgeuze commented Jul 14, 2026

Copy link
Copy Markdown
Member

Verified locally on Win64: reproduced the access violation on main, and with this branch the same inputs return -32700 "Invalid JSON" while the server keeps serving. Valid requests unaffected, clean build, security review clean. Thanks @eivindbakkestuen!

@cmgeuze
cmgeuze merged commit 35059ef into GDKsoftware:main Jul 14, 2026
@cmgeuze

cmgeuze commented Sep 7, 2026

Copy link
Copy Markdown
Member

Hi @eivindbakkestuen, your fix from this PR is still in and now has tests around it.

The 2026-07-28 MCP revision is on feature/mcp-2026-07-28 (#40) before it goes to main. It serves 2025-06-18 and 2025-11-25 on the same endpoint and the same stdio process, so an existing client keeps working. If you have time to run it against your setup before I merge, I'd like to hear about anything that breaks. MIGRATION.md lists the behaviour changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants