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
Published August 22, 2026Reviewed 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

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.