Sep 30, 2026·8 min read·39 visits
PyJWT versions 2.13.0 to 2.14.0 fail to validate key lengths on PyJWK paths, allowing attackers to forge tokens with an empty HMAC key if a trusted JWK contains an empty 'k' parameter.
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.
CVE-2026-102266 represents a signature verification bypass vulnerability in PyJWT, a prominent Python library implementing JSON Web Token (JWT) and JSON Web Signature (JWS) standards. The flaw specifically affects PyJWT versions 2.13.0 up to, but not including, 2.14.0. The bug resides within the signature validation pathway for tokens verified using JSON Web Keys (JWKs) or JSON Web Key Sets (JWKS). Under specific configurations, this allows an unauthenticated remote attacker to bypass cryptographic trust controls and forge arbitrary JWT assertions.
The failure is located in the key handling discrepancy between raw cryptographic keys and keys encapsulated within a PyJWK object. When PyJWT decodes a token using a raw key, it executes input validation checks to prevent the use of weak or empty keys. However, when parsing and processing keys extracted from a PyJWK instance, the verification pipeline bypassed these validation steps. This architectural oversight allowed the verification process to execute using an empty byte-string secret key when a trusted JWK structure contained empty material.
Because the library defaulted to raising warnings instead of exceptions for inadequate key lengths, the verification function proceeded to calculate and validate signatures using an empty HMAC key. Consequently, if an application relies on a JWK Set containing a symmetric key with an empty representation, an offline attacker can construct a valid signature using a null-byte key. This results in complete validation bypass, granting the attacker the ability to inject arbitrary claims and assume unauthorized identities within the application context.
The root cause of CVE-2026-102266 lies in the dual-path design of the key validation logic inside PyJWT. Historically, PyJWT implemented a validation pipeline for raw cryptographic keys through the prepare_key method of individual algorithm classes. For HMAC algorithms, this method verified that the key length was non-zero and threw an InvalidKeyError if an empty key was provided. This safe-default behavior ensured that any attempt to initialize an HMAC verification operation with a blank key would fail immediately prior to execution.
In contrast, when the verification pipeline utilized a PyJWK key representation (often dynamically resolved from JWK Sets), the code bypassed this central validation checkpoint. Instead of invoking the algorithm's specialized prepare_key method, the JWS verification layer directly retrieved the raw internal key buffer from the PyJWK object. If the source JWK entry was a symmetric oct key with an empty k parameter, the Base64URL decoding mechanism parsed this parameter into an empty byte string b"" without triggering any initialization errors.
This dual-path vulnerability was compounded by the default handling of key length violations within PyJWT. When the enforce_minimum_key_length configuration option is disabled, which is the default setting, the library only emits an InsecureKeyLengthWarning instead of raising an exception when encountering extremely short keys. Because the empty key path did not trigger an upfront validation failure, the verification function continued its execution path, comparing the incoming signature against an HMAC value generated with a zero-length secret key.
To understand the mechanics of the vulnerability, we analyze the key loading and signature validation files. The code block below contrasts the vulnerable key loading path in jwt/api_jws.py with the corrected implementation of version 2.14.0. Prior to the fix, the validation logic directly extracted the internal raw key representation from the PyJWK object without invoking the algorithm-specific key preparation logic.
# Vulnerable code in PyJWS._verify_signature (jwt/api_jws.py)
alg_obj = key.Algorithm
prepared_key = key.key # Bypasses prepare_key validation entirely
# Patched code in PyJWS._verify_signature (jwt/api_jws.py)
alg_obj = key.Algorithm
prepared_key = alg_obj.prepare_key(key.key) # Core Fix: apply prepare_key checksBy replacing key.key with alg_obj.prepare_key(key.key), the patch ensures that any key resolved from a PyJWK context is forced through the same validation checks as a raw key, causing the library to throw an InvalidKeyError if the key length is zero.
The secondary patch acts as a defense-in-depth measure within the JWK parser in jwt/algorithms.py. When an HMAC key is loaded via the from_jwk interface, the updated logic validates the length of the decoded key material before creating the internal representation. If the decoded byte sequence is empty, the parser raises an InvalidKeyError during key initialization, preventing the creation of a vulnerable PyJWK instance entirely.
# Patched code in HMACAlgorithm.from_jwk (jwt/algorithms.py)
key_bytes = base64url_decode(obj["k"])
if len(key_bytes) == 0:
raise InvalidKeyError("HMAC key must not be empty.") # Prevent loading empty oct keys
return key_bytesExploitation of CVE-2026-102266 requires specific environmental criteria but presents a low attack complexity once those criteria are met. The target application must process token signatures using keys retrieved from a JSON Web Key Set (JWKS). Crucially, the JWKS must contain an oct key with an empty key parameter "k": "". This condition often arises in testing environments, misconfigured directory services, or situations where keys are populated from uninitialized databases.
Once an empty oct key is present in the active JWKS, the attacker can execute the exploit completely offline. The attacker constructs a JWT payload with administrative privileges or desired claims, referencing the identifier (kid) of the empty key in the header. The attacker then generates an HMAC-SHA256 signature using a zero-length byte string (b"") as the secret key. The resulting base64url-encoded signature is appended to the token header and payload.
The following test script demonstrates how the signature generation is constructed and demonstrates how vulnerable versions of PyJWT accept the forged token, while patched versions throw an exception:
import base64
import hashlib
import hmac
import jwt
def test_exploit():
empty_jwk = {
"kty": "oct",
"k": "",
"kid": "active",
"alg": "HS256"
}
jwk_obj = jwt.PyJWK.from_dict(empty_jwk)
signing_input = b"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdHRhY2tlciJ9"
signature_bytes = hmac.new(b"", signing_input, hashlib.sha256).digest()
encoded_signature = base64.urlsafe_b64encode(signature_bytes).rstrip(b"=")
token = (signing_input + b"." + encoded_signature).decode()
try:
decoded = jwt.decode(token, jwk_obj, algorithms=["HS256"])
print("Verification Succeeded: " + str(decoded))
except jwt.InvalidKeyError as e:
print("Verification Blocked: " + str(e))The impact of CVE-2026-102266 is high because it allows complete authentication and authorization bypass. An attacker who successfully exploits this vulnerability can forge valid cryptographic signatures representing any user identity, including administrative accounts. In typical deployments where JWTs are used to transmit authorization claims, this enables unauthorized privilege escalation, complete data access, and administrative control over the affected application.
The National Vulnerability Database assigns this vulnerability a CVSS score of 7.4. The vector string is CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N. The attack vector is Network, indicating remote exploitation capability. The complexity is High because of the prerequisite that the trust store contains an empty key. Confidentially and integrity impacts are both rated High, as arbitrary operations can be performed on behalf of simulated user sessions.
Although no active in-the-wild exploitation has been recorded by CISA or threat intelligence networks, the availability of clear proof-of-concept material increases the threat profile. Software developers and operations teams must prioritize remediation, particularly in multi-tenant environments where JWK Sets are dynamically fetched or managed by tenant-controlled identity providers.
The primary remediation strategy is upgrading the PyJWT library to version 2.14.0 or higher. This version integrates the dual fixes that enforce strict key length validations on the PyJWK verification path and block empty HMAC keys during dynamic JWK parsing. The update is backwards-compatible and can be applied through python package managers using standard commands such as pip install --upgrade pyjwt.
When immediate upgrading is not feasible, several defensive workarounds can be applied at the application or gateway layer. Security teams should enforce key length validations by explicitly passing the enforce_minimum_key_length configuration option as True during token decoding. This ensures that even if an empty key bypasses the initial check, the signature validation phase raises an error due to the insufficient key length.
Additionally, applications that ingest remote JWK Sets should sanitize incoming keys. The sanitization logic must inspect the key list and explicitly reject any oct key where the k parameter is absent, null, or is an empty string. This preventative step blocks the entry of the vulnerable condition into the active key memory, mitigating the root cause of the signature bypass.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N| Attribute | Detail |
|---|---|
| CWE ID | CWE-347 |
| Attack Vector | Network (AV:N) |
| CVSS | 7.4 |
| EPSS | 0.00175 |
| Impact | Authentication Bypass / Privilege Escalation |
| Exploit Status | Proof-of-Concept |
| KEV Status | Not Listed |
The software imports or uses a cryptographic key or signature but does not verify or improperly verifies the signature, allowing attackers to bypass authentication or integrity checks.
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.