@@ -445,6 +445,12 @@ def _extract_tool_declarations(
445445# values beyond it fail closed.
446446_MAX_JSON_INSPECT_CHARS = 4_000_000
447447
448+ # Keep deeply nested JSON behavior deterministic across Python versions.
449+ # CPython <=3.13 rejects extreme nesting from json.loads with RecursionError,
450+ # while 3.14's iterative decoder accepts it. A fixed ceiling preserves the
451+ # fail-closed contract and bounds the materialized object graph everywhere.
452+ _MAX_JSON_NESTING_DEPTH = 1_000
453+
448454
449455def _strip_bom_ws (value : str ) -> str :
450456 """Strips BOMs and ALL Unicode whitespace from the start of a string.
@@ -465,6 +471,36 @@ def _strip_bom_ws(value: str) -> str:
465471 return value [i :] if i else value
466472
467473
474+ def _json_nesting_exceeds_limit (value : str ) -> bool :
475+ """Returns whether JSON structural nesting exceeds the fixed ceiling.
476+
477+ Brackets and braces inside strings are payload, not structure. This linear
478+ preflight runs only after the existing character-size bound, before
479+ ``json.loads`` can materialize a runtime-dependent deep object graph.
480+ """
481+ depth = 0
482+ in_string = False
483+ escaped = False
484+ for char in value :
485+ if in_string :
486+ if escaped :
487+ escaped = False
488+ elif char == "\\ " :
489+ escaped = True
490+ elif char == '"' :
491+ in_string = False
492+ continue
493+ if char == '"' :
494+ in_string = True
495+ elif char in "[{" :
496+ depth += 1
497+ if depth > _MAX_JSON_NESTING_DEPTH :
498+ return True
499+ elif char in "]}" :
500+ depth -= 1
501+ return False
502+
503+
468504def _normalize_json_native (
469505 obj : Any ,
470506 max_len : int ,
@@ -719,6 +755,9 @@ def _sanitize_json_blob(
719755 if len (stripped ) > inspect_limit :
720756 return "[UNPARSEABLE_JSON_BLOB]" , True , True
721757
758+ if _json_nesting_exceeds_limit (stripped ):
759+ return "[UNPARSEABLE_JSON_BLOB]" , True , True
760+
722761 # json.loads silently keeps only the LAST duplicate member, so a blob like
723762 # {"access_token":"SECRET","access_token":"x"} can compare equal after
724763 # sanitization while the raw string still carries the secret (#6360 review
0 commit comments