Sep 30, 2026·5 min read·6 visits
PyJWT fails to catch RecursionError during JSON header parsing, allowing remote attackers to crash request-handling threads with malformed JWTs.
An uncontrolled recursion vulnerability exists in PyJWT from version 2.13.0 to 2.14.0. The vulnerability allows remote, unauthenticated attackers to cause a Denial of Service (DoS) via crafted JWT headers that trigger stack exhaustion during JSON decoding.
PyJWT is a Python implementation of the JSON Web Token (JWT) standards. The library is widely used in web applications to decode and verify security tokens before executing application logic. This role puts PyJWT on the outer perimeter of the application, exposing its parsing code directly to unauthenticated network input.
During the initial decoding stage of a JSON Web Signature (JWS) or JWT, the library parses the base64url-encoded header segment. This parsing must occur before signature verification, as the header contains critical metadata such as the algorithm used to sign the payload. Consequently, any parsing flaw in this phase can be reached without valid credentials or a verified cryptographic signature.
The vulnerability is classified under CWE-674 (Uncontrolled Recursion). By supplying a crafted JWS containing a deeply nested structure in the header, an attacker can exhaust the CPython call stack. This results in an unhandled interpreter exception that escapes the library's exception wrapper and crashes the active processing thread.
The vulnerability lies in how the standard Python json.loads decoder operates and how PyJWT handles the errors it raises. The CPython standard library JSON decoder utilizes a recursive-descent parser. When processing nested JSON arrays or dictionaries, the decoder recursively allocates call frames on the interpreter's stack to track state.
If the recursion depth exceeds CPython's execution limit, which is typically governed by the sys.getrecursionlimit() value, the interpreter raises a native RecursionError. In python's class hierarchy, RecursionError inherits from RuntimeError, which in turn inherits from the base Exception class.
In vulnerable version 2.13.0, the PyJWS._load method wrapped the decoding statement in a try-except block that explicitly intercepted only ValueError. Because RecursionError does not inherit from ValueError, it bypassed the exception handler. The exception then bubbled up through the application web server, causing the handling worker to terminate prematurely and return an HTTP 500 error.
The vulnerability exists in jwt/api_jws.py inside the internal helper method _load. This method is responsible for splitting the three-part compact token representation, decoding the payload, and reconstructing the Python dictionary representing the header.
Below is the vulnerable implementation of the parsing try-block:
try:
header: dict[str, Any] = json.loads(header_data)
except ValueError as e:
raise DecodeError(f"Invalid header string: {e}") from eBecause the block only intercepts ValueError, a RecursionError is raised directly back to the caller. This behavior violates the PyJWT contract, which guarantees that all parsing and encoding anomalies are translated into a subclass of PyJWTError.
The patch remediates this issue by explicitly catching RecursionError alongside ValueError as shown in the diff below:
try:
header: dict[str, Any] = json.loads(header_data)
except (ValueError, RecursionError) as e:
raise DecodeError(f"Invalid header string: {e}") from eThis modification ensures that if stack exhaustion occurs during the JSON parsing phase, it is normalized to a standard DecodeError. Web frameworks and application controllers that rely on PyJWT to handle authentication can then gracefully catch DecodeError and issue a standard HTTP 401 or 403 status code.
Exploitation does not require high technical complexity. An attacker begins by constructing a malformed JSON payload containing deeply nested brackets. For example, an array nested within one thousand other arrays ([[[[...]]]]) is sufficient to exceed standard recursion limits.
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ ... ]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
The attacker base64url-encodes this nested JSON string to generate the first token segment. They then append two arbitrary strings separated by periods, representing a dummy payload and a dummy signature. This constructs a well-formed compact JWS token layout containing a malicious header.
When the target web service receives the token, it forwards the string to PyJWT. PyJWT attempts to decode the header first. The parser exhausts the execution stack, raises a RecursionError, and crashes the thread. In asynchronous or multi-threaded environments, sending a steady stream of these payloads can exhaust the web server's available thread pool, inducing complete application unavailability.
The impact of this vulnerability is a remote, unauthenticated Denial of Service (DoS). Because the header is decoded prior to signature verification, the attacker does not need to possess a valid signing key, access token, or active session on the target application.
While the vulnerability does not allow remote code execution or data extraction, the operational cost of unhandled thread termination can be significant. High-performance web applications running on single-process event loops can experience severe latency spikes or temporary outages if the primary event handler is blocked or terminated.
This vulnerability has been assigned a CVSS v3.1 score of 5.3 (Medium). The vector string is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L. This reflects network-based availability impact with low attack complexity and no privilege requirements.
The primary remediation is upgrading PyJWT to version 2.14.0 or higher. This version implements the revised exception handling wrapper, converting the raw RecursionError into a library-controlled DecodeError.
For systems where upgrading PyJWT is not immediately viable, a defense-in-depth approach can be deployed. Web application firewalls (WAFs) should be configured to inspect authorization headers and block request parameters containing repeating arrays or dictionary characters before they reach the application interpreter.
Additionally, developers can implement a middleware check that measures the length and structural nesting of the header segment before calling PyJWT. If a token contains more than 50 opening bracket characters, the middleware can reject the transaction outright.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
PyJWT jpadilla | >= 2.13.0, < 2.14.0 | 2.14.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-674 (Uncontrolled Recursion) |
| Attack Vector | Network |
| CVSS Score | 5.3 (Medium) |
| EPSS Score | 0.00291 (0.29%) |
| Exploit Status | Proof of Concept |
| CISA KEV Status | Not Listed |
The software implements a function that calls itself or relies on recursive parsing libraries without restricting the depth of recursion, allowing stack exhaustion.
A Denial of Service (DoS) vulnerability exists in the PyJWT library when parsing unverified token payloads containing deeply nested JSON structures. Because PyJWT fails to catch RecursionError during payload parsing, an unauthenticated remote attacker can crash the application thread or worker by sending a specially crafted token.
An improper output encoding and escaping vulnerability (CWE-116) in Vercel Satori allows unauthenticated remote attackers to perform markup injection in dynamic Open Graph images generated via Next.js's ImageResponse. Unsanitized parameter interpolation into SVG elements breaks XML structural boundaries. This exposes downstream parsing, rasterization, and rendering pipelines to Server-Side Request Forgery (SSRF), Local File Read, and Remote Code Execution (RCE).
A signature verification bypass vulnerability in PyJWT allows unauthenticated remote attackers to forge JSON Web Tokens when processing JSON Web Key Sets containing an empty symmetric key.
A Regular Expression Denial of Service (ReDoS) vulnerability exists in Nodemailer's addressparser fallback engine before version 10.0.6. Under specific malformed inputs with excessive word boundaries, the parser exhibits quadratic backtracking, leading to high CPU utilization and event loop blockage.
Nodemailer versions prior to 10.0.9 are vulnerable to a parser differential bug. When processing a quoted local-part followed by an RFC 5322 comment and trailing characters, the internal addressparser module fails to order its normalization routine correctly. This error results in the generation of malformed envelope recipient addresses containing injected whitespace and secondary domains, allowing attackers to bypass routing restrictions and exfiltrate sensitive emails.
CVE-2026-102276 is a high-severity Denial of Service (DoS) vulnerability impacting the 'brace-expansion' library, a popular Node.js utility designed to expand brace patterns into combinatorial lists. Due to uncontrolled recursion and argument-list stack exhaustion within the internal parseCommaParts function, remote attackers can trigger an unhandled RangeError that abruptly terminates the Node.js process.