Sep 30, 2026·5 min read·19 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.
CVE-2026-128087 (GHSA-9264-jvgf-6w2f) is an incorrect behavior order (CWE-696) and improper authorization (CWE-863) vulnerability in the russh Rust SSH library, affecting versions 0.55.0 through 0.65.0. During the initial SSH key exchange (KEX) handshake, the client state machine invokes the client::Handler::kex_done() callback—providing the negotiated raw shared secret bytes—prior to validating the remote server's host key or certificate via check_server_key(). Consequently, an untrusted or rogue SSH server can complete cryptographic key agreement and force a connecting client to execute side-effecting application logic inside kex_done() before the server's identity is checked and rejected.
CVE-2026-128082 (GHSA-6vhm-j9h7-f9gh) is an uncontrolled recursion vulnerability (CWE-674) in AutoMapper, a convention-based object-object mapper for .NET. When mapping nested or self-referential types into destination models that rely on parameterized constructors, C# positional records, or recursive ConstructUsing expressions, AutoMapper evaluates child constructor arguments prior to incrementing the parent's recursion depth counter. This ordering flaw bypasses both explicit MaxDepth limits and the default depth limit of 64. An unauthenticated remote attacker can supply deeply nested input payloads to affected endpoints, exhausting the thread call stack and triggering an uncatchable System.StackOverflowException that terminates the hosting .NET process.
An information exposure vulnerability in the @opentelemetry/instrumentation-http package for OpenTelemetry JavaScript versions 0.204.0 prior to 0.222.0 causes incoming HTTP server spans to record raw, unredacted query string parameters under the url.query attribute. While outgoing client spans applied built-in and user-configured query parameter redaction, incoming server spans bypassed this sanitization completely, resulting in the transmission of sensitive credentials such as pre-signed cloud URLs and API tokens to external observability backends.
CVE-2026-128089 is an uncontrolled resource consumption vulnerability in the dnspython toolkit for Python, affecting all versions prior to 2.9.2. When parsing Well-Known Service (WKS) records from text via dns.rdtypes.IN.WKS.WKS.from_text(), the parser fails to validate that numeric service port identifiers fall within the RFC 1035 valid range of 0 through 65535. An attacker capable of supplying untrusted zone files or RDATA text strings containing an arbitrarily large service port integer can force the parser to allocate an unbounded bitmap array using an inefficient linear append loop, resulting in severe CPU exhaustion, application hangs, or Out-Of-Memory (OOM) process termination.
CVE-2026-126347 (GHSA-5cjw-f85j-cv6r) is an open redirect vulnerability (CWE-601) in Tornado, an asynchronous Python networking library and web framework. The flaw resides in tornado.web.RedirectHandler when dynamic URL redirection templates incorporate unvalidated user input. Due to a parser differential between RFC 3986 (used by Python's urllib.parse.urlsplit) and the WHATWG URL Standard (used by modern web browsers), inputs containing three or more leading slashes bypass Tornado 6.6.0's validation logic, allowing unauthenticated attackers to redirect users to arbitrary external domains.
A Denial of Service (DoS) vulnerability exists in @sveltejs/adapter-node versions 5.5.7 and 6.0.0-next.0 through 6.0.0. When a SvelteKit server route returns a direct response from a fetch() call with a Content-Type of text/event-stream, the Node.js adapter attempts to mutate the response headers to disable proxy buffering. Because headers returned directly by the WHATWG Fetch API carry an immutable guard, this mutation throws an unhandled TypeError, terminating the underlying Node.js process and denying service to all active and subsequent clients.