GHSA-xf7x-x43h-rpqh json_repair: Circular JSON Schema `$ref` causes unbounded CPU DoS
Circular JSON Schema `$ref` causes unbounded CPU DoS in `json_repair`Summary`SchemaRepairer.resolve_schema()` in `json_repair` follows JSON Schema `$ref` pointers in an unbounded `while` loop without any cycle detection. An attacker who can supply a schema containing a self-referencing `$ref` (e.g., via the demo Flask API or any application that passes untrusted input to `loads(..., schema=...)`), can cause a worker process to spin indefinitely on CPU, resulting in a complete denial of service. No authentication is required against the public demo API. The vulnerability is confirmed reproducible at CVSS 7.5 (High).Details`SchemaRepairer.resolve_schema()` at `src/json_repair/schema_repair.py:184–190` resolves `$ref` chains using a plain `while` loop:`_resolve_ref()` at `src/json_repair/schema_repair.py:654–665` always resolves references relative to `self.root_schema`, which is initialised from the caller-supplied schema (`src/json_repair/schema_repair.py:130`). When the schema contains a circular reference such as:`_resolve_ref()` returns the same `dict` object on every iteration, so `"$ref" in schema_dict` is always `True` and the loop never terminates.The vulnerable sink is reachable without authentication through the demo Flask API:The only guard is a top-level `isinstance(dict, bool)` check; there is no `$ref` depth limit, no visited-set, and no timeout enforced by the library. The full data-flow path is:`docs/app.py:14` — `request.get_json()` reads the attacker-controlled HTTP body.`docs/app.py:21–23` — `schema` is extracted; only `dict`/`bool` type check applied.`docs/app.py:33–36` — schema is forwarded verbatim to `loads()`.`src/json_repair/json_repair.py:145–148` — `schema_from_input(schema)` instantiates `SchemaRepairer`.`src/json_repair/json_repair.py:160` — `repairer.is_valid()` calls `resolve_schema()`, triggering the infinite loop.`src/json_repair/schema_repair.py:184–190` — unbounded `while "$ref" in schema_dict` loop (sink).`src/json_repair/schema_repair.py:654–665` — `_resolve_ref()` returns the same object on every call.**Recommended fix:**PoC**Environment setup:****Alternatively, use the provided Docker image:****HTTP attack request (demo API):****Direct library attack:****Observed results (from Docker-based dynamic reproduction):**Baseline (valid schema `{"type":"object","properties":{"name":{"type":"string"}}}`): completed in **0.261 s**.Attack (circular `$ref` schema): **timed out after 5.01 s** — process killed; infinite loop confirmed.ImpactThis is an unauthenticated **denial-of-service** vulnerability. Any single HTTP request carrying a circular `$ref` schema hangs the Flask worker process indefinitely, making the service unavailable to all other users until the process is killed or the server is restarted. Because the public demo API (`docs/app.py`) accepts the `schema` field from the request body without authentication and passes it directly to `loads()`, remote attackers can exploit this with a trivial one-liner.Beyond the demo API, any application that exposes `json_repair.loads(..., schema=<user-controlled>)` to untrusted callers is equally affected. The vulnerability requires no special privileges, produces no useful output for the attacker (confidentiality and integrity are unaffected), and is deterministically reproducible.Reproduction artifacts`Dockerfile``poc.py`
CVSS v3.1 score: 7.5