Introduction
Four years ago, a partial security fix to PyJWT, the most popular JWT library in the Python ecosystem, left a gap. Attackers can forge arbitrary authentication tokens by using a different key format from the one that fix blocked. That gap is CVE-2026-48526, a JWK-format bypass of the algorithm confusion protections introduced in PyJWT 2.4.0. It carries a CVSS v3.1 score of 7.4 and enables full authentication bypass when specific preconditions are met.
PyJWT implements RFC 7519 (JSON Web Token) for Python and is maintained by José Padilla. With over 5,700 GitHub stars and deep integration across Django, Flask, FastAPI, and Auth0's own Python SDK, it is the de facto standard for JWT handling in the Python ecosystem. Any vulnerability in PyJWT has a large blast radius across the Python web applications that rely on token-based authentication.
Technical Information
Root Cause: JWK JSON Bypasses HMAC Key Validation
The vulnerability is in the HMACAlgorithm.prepare_key method in jwt/algorithms.py. This method is the gatekeeper for key material used in HMAC signing and verification. The fix for CVE-2022-29217 in PyJWT 2.4.0 updated it to detect PEM-formatted and SSH-formatted public keys and to raise an InvalidKeyError when it found either. The logic was simple. If someone passes an asymmetric public key where an HMAC secret is expected, something has gone wrong.
However, the fix used a blocklist. It explicitly checked for known dangerous formats rather than whitelisting acceptable ones, and JSON Web Key (JWK) format was not on the list. The vulnerable code pattern in versions up through 2.10.1 looked like this:
def prepare_key(self, key: str | bytes) -> bytes: key_bytes = force_bytes(key) if is_pem_format(key_bytes) or is_ssh_key(key_bytes): raise InvalidKeyError(...) return key_bytes
A JWK JSON string, such as {"kty":"RSA","n":"...","e":"AQAB"}, passes straight through both checks. HMAC algorithms accept arbitrary byte strings as secrets, so PyJWT silently accepts the raw text of the JWK document as a valid HMAC key.
Attack Prerequisites
Exploitation requires two conditions to hold at the same time in the target application:
| Condition | Description |
|---|---|
| Mixed algorithm families | The verifier passes both a symmetric algorithm (e.g., HS256) and an asymmetric algorithm (e.g., RS256) in the algorithms list when calling decode() |
| Raw JWK as key argument | The verifier supplies the public key as a raw JWK JSON string via the key parameter instead of using a PEM-encoded key or a dedicated key object |
The CVSS vector reflects this. Attack Complexity is rated High (AC:H) because both conditions must hold. When they do, the Confidentiality and Integrity impacts are both High, and the attacker can forge arbitrary tokens.
Attack Flow
The attack follows the classic JWT algorithm confusion pattern, adapted for the JWK format gap:
-
Obtain the public key in JWK format. The issuer's public key is public by design, so the attacker can fetch it from a publicly accessible endpoint such as
.well-known/jwks.json. -
Craft a malicious token header. The header
{"alg":"HS256","typ":"JWT"}switches the algorithm from the expected asymmetric algorithm (e.g., RS256) to HMAC. -
Set arbitrary claims. Any payload works, such as
{"sub":"alice","admin":true}, so the attacker can impersonate any identity or assume any role. -
Sign with HMAC using the JWK JSON as the secret. The attacker computes an HMAC-SHA256 signature with the raw JWK JSON text as the symmetric key. The verifier will use the same JWK text as its "key" argument, so the signatures will match.
-
Submit the forged token. A vulnerable verifier that calls
decode(token, key=rsa_jwk_json, algorithms=["HS256","RS256"])sees that thealgheader isHS256and routes verification toHMACAlgorithm.prepare_keydoes not reject the JWK string, so the verifier accepts the attacker's forged signature as legitimate.
The result is a complete authentication bypass. The attacker can impersonate any user, escalate privileges, or access any protected resource that trusts the JWT verifier.
Relationship to CVE-2022-29217
This vulnerability is a direct descendant of CVE-2022-29217 (GHSA-ffqj-6fqr-9h24), which was fixed in PyJWT 2.4.0. That earlier fix addressed the same algorithm confusion attack class, but only for PEM and SSH key formats.
| Attribute | CVE-2022-29217 | CVE-2026-48526 |
|---|---|---|
| Advisory | GHSA-ffqj-6fqr-9h24 | GHSA-xgmm-8j9v-c9wx |
| Key format bypassed | PEM, SSH | JWK JSON |
| Fixed version | 2.4.0 | 2.13.0 |
| CVSS | 7.5 | 7.4 |
| Root cause | HMACAlgorithm.prepare_key insufficient validation | Same class, different format gap |
Both vulnerabilities have the same root cause. HMACAlgorithm.prepare_key relied on an enumerated blocklist of key formats rather than a whitelist of acceptable HMAC key types, so any format not explicitly blocked became an attack vector. The JWK gap persisted across every release from 2.4.0 through 2.10.1. Applications that patched CVE-2022-29217 stayed vulnerable to this variant for over two years.
CVSS v3.1 Vector Breakdown
The full vector is CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N, which breaks down as follows:
| Metric | Value | Rationale |
|---|---|---|
| Attack Vector | Network (AV:N) | Exploitable over the network via crafted JWT |
| Attack Complexity | High (AC:H) | Requires mixed algorithm families and JWK key format |
| Privileges Required | None (PR:N) | No authentication needed |
| User Interaction | None (UI:N) | No user action required |
| Scope | Unchanged (S:U) | Impact confined to the vulnerable component |
| Confidentiality | High (C:H) | Full access to protected data |
| Integrity | High (I:H) | Token forgery enables arbitrary identity impersonation |
| Availability | None (A:N) | No direct availability impact |
The NVD assigns both CWE-287 (Improper Authentication) and CWE-347 (Improper Verification of Cryptographic Signature). The flaw is an authentication logic error and a cryptographic verification failure at once.
Patch Information
PyJWT version 2.13.0, released on May 21, 2026, contains a public patch for CVE-2026-48526. Maintainer José Padilla committed the fix in commit 95791b1 and tagged it in 7144e45. GitHub Security Advisory GHSA-xgmm-8j9v-c9wx tracks it.
The patch adds a new guard in HMACAlgorithm.prepare_key after the existing PEM/SSH checks. It strips leading whitespace from the key bytes, checks whether the result starts with {, and if so, tries to parse it as JSON. If parsing succeeds and the resulting dictionary contains a "kty" key (the mandatory JWK field per RFC 7517), the method raises an InvalidKeyError:
# Defense against algorithm-confusion attacks: an attacker with # control over the token header can force this code path by setting # alg=HS*, and HMACAlgorithm is the only algorithm that accepts # arbitrary bytes as a valid secret. stripped = key_bytes.lstrip() if stripped.startswith(b"{"): try: jwk_obj = json.loads(key_bytes) except ValueError: jwk_obj = None if isinstance(jwk_obj, dict) and "kty" in jwk_obj: raise InvalidKeyError( "The specified key looks like a JWK and should not be " "used directly as an HMAC secret. Load it via " "PyJWK / HMACAlgorithm.from_jwk first." )
The check is deliberately narrow. JSON strings without a "kty" field are still accepted as HMAC secrets, because a JSON-shaped string without JWK structure is a legitimate (if unusual) secret. Keeping the check narrow avoids false positives.
The same commit also hardens prepare_key by rejecting empty HMAC keys (len(key_bytes) == 0) outright with an InvalidKeyError. Previously it only emitted a warning. The change catches the common misconfiguration where os.getenv("JWT_SECRET", "") falls through silently.
The fix ships with test coverage. Parametrized tests feed RSA, EC, OKP, and HMAC JWK files into prepare_key and assert that all of them are rejected, and a negative test checks that plain JSON strings without "kty" are still accepted.
To upgrade:
pip install --upgrade PyJWT==2.13.0
Version 2.13.0 also includes four more security fixes for related attack surfaces:
| Advisory | Issue |
|---|---|
| GHSA-jq35-7prp-9v3f | Algorithm allow list bypass via PyJWK/PyJWKClient |
| GHSA-993g-76c3-p5m4 | URI scheme injection in PyJWKClient |
| GHSA-fhv5-28vv-h8m8 | JWK Set cache cleared on transient fetch errors |
| GHSA-w7vc-732c-9m39 | Unauthenticated DoS via b64=false base64 decode |
Organizations should upgrade to 2.13.0 for these hardening measures as well as for the CVE-2026-48526 fix.
Post-Upgrade Verification Checklist
After upgrading, security teams should verify:
- All PyJWT dependencies are at version 2.13.0 or later.
- No
decode()calls mix symmetric and asymmetric algorithm families. - No
decode()calls pass raw JWK JSON strings as thekeyargument. - The
algorithmsparameter is always explicitly specified (never omitted or defaulted).
Affected Systems and Versions
The vulnerability affects PyJWT versions prior to 2.13.0. Specifically:
- Versions 2.4.0 through 2.10.1 are vulnerable to the JWK format bypass described in CVE-2026-48526. These versions contain the CVE-2022-29217 fix (blocking PEM and SSH keys) but lack the JWK guard.
- Versions prior to 2.4.0 are vulnerable to both the original PEM/SSH algorithm confusion (CVE-2022-29217) and the JWK variant.
- The vulnerability is only exploitable when the application both accepts symmetric and asymmetric algorithms in the
algorithmslist and passes a raw JWK JSON string as thekeyargument todecode().
The fixed version is PyJWT 2.13.0.
Vendor Security History
PyJWT has had several algorithm-related vulnerabilities over its lifetime. Together they point to a persistent architectural difficulty in handling the JWT specification's algorithm flexibility safely.
| CVE | Year | Severity | Issue | Fixed Version |
|---|---|---|---|---|
| CVE-2022-29217 | 2022 | 7.5 High | Key confusion via non-blocklisted PEM/SSH key formats | 2.4.0 |
| CVE-2025-45768 | 2025 | N/A | Weak encryption vulnerability in PyJWT v2.10.1 | Under investigation |
| CVE-2026-32597 | 2026 | N/A | Related to GHSA-752w advisory | 2.13.0 |
| CVE-2026-48526 | 2026 | 7.4 High | JWK JSON accepted as HMAC secret (algorithm confusion) | 2.13.0 |
The path from CVE-2022-29217 to CVE-2026-48526 shows what a blocklist does to key format validation. It becomes a game of whack-a-mole, where each new key format representation is a potential bypass. The 2.13.0 release addresses five security advisories at once, which suggests the maintainer has moved toward a more systemic approach to security. The same release adds algorithm binding (GHSA-jq35-7prp-9v3f), a step toward a type-aware verification model instead of relying only on format detection.
CVE-2026-34950, a parallel vulnerability in the Node.js fast-jwt library, confirms that algorithm confusion is still an active, recurring problem for JWT implementations across multiple languages.
References
- NVD: CVE-2026-48526
- CVE Record: CVE-2026-48526
- GitHub Security Advisory: GHSA-xgmm-8j9v-c9wx
- PyJWT 2.13.0 Changelog
- PyJWT GitHub Repository
- PyJWT 2.12.1 to 2.13.0 Comparison
- PyJWT Releases
- PyJWT algorithms.py Source
- CVE-2022-29217 Advisory: GHSA-ffqj-6fqr-9h24
- WorkOS: JWT Algorithm Confusion Attacks
- PortSwigger: Algorithm Confusion Attacks
- VulnCheck: 2026 Routinely Targeted Vulnerabilities
- fast-jwt Algorithm Confusion Re-Enabled (CVE-2026-34950)
- SentinelOne: CVE-2022-29217
- Auth0: How to Handle JWTs in Python
- PyJWT 2.13.0 Documentation



