Skip to content

bug: redact_json_value recurses without depth or cycle limits #395

Description

@codeforester

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

  1. 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.
  2. Track visited container ids to detect cycles and emit a marker rather than recursing.
  3. Apply the same bound to json.dumps() usage by keeping the redaction limit at or below what the
    encoder can handle.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething is not working

Type

No type

Projects

  • Status
    Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions