Skip to content

Stop JSON parser errors from echoing peer payload - #211

Merged
simonx1 merged 1 commit into
mainfrom
fix/parser-error-payload-leak
Aug 4, 2026
Merged

Stop JSON parser errors from echoing peer payload#211
simonx1 merged 1 commit into
mainfrom
fix/parser-error-payload-leak

Conversation

@simonx1

@simonx1 simonx1 commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Summary

Found by Codex in a follow-up review of the 2.1.0 release PR (#208), verified before fixing.

JSON::ParserError#message quotes the offending token:

JSON.parse('{LEAKME-123-45-6789 bad}')
# => expected object key, got 'LEAKME-123-45-6789' at line 1 column 2

#210 replaced the log lines that printed payloads directly, but kept e.message — on my assumption that it carried only a position. It doesn't, so peer-controlled bytes still reached logs.

The leak was reachable on every JSON receive path:

Path Channel
GET events stream (server_streamable_http.rb) logger.error
POST SSE response parser (server_streamable_http/json_rpc_transport.rb) logger.warndefault-visible
Legacy SSE parser (server_sse/sse_parser.rb) logger.warndefault-visible
OAuth token-refresh (auth/oauth_provider.rb) logger.warn — and the payload there is a token response
All four transports' Invalid JSON response from server TransportError message, which hosts routinely log

The test that gave false assurance

Codex also spotted that the regression test I added in #210 passed by accident: its sentinel appeared after the first invalid token, so the parser never quoted it. That is the more useful half of the finding — the fix looked verified when it wasn't.

The new tests place the sentinel as the first invalid token on each path, which is the case that actually exercises the quoting.

Fix

describe_parse_error (in JsonRpcCommon, shared by every transport) keeps what makes a parse failure diagnosable — the line N column M position and the payload byte size — and drops the quoted content:

Invalid JSON in server message: malformed JSON, at line 1 column 2, 18 bytes

I kept the position deliberately rather than logging only a class name: debugging a server that emits subtly broken JSON is painful without it, and the position is not peer content.

The Invalid JSON response from server: prefix is unchanged, so existing rescues and the nine specs matching on it are unaffected.

Testing

New specs assert no sentinel reaches the log on the GET, POST-SSE and legacy-SSE paths, none reaches the raised TransportError for a malformed HTTP body, and that the position and size survive. Full suite 1699 examples / 0 failures, RuboCop clean.

🤖 Generated with Claude Code

JSON::ParserError#message quotes the offending token — "expected object
key, got 'SECRET-123' at line 1 column 2" — so interpolating it puts
peer-controlled bytes into logs and exception messages. #210 replaced
the payload-bearing log lines but kept the parser message, on the wrong
assumption that it carried only a position.

The leak was reachable on every JSON receive path: the GET events stream
(ERROR), the POST SSE response and legacy SSE parsers (WARN, which the
default logger emits), the OAuth token-refresh warning, and the
"Invalid JSON response from server" TransportError raised by all four
transports.

describe_parse_error keeps what actually helps diagnose a broken server
- the line/column and the payload size - and drops the quoted content.

The regression test added in #210 passed only by accident: its sentinel
sat after the first invalid token, so the parser never quoted it. The
new tests place the sentinel FIRST on each path.

Found by Codex follow-up review of the 2.1.0 release PR (#208).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@simonx1
simonx1 merged commit b3307b2 into main Aug 4, 2026
4 checks passed
simonx1 added a commit that referenced this pull request Aug 4, 2026
Picks up the parser-error payload leak fix and folds it into the
"logs no longer contain payloads" entry, which was previously true only
of the paths that log a parsed message.
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.

1 participant