Skip to content

Reject incomplete JSON numbers instead of coercing them via strtod - #351

Merged
tylerreckart merged 4 commits into
mainfrom
fix/json-incomplete-number
Sep 21, 2026
Merged

tylerreckart merged 4 commits into
mainfrom
fix/json-incomplete-number

Conversation

@cursor

@cursor cursor Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Problem

json_parse tokenized 1., 1e, 1e+, and -.5 as numbers. RFC 8259 requires at least one digit after a decimal point and after an exponent marker, and an integer part before a fraction. strtod then parsed a prefix of that capture ("1e" → 1.0, end pointer on 'e'), so the document was accepted.

MCP tool payloads, A2A envelopes, HTTP JSON bodies, and TUI session blobs all share this parser. A malformed number was silently coerced instead of rejected, which can shift later tokens in arrays/objects ([1.,2] became [1, 2]).

Distinct from #342 (UTF-16 surrogate pairs in parse_string).

Fix

parse_number now follows the JSON grammar for frac/exp completeness: a present . or e/E must be followed by at least one digit, and the integer part cannot be empty (-.5 fails). strtod must consume the whole token; ERANGE still fails closed. Leading zeros (01) are unchanged.

Tests

unit_json_numbers — well-formed ints/fracs/exponents still parse; incomplete frac/exp (bare, nested in arrays/objects) throw; overflow 1e9999 still throws. Suite 4/4 locally (56 assertions), including ASan and ASan+UBSan. arbiter compiles.

Independently mergeable against main (0456350). Code hunks (parse_number + new tests/test_json_numbers.cpp + CMake target after unit_remote_connect) auto-merge with #342 (parse_string surrogates + tests/test_json.cpp after unit_logger). CHANGELOG [Unreleased] sibling-conflicts as usual after the first of #321–#350 merges.

Open in Web View Automation 

Note

Medium Risk
Stricter parsing may reject payloads that previously parsed successfully, but that is intentional fail-closed behavior on shared ingress JSON for MCP/A2A/HTTP.

Overview
json_parse now enforces RFC 8259 number shape instead of accepting truncated tokens that strtod could partially parse (e.g. 1. → 1, 1e → 1.0, -.5 accepted without an integer part). Fraction and exponent segments must include at least one digit when present, and strtod must consume the entire token (plus existing ERANGE handling).

Because MCP, A2A, and HTTP JSON all use this parser, malformed numbers fail the document rather than shifting later tokens (e.g. [1.,2] no longer becomes [1, 2]).

Adds unit_json_numbers (tests/test_json_numbers.cpp) covering valid ints/fracs/exponents, incomplete tokens in isolation and inside arrays/objects, and overflow. Documents the fix under [Unreleased] in CHANGELOG.md.

Reviewed by Cursor Bugbot for commit 5e1525d. Bugbot is set up for automated code reviews on this repo. Configure here.

json_parse treated 1., 1e, 1e+, and -.5 as valid because the tokenizer
consumed the incomplete token and strtod parsed a prefix (1e → 1.0).
Require digits after a decimal point or exponent marker, and require
strtod to consume the whole token.

Co-authored-by: Tyler Reckart <tylerreckart@users.noreply.github.com>
@tylerreckart
tylerreckart marked this pull request as ready for review September 21, 2026 12:58
@tylerreckart
tylerreckart enabled auto-merge (squash) September 21, 2026 12:58
@tylerreckart
tylerreckart merged commit 725e1b4 into main Sep 21, 2026
1 check passed
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