Skip to content

fix(utils): give parse_json_body an explicit max_depth - #169

Merged
caterryan merged 1 commit into
mainfrom
fix/json-body-max-depth
Sep 16, 2026
Merged

caterryan merged 1 commit into
mainfrom
fix/json-body-max-depth

Conversation

@caterryan

@caterryan caterryan commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

parse_json_body promised to refuse a body "nested too deeply" but only caught the interpreter's RecursionError from json.loads. Where that fires moves with every Python release. Measured locally with "[" * n + "]" * n:

Python still parses at RecursionError at
3.11 near 1,000: the C scanner counts against the Python recursion limit
3.13 5,000 20,000
3.14 100,000 1,000,000

Python 3.14's C recursion guard is stack-based, so the existing 20,000-level test parses successfully there and test_a_deeply_nested_body_stays_within_the_valueerror_contract fails. The CI matrix stops at 3.13, so nothing caught it. I hit it running the suite locally on 3.14 while preparing the 0.4.0 release.

Change. parse_json_body takes max_depth (keyword-only, default DEFAULT_MAX_BODY_DEPTH = 100), mirroring max_bytes. The parsed body is walked with an explicit stack and refused once a container sits deeper than the limit, counting the top-level object as level 1. None lifts the limit. A dict body is not measured, as with max_bytes.

The RecursionError catch stays as the fallback for a body that out-nests the interpreter before the walk can run: 3.11 at 20,000 levels, or a caller who raised max_depth past what the interpreter tolerates. Same "nested too deeply" prefix; the explicit refusal also names the limit.

Changelog entry under Unreleased. No version bump.

Why 100. Real plugin request bodies are a handful of levels deep. 100 leaves wide headroom while sitting far below every interpreter's guard, so the explicit check is what fires everywhere and the message is the same on 3.11 and 3.14. A body deeper than 100 that 3.13 used to accept is now refused; that is the point, and max_depth is there for a caller who needs more.

Verified. The existing suite passes on 3.13 and 3.14 locally, including the 20,000-level test that failed on 3.14 before. CI covers 3.11.

🤖 Generated with Claude Code

The "nested too deeply" refusal only caught RecursionError from
json.loads, and where that fires moves with every Python release: near
1,000 levels on 3.11, 20,000 on 3.13, and not before 1,000,000 on 3.14,
whose C recursion guard is stack-based. The existing 20,000-level test
therefore fails on 3.14.

Add max_depth (default 100) alongside max_bytes, checked with an
explicit-stack walk of the parsed body so the check itself cannot
recurse. Keep the RecursionError catch as the fallback for a body that
out-nests the interpreter before the walk runs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@caterryan
caterryan merged commit d639274 into main Sep 16, 2026
8 checks passed
@caterryan
caterryan deleted the fix/json-body-max-depth branch September 16, 2026 14:12
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.

2 participants