Problem
redact_json_value() recurses over mappings, lists, and tuples with no depth limit and no cycle
detection (lib/python/base_cli/json_contracts.py:103-116). It is on the error path: every
success_envelope(), error_envelope(), and dumps_envelope() call passes consumer-supplied
details through it before serialization.
Two consequences:
- a deeply nested
details mapping raises RecursionError inside envelope construction, so the
framework's own error rendering fails while reporting a command failure;
- a self-referential structure raises
RecursionError rather than the clear
ValueError: Circular reference detected that json.dumps() would produce, because the redaction
pass runs first and hits the recursion limit before the encoder can diagnose it.
Deeply nested or cyclic details is not a normal input, which is why this is low severity. But it
arrives from consumer data — parsed YAML/JSON from an API response, a config tree, an object graph
someone dropped into details for diagnostics — and the failure lands in the code path whose job is
to report failures reliably.
Verified evidence
Reviewed 2026-09-30 at a58ec109349fa3f3d03eae5b0de078b39ea361a2 (macOS, Python 3.14.6).
deep = cur = {}
for _ in range(3000):
cur["a"] = {}
cur = cur["a"]
redact_json_value(deep) # -> RecursionError
Proposal
- Add a bounded depth to
redact_json_value() (a _depth parameter with a module-level maximum).
On exceeding it, either raise a typed ValueError naming the limit or replace the subtree with a
documented marker — replacing is friendlier on an error path, where producing some envelope beats
producing none.
- Track visited container ids to detect cycles and emit a marker rather than recursing.
- Apply the same bound to
json.dumps() usage by keeping the redaction limit at or below what the
encoder can handle.
- Document the bound in
docs/json-contracts.md next to the existing MAX_JSON_LOG_MESSAGE_LENGTH
and 8 MiB capture limits — this package already documents its other bounds, so this one fits the
established pattern.
Acceptance criteria
redact_json_value() on a structure deeper than the documented limit produces a bounded result or a
typed error, never RecursionError.
- A self-referential
details mapping produces a deterministic, documented outcome.
- Redaction behaviour for ordinary nested data is unchanged, verified by the existing contract fixtures.
- The limit is documented alongside the other JSON contract bounds.
Non-goals
- Do not change which keys are treated as sensitive (tracked separately).
- Do not change the envelope shape.
Problem
redact_json_value()recurses over mappings, lists, and tuples with no depth limit and no cycledetection (
lib/python/base_cli/json_contracts.py:103-116). It is on the error path: everysuccess_envelope(),error_envelope(), anddumps_envelope()call passes consumer-supplieddetailsthrough it before serialization.Two consequences:
detailsmapping raisesRecursionErrorinside envelope construction, so theframework's own error rendering fails while reporting a command failure;
RecursionErrorrather than the clearValueError: Circular reference detectedthatjson.dumps()would produce, because the redactionpass runs first and hits the recursion limit before the encoder can diagnose it.
Deeply nested or cyclic
detailsis not a normal input, which is why this is low severity. But itarrives from consumer data — parsed YAML/JSON from an API response, a config tree, an object graph
someone dropped into
detailsfor diagnostics — and the failure lands in the code path whose job isto report failures reliably.
Verified evidence
Reviewed 2026-09-30 at
a58ec109349fa3f3d03eae5b0de078b39ea361a2(macOS, Python 3.14.6).Proposal
redact_json_value()(a_depthparameter with a module-level maximum).On exceeding it, either raise a typed
ValueErrornaming the limit or replace the subtree with adocumented marker — replacing is friendlier on an error path, where producing some envelope beats
producing none.
json.dumps()usage by keeping the redaction limit at or below what theencoder can handle.
docs/json-contracts.mdnext to the existingMAX_JSON_LOG_MESSAGE_LENGTHand 8 MiB capture limits — this package already documents its other bounds, so this one fits the
established pattern.
Acceptance criteria
redact_json_value()on a structure deeper than the documented limit produces a bounded result or atyped error, never
RecursionError.detailsmapping produces a deterministic, documented outcome.Non-goals