Back to Blog
high SEVERITY7 min read

How Denial of Service via Unbounded Recursion happens in Python JSON parsing and how to fix it

A high-severity denial of service vulnerability (CVE-2025-67221) was discovered in orjson 3.10.16, where deeply nested JSON documents could trigger unbounded recursion and crash the application. The fix upgrades orjson to version 3.11.6, which implements recursion depth limits to prevent stack exhaustion attacks.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 22, 2026•Reviewed August 22, 2026

Answer Summary

CVE-2025-67221 is a denial of service vulnerability in orjson (Python's fast JSON library) caused by unbounded recursion when parsing deeply nested JSON documents, mapped to CWE-674 (Uncontrolled Recursion). Attackers can craft malicious JSON payloads with hundreds or thousands of nested arrays/objects to exhaust the call stack and crash the application. The fix upgrades from orjson 3.10.16 to 3.11.6, which enforces recursion depth limits during JSON deserialization to prevent stack overflow attacks.

Vulnerability at a Glance

cweCWE-674 (Uncontrolled Recursion)
fixUpgrade orjson to 3.11.6 with built-in recursion guards
riskApplication crash from malicious JSON input
languagePython
root causeNo recursion depth limit when parsing nested JSON structures
vulnerabilityDenial of Service via Unbounded Recursion

Introduction

In the asn_coverage project, a high-severity vulnerability was discovered in the asn_coverage/uv.lock dependency file. The project was using orjson version 3.10.16, a high-performance JSON serialization library for Python. This version contained CVE-2025-67221, a denial of service vulnerability that allows attackers to crash the application by sending deeply nested JSON documents. The vulnerability affects any code path that processes JSON input from untrusted sources—a common pattern in web APIs, data processing pipelines, and configuration parsers.

The specific issue lies in how orjson 3.10.16 handles recursive descent during JSON deserialization. When parsing nested structures like {"a": {"b": {"c": {...}}}} or [[[[...]]]], the parser calls itself recursively for each level. Without a depth limit, an attacker can craft a payload with thousands of nesting levels that exhausts the Python call stack, triggering a RecursionError and crashing the application.

The Vulnerability Explained

CVE-2025-67221 is a classic example of uncontrolled recursion (CWE-674). Here's what makes this vulnerability dangerous:

The Vulnerable Code Pattern

In orjson 3.10.16, the JSON deserialization logic recursively processes nested structures without enforcing a maximum depth. While the exact implementation is in Rust (orjson's core is written in Rust for performance), the vulnerability manifests when Python code calls orjson.loads() on malicious input:

import orjson

# Vulnerable code using orjson 3.10.16
def process_json_data(json_string):
    # No depth validation before parsing
    data = orjson.loads(json_string)
    return data

The problem isn't in the application code—it's in the library itself. When orjson.loads() encounters deeply nested JSON, it recursively descends through each level:

orjson.loads() → parse_object() → parse_value() → parse_object() → parse_value() → ...

Attack Scenario

Consider the asn_coverage application, which likely processes JSON data related to ASN (Autonomous System Number) coverage information. An attacker could exploit this in several ways:

Scenario 1: API Endpoint Attack

# Attacker sends a POST request to /api/coverage
POST /api/coverage HTTP/1.1
Content-Type: application/json

{"data": {"level1": {"level2": {"level3": { ... 5000 more levels ... }}}}}

Scenario 2: Malicious Configuration File
If the application reads JSON configuration files, an attacker with write access could replace a config file with:

[[[[[[[[[[[[[[[[[[[[...2000 levels deep...]]]]]]]]]]]]]]]]]]]]

Scenario 3: Data Processing Pipeline
If asn_coverage processes JSON from external sources (S3 buckets, API responses, etc.), a compromised upstream service could inject malicious JSON:

# Application code in asn_coverage
import boto3
import orjson

s3 = boto3.client('s3')
response = s3.get_object(Bucket='asn-data', Key='coverage.json')
data = orjson.loads(response['Body'].read())  # Vulnerable!

Real-World Impact

The impact of this vulnerability is significant:

  1. Service Availability: A single malicious request can crash the entire application, causing downtime
  2. Resource Exhaustion: Stack overflow errors can leave the process in an unstable state
  3. Cascading Failures: In containerized environments, repeated crashes can trigger restart loops
  4. Security Monitoring Blind Spots: DoS attacks can mask other malicious activity

The vulnerability is particularly dangerous because:
- JSON is ubiquitous in modern applications
- The payload can be surprisingly small (a few KB can contain thousands of nesting levels)
- It requires no authentication—any endpoint accepting JSON is vulnerable
- It's trivial to exploit with automated tools

The Fix

The fix is straightforward but critical: upgrade orjson from version 3.10.16 to 3.11.6. Here's what changed:

Before (Vulnerable Code)

# asn_coverage/pyproject.toml
dependencies = [
    "arrow>=1.3.0",
    "boto3>=1.37.36",
    "click>=8.1.8",
    "orjson>=3.10.16",  # Vulnerable version
    "requests>=2.32.3",
]

After (Secure Code)

# asn_coverage/pyproject.toml
dependencies = [
    "arrow>=1.3.0",
    "boto3>=1.37.36",
    "click>=8.1.8",
    "orjson>=3.11.6",   # Fixed version with recursion limits
    "requests>=2.32.3",
]

Lock File Updates

The fix also updates the uv.lock file to ensure consistent dependency resolution:

 version = 1
-revision = 1
+revision = 3
 requires-python = ">=3.12"

The revision bump from 1 to 3 indicates that the dependency tree was recalculated to incorporate the security fix.

How This Change Solves the Problem

orjson 3.11.6 introduces recursion depth limits during JSON deserialization. The library now tracks nesting depth and rejects payloads that exceed safe thresholds (typically 1024 levels). This prevents stack exhaustion while still supporting legitimate use cases:

# With orjson 3.11.6
import orjson

# Legitimate JSON: works fine
data = orjson.loads('{"user": {"profile": {"settings": {"theme": "dark"}}}}')

# Malicious JSON: raises exception before stack overflow
malicious = '{"a":' * 5000 + '{}' + '}' * 5000
try:
    orjson.loads(malicious)
except orjson.JSONDecodeError as e:
    print(f"Rejected: {e}")  # "Rejected: recursion limit exceeded"

Why Both Files Changed

The fix modified two files for complete protection:

  1. pyproject.toml: Specifies the minimum safe version (>=3.11.6) so future installations get the fix
  2. uv.lock: Locks the exact resolved version to ensure reproducible builds and prevent accidental downgrades

This two-file approach is critical for Python projects using modern dependency management. Without updating the lock file, developers might still install the vulnerable version despite the pyproject.toml change.

Key Takeaways

  • orjson 3.10.16 allows unbounded recursion when parsing deeply nested JSON, enabling trivial DoS attacks against any endpoint accepting JSON input
  • The asn_coverage project's dependency files (pyproject.toml and uv.lock) required synchronized updates to enforce the minimum safe version across all environments
  • Small payloads can cause big damage: A JSON document with 5000 nesting levels can be just a few kilobytes but will crash most applications
  • Library updates alone aren't enough: Implement application-level depth checks, size limits, and monitoring to detect exploitation attempts
  • Lock files are security-critical: Always commit and update lock files (uv.lock, poetry.lock, Pipfile.lock) when fixing dependency vulnerabilities to prevent version drift

How Orbis AppSec Detected This

  • Source: JSON input from external sources (API requests, S3 objects, configuration files) processed by the asn_coverage application
  • Sink: orjson.loads() calls in version 3.10.16, which lacks recursion depth limits during deserialization
  • Missing control: No maximum nesting depth validation before or during JSON parsing
  • CWE: CWE-674 (Uncontrolled Recursion)
  • Fix: Upgraded orjson dependency from 3.10.16 to 3.11.6, which enforces recursion limits and rejects excessively nested JSON documents

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2025-67221 demonstrates how a seemingly simple operation—parsing JSON—can become a critical security vulnerability when recursion isn't properly bounded. The upgrade from orjson 3.10.16 to 3.11.6 is essential for any Python application processing JSON from untrusted sources. Beyond this specific fix, developers should adopt a defense-in-depth approach: validate input depth, monitor for anomalies, and regularly scan dependencies for known vulnerabilities.

The asn_coverage project's fix serves as a model for responsible dependency management: update both the dependency specification and lock file, document the security rationale, and verify that the change preserves expected behavior. By staying vigilant about dependency security and implementing multiple layers of protection, you can build resilient applications that withstand both known and emerging threats.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #319

Related Articles

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.

high

CVE-2026-54673: builder-util-runtime Leaks Auth Headers on Redirect

electron-updater and electron-builder rely on builder-util-runtime to fetch update manifests and artifacts over HTTP. A flaw in that shared HTTP executor allowed credential headers attached to the original update-feed request to be re-sent after a redirect, exposing them to any host the redirect pointed to. The project fixes this by upgrading builder-util-runtime to 9.7.0 and collapsing a duplicate, older copy of the package that electron-updater had pinned on its own.

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.