Automated Threat Modeling

PyJWT CVE-2026-48526: Brief Summary of JWK Algorithm Confusion Leading to Token Forgery

A brief summary of CVE-2026-48526, a high-severity algorithm confusion vulnerability in PyJWT that lets attackers forge JWT tokens through a gap in HMAC key validation for JWK-format keys. Includes patch details and mitigation guidance.

CVE Analysis

10 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-05-28

PyJWT CVE-2026-48526: Brief Summary of JWK Algorithm Confusion Leading to Token Forgery
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

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:

ConditionDescription
Mixed algorithm familiesThe 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 argumentThe 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:

  1. 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.

  2. Craft a malicious token header. The header {"alg":"HS256","typ":"JWT"} switches the algorithm from the expected asymmetric algorithm (e.g., RS256) to HMAC.

  3. Set arbitrary claims. Any payload works, such as {"sub":"alice","admin":true}, so the attacker can impersonate any identity or assume any role.

  4. 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.

  5. Submit the forged token. A vulnerable verifier that calls decode(token, key=rsa_jwk_json, algorithms=["HS256","RS256"]) sees that the alg header is HS256 and routes verification to HMACAlgorithm. prepare_key does 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.

AttributeCVE-2022-29217CVE-2026-48526
AdvisoryGHSA-ffqj-6fqr-9h24GHSA-xgmm-8j9v-c9wx
Key format bypassedPEM, SSHJWK JSON
Fixed version2.4.02.13.0
CVSS7.57.4
Root causeHMACAlgorithm.prepare_key insufficient validationSame 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:

MetricValueRationale
Attack VectorNetwork (AV:N)Exploitable over the network via crafted JWT
Attack ComplexityHigh (AC:H)Requires mixed algorithm families and JWK key format
Privileges RequiredNone (PR:N)No authentication needed
User InteractionNone (UI:N)No user action required
ScopeUnchanged (S:U)Impact confined to the vulnerable component
ConfidentialityHigh (C:H)Full access to protected data
IntegrityHigh (I:H)Token forgery enables arbitrary identity impersonation
AvailabilityNone (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:

AdvisoryIssue
GHSA-jq35-7prp-9v3fAlgorithm allow list bypass via PyJWK/PyJWKClient
GHSA-993g-76c3-p5m4URI scheme injection in PyJWKClient
GHSA-fhv5-28vv-h8m8JWK Set cache cleared on transient fetch errors
GHSA-w7vc-732c-9m39Unauthenticated 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:

  1. All PyJWT dependencies are at version 2.13.0 or later.
  2. No decode() calls mix symmetric and asymmetric algorithm families.
  3. No decode() calls pass raw JWK JSON strings as the key argument.
  4. The algorithms parameter 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 algorithms list and passes a raw JWK JSON string as the key argument to decode().

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.

CVEYearSeverityIssueFixed Version
CVE-2022-2921720227.5 HighKey confusion via non-blocklisted PEM/SSH key formats2.4.0
CVE-2025-457682025N/AWeak encryption vulnerability in PyJWT v2.10.1Under investigation
CVE-2026-325972026N/ARelated to GHSA-752w advisory2.13.0
CVE-2026-4852620267.4 HighJWK 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

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization