SECURITYCRITICALCVE-2026-102268

CVE-2026-102268: PyJWT is_pem_format PEM Validation Bypass

A PEM-detection gap in PyJWT lets mutated public-key PEMs slip past its HMAC guard, enabling JWT signature forgery.

Dylan H.

Security Team

September 29, 2026
6 min read
CVE-2026-102268: PyJWT is_pem_format PEM Validation Bypass

Critical severity

Rated critical. Prioritise patching — see the remediation guidance below.

Affected Products

  • PyJWT (versions prior to 2.14.0)

Overview

PyJWT is the most widely used Python implementation of the JSON Web Token (JWT) standard, embedded in countless authentication and API-authorization flows. A critical vulnerability tracked as CVE-2026-102268 (CVSS 9.1) has been disclosed in versions prior to 2.14.0, centered on the library's is_pem_format helper in jwt/utils.py.

The flaw is a PEM-format detection weakness: is_pem_format does not recognize every PEM representation that the underlying cryptography library's loader will happily accept. In deployments that verify tokens against an allow-list mixing HMAC (HS256) and asymmetric (e.g. RS256/ES256) algorithms, this gap can let a public key be silently reinterpreted as an HMAC secret — the classic algorithm-confusion primitive that has plagued JWT libraries for years. When that happens, anyone who knows the (public, by definition) verification key can forge tokens that pass signature verification.

This matters because algorithm-confusion bugs in JWT tooling are not theoretical curiosities — they translate directly into authentication bypass and full token forgery, often across every service that trusts the affected verifier.


Technical Details

FieldValue
CVE IDCVE-2026-102268
SeverityCritical (CVSS 9.1)
CWECWE-347 (Improper Verification of Cryptographic Signature)
Componentjwt/utils.py — is_pem_format(), consumed by the guard in jwt/algorithms.py (HMACAlgorithm.prepare_key)
Affected VersionsPyJWT versions prior to 2.14.0
Fixed VersionPyJWT 2.14.0 (released 2026-09-11)
ImpactJWT signature/algorithm-confusion bypass — forged HMAC-signed tokens accepted as valid

How It Works

PyJWT already carries a guard, added for an earlier issue (CVE-2022-29217), that is supposed to reject an asymmetric key when it's handed to an HMAC algorithm. That guard only fires when is_pem_format() recognizes the input as PEM data — and that recognizer is stricter than it needs to be.

is_pem_format() matches against a regular expression that expects a fairly rigid structure: a BEGIN marker followed by an LF-terminated line, body content, and a matching END marker, with only spaces and hyphens tolerated immediately around the markers. The cryptography library's load_pem_public_key(), which actually parses the key material, is considerably more permissive — it accepts:

  • Extra whitespace (tabs or spaces) directly adjacent to the BEGIN/END markers
  • Bare carriage-return (\r-only) line terminators instead of the \r?\n the regex requires
  • PEM content folded onto a single line

Any of these forms can arise organically — a PEM value indented inside a YAML or JSON config block, a key passed through a single-line environment variable, or a file that has been through a CR/LF round-trip in some tooling. When a key in one of these forms is used, is_pem_format() returns False, so the asymmetric-key guard never engages, even though the exact same bytes load successfully as a valid public key elsewhere in the pipeline. HMACAlgorithm.prepare_key then treats those raw bytes as an HMAC secret. If the application's verification call allows both an HMAC and an asymmetric algorithm (e.g. algorithms=["HS256", "ES256"]), an attacker who has the (public) verification key can compute a valid HS256 signature over a token of their choosing, and PyJWT will accept it as authentic.


Impact Assessment

Who Is At Risk

Any application that:

  • Uses PyJWT to verify incoming JWTs, and
  • Configures its algorithms=[...] allow-list to accept both an HMAC algorithm (HS256/HS384/HS512) and an asymmetric algorithm (RS256, ES256, etc.) on the same verification path, and
  • Has not yet upgraded to PyJWT 2.14.0 or later

is potentially exposed, regardless of whether the application itself ever intended to issue HMAC-signed tokens.

Potential Attack Chains

  1. Key acquisition — Attacker obtains the application's public verification key (by definition public — often published in a JWKS endpoint, embedded in client code, or otherwise not secret).
  2. PEM mutation — Attacker (or, more realistically, the normal handling pipeline) supplies that key in one of the loader-accepted-but-regex-missed forms (extra whitespace near markers, CR-only line endings, single-line folding).
  3. Guard bypass — is_pem_format() fails to flag the key as PEM, so PyJWT's asymmetric-key rejection never triggers.
  4. Algorithm confusion — HMACAlgorithm.prepare_key treats the public key bytes as an HMAC secret.
  5. Token forgery — Attacker signs an arbitrary JWT payload with HS256 using the public key as the "secret," and the forged token passes verification as if it were legitimately issued.

Mitigation

Immediate Actions

  • Upgrade to PyJWT 2.14.0 or later, which hardens PEM/key-format detection so it matches every form the cryptography loader accepts.
  • Explicitly pin the algorithms parameter on every jwt.decode() call — pass algorithms=[...] with a single, specific algorithm family rather than a permissive list.
  • Never mix HMAC and asymmetric algorithms in the same verification allow-list. If different token issuers require different algorithms, verify them through separate, algorithm-specific code paths rather than a shared allow-list.

Detection Opportunities

  • Audit codebases for jwt.decode(...) calls that pass an algorithms list containing both an HS* entry and an RS*/ES*/PS* entry.
  • Review any configuration, secrets-management, or environment-variable pipeline that stores PEM keys, since CR/LF normalization or line-folding during storage/retrieval is exactly the kind of mutation that triggers this bug.
  • Monitor authentication logs for unexpected HS256-signed tokens where the service otherwise expects asymmetric-signed tokens.

Defence-in-Depth

  • Treat JWT verification configuration as security-critical code — require review for any change to algorithms=[...] lists.
  • Where possible, use distinct key material and distinct verification functions for HMAC-based and asymmetric-based token flows so a confusion bug in one library can't cross algorithm boundaries.
  • Keep dependency-scanning/SCA tooling current so PyJWT and other cryptographic libraries are flagged promptly when new advisories land.

Background

Algorithm-confusion attacks are a well-established vulnerability class in JWT implementations, not a novelty specific to PyJWT. The core pattern — a verifier configured to accept both symmetric and asymmetric algorithms, allowing an attacker who knows a public key to forge an HMAC-signed token — has surfaced repeatedly across the JWT ecosystem over the years, which is why PyJWT already carried a guard against it (originally added for CVE-2022-29217). CVE-2026-102268 shows how narrowly-scoped format-detection logic can reintroduce a previously-fixed class of bug: the guard itself was sound, but the PEM recognizer feeding it was stricter than the actual key parser, leaving a gap that mutated-but-valid PEM input could slip through. PyJWT 2.14.0 addressed this alongside a cluster of related advisories covering JWK, JWKS, array, encoded, BOM-prefixed, and DER-form key inputs to the same HMAC guard.


References