CVE-2026-102274: PyJWT Malformed RSA JWK Aborts Parsing Vulnerability

MODERATE
Advisory Identifier: GHSA-w6j9-cwv2-h6wq
CVSS 5.9
CWE-755
Published
Advisory Summary

CVE-2026-102274 is a MODERATE (CVSS 5.9) malformed rsa jwk aborts parsing vulnerability (CWE-755: Improper Handling of Exceptional Conditions) affecting PyJWT libraries in the PyPI ecosystem. The issue involves malformed rsa jwk aborts parsing of an entire jwk set. Remediation is available by upgrading affected packages to version 2.14.0.

CVE-2026-102274 at a Glance

CVE Identifier CVE-2026-102274
GHSA Identifier GHSA-w6j9-cwv2-h6wq
Severity MODERATE
CVSS v3.1 Base Score 5.9
CWE Classification CWE-755: Improper Handling of Exceptional Conditions
Affected Product PyJWT
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 5.9

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-102274

To remediate this vulnerability, upgrade all affected PyJWT dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

PyJWT fixed: 2.14.0
pip install PyJWT>=2.14.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show PyJWT

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-102274, inspect your installed packages using pip:

pip show PyJWT

Analyze the terminal output to locate any version of PyJWT in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.14.0), your application is affected.

Note: The pip show PyJWT command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-755: Improper Handling of Exceptional Conditions, where the product does not handle or incorrectly handles an exceptional condition. In PyJWT, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary A malformed RSA JWK inside a JWK Set aborts parsing of the entire set instead of being skipped, because `RSAAlgorithm.from_jwk` can raise a plain `ValueError` that isn't caught by `PyJWKSet`'s per-key error-skipping logic. ## Affected component / version - Package: `PyJWT` (PyPI, ecosystem `pip`) - Files: `jwt/api_jwk.py` (`PyJWK.__init__`, `PyJWKSet.__init__`), `jwt/algorithms.py` (`RSAAlgorithm.from_jwk`) - Confirmed present in the `master` branch as of 2026-09-05 (commit `7144e4534c34810f4525dc4578a32addd8212cff`, tag `2.13.0`). Directly verified identical in tags `2.9.0`, `2.10.0`, `2.11.0`, `2.12.0`, `2.12.1`, `2.13.0` -- the vulnerable call and the `except PyJWTError` guard are unchanged across all six releases. Not verified against any release prior to `2.9.0`. ## Details `PyJWKSet.__init__` (`jwt/api_jwk.py:145-152`) iterates each key in a JWK Set: ```python for key in keys: try: self.keys.append(PyJWK(key)) except PyJWTError as error: if isinstance(error, MissingCryptographyError): raise error # skip unusable keys continue ``` `PyJWK.__init__` (`api_jwk.py:82`) calls `self.Algorithm.from_jwk(self._jwk_data)` with no try/except of its own. For an RSA JWK, this dispatches to `RSAAlgorithm.from_jwk` (`jwt/algorithms.py:539-586`). When the JWK supplies `d`, `e`, `n` without the CRT parameters (`p`, `q`, `dp`, `dq`, `qi`), `from_jwk` calls `cryptography`'s `rsa_recover_prime_factors(public_numbers.n, d, public_numbers.e)` (`algorithms.py:572-574`) to derive the key. If `d` is not the correct private exponent for that `n`/`e` pair, `rsa_recover_prime_factors` raises a plain `ValueError`. `ValueError` is a built-in Python exception and is not a subclass of `jwt.exceptions.PyJWTError` (`PyJWTError(Exception)` is the root of PyJWT's own exception hierarchy). It is therefore not caught by `PyJWKSet.__init__`'s `except PyJWTError`, and propagates out of the constructor, aborting the `for key in keys:` loop before any subsequent key in the list is processed. `PyJWKSet.from_dict`/`from_json` and `PyJWK.from_dict`/`from_json` are exported public API (`jwt/__init__.py`). `PyJWKClient.get_jwk_set` (`jwt/jwks_client.py:158`) feeds a fetched JWKS HTTP response directly into `PyJWKSet.from_dict` with no per-key pre-validation, so this is reachable through the documented `PyJWKClient` flow whenever the fetched JWKS contains a malformed key alongside valid ones. ## Proof of concept ```python import jwt good_jwk = { "kty": "RSA", "n": "<a valid base64url-encoded RSA modulus, e.g. from a real 2048-bit public key>", "e": "AQAB", } bad_jwk = { "kty": "RSA", "n": good_jwk["n"], "e": "AQAB", "d": "AAAAAA", # not the true private exponent for n/e, no CRT params present } jwks_doc = {"keys": [bad_jwk, good_jwk]} jwt.PyJWKSet.from_dict(jwks_doc) # raises: ValueError: Unable to compute factors p and q from exponent d. # (uncaught -- PyJWKSet.__init__ never returns, `good_jwk` is never added) ``` ## Impact `PyJWKSet.__init__` raises before completing, so no key in the JWK Set is added to the resulting set, including keys unrelated to the malformed entry. This requires the malformed key to already be present in a JWK Set the application parses (e.g. one entry in an aggregated/federated key set, or a key affected by transit corruption before signature verification of the JWKS transport itself). Applications that vet each key individually before adding it to a trusted set are not affected. The failure is an uncaught `ValueError`, not one of PyJWT's documented `jwt.exceptions.*` types, so exception handling written against PyJWT's documented contract (`except jwt.exceptions.PyJWTError`) will not catch it either. ## Suggested remediation Wrap the key-construction call in `PyJWK.__init__` (`api_jwk.py:82`) so a `ValueError` is converted into `InvalidKeyError` (a `PyJWTError` subclass): ```python try: self.key = self.Algorithm.from_jwk(self._jwk_data) except ValueError as e: raise InvalidKeyError(f"Unable to construct key from JWK: {e}") from e ``` This lets `PyJWKSet.__init__`'s existing `except PyJWTError: continue` skip the one malformed key as its own comment already states is intended. ## Responsible Disclosure Timeline Per the [OWASP Vulnerability Disclosure Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html) and [Google Project Zero's 2020 disclosure policy](https://projectzero.google/2020/01/policy-and-disclosure-2020-edition.html): - **Day 0** (date of submission): to be set to the actual API response's `created_at` timestamp when this report is submitted. If submitted on the date of this draft (2026-09-05), Day 0 = 2026-09-05. - **Day 90** (full-public-disclosure deadline, regardless of fix status): Day 0 + 90 days -- 2026-12-04 if Day 0 is 2026-09-05. - A brief mutually-agreed extension (standard 14-day grace period) is available if a fix is scheduled but not yet shipped by Day 90 -- extending to 2026-12-18 in that case. - This window may shorten instead of extend if the issue is confirmed under active exploitation. ## Try It Yourself (Sandbox) Reproduce the PoC above, and attempt your own fix, in an isolated sandbox with no access to production systems, secrets, or real data. - Sandbox: [jwt.py](https://play.secdim.com/game/python/challenge/jwtpy) - Course: [Tenant Zero: AI & SaaS Authz Failures](https://learn.secdim.com/course/tenant-zero) ## Credit Discovered and reported by SecDim Security Research: secdim.com, @secdim, security@secdim.com. ## Maintainer update — 2026-09-08 We reproduced the reported behavior on PyJWT 2.13.0 with `cryptography` installed: a malformed RSA private JWK containing an invalid `d` value and no CRT parameters raised a plain `ValueError` while parsing a JWK set. Because `PyJWKSet` skips `PyJWTError` instances only, that exception aborted parsing before subsequent valid keys were loaded. The impact is a conditional availability failure for applications that parse a key set containing a malformed entry; it is not a signature-forgery or claims-verification bypass. The independently verified affected range is `>= 2.9.0, <= 2.13.0`; earlier releases were not checked. A narrow fix has been prepared in commit `8915570` based on master commit `5fa7594`: `PyJWK` converts key-construction `ValueError` exceptions to `InvalidKeyError`, allowing the existing `PyJWKSet` skip path to continue. Regression coverage verifies that a malformed RSA key is skipped while a valid key in the same set remains usable. The full suite passes with 370 tests and 4 intentional cryptography-environment skips; formatting, lint, and targeted type checks pass. The advisory remains in triage while the fix goes through release planning.

CVE-2026-102274 Affected PyJWT Versions

1 component(s)
Fixed Versions:
2.14.0
Product / Package Ecosystem Affected Versions Fixed Version
PyJWT
PyPI
≥ 2.9.0
< 2.14.0
2.14.0

References

Frequently Asked Questions

What is CVE-2026-102274?
CVE-2026-102274 is a security vulnerability in PyJWT involving Malformed RSA JWK aborts parsing of an entire JWK Set under CWE-755: Improper Handling of Exceptional Conditions.
Which PyJWT versions are affected?
Affected versions include releases prior to 2.14.0 across affected packages: PyJWT.
What version fixes CVE-2026-102274?
Upgrading to 2.14.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is PyJWT affected?
Yes, PyJWT is affected in releases prior to 2.14.0. Upgrade your dependency to 2.14.0 or later.
What is the CVSS score of CVE-2026-102274?
CVE-2026-102274 has a CVSS v3.1 base score of 5.9 (MODERATE) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-102274?
CVE-2026-102274 is classified under CWE-755: Improper Handling of Exceptional Conditions (The product does not handle or incorrectly handles an exceptional condition.).
How can I check PyPI dependencies for CVE-2026-102274?
Run the dependency tree command: 'pip show PyJWT'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-w6j9-cwv2-h6wq",
  "modified" : "2026-09-29T18:23:01Z",
  "published" : "2026-09-29T18:23:01Z",
  "aliases" : [ "CVE-2026-102274" ],
  "summary" : "PyJWT: Malformed RSA JWK aborts parsing of an entire JWK Set",
  "details" : "## Summary\n\nA malformed RSA JWK inside a JWK Set aborts parsing of the entire set instead of being skipped, because `RSAAlgorithm.from_jwk` can raise a plain `ValueError` that isn't caught by `PyJWKSet`'s per-key error-skipping logic.\n\n## Affected component / version\n\n- Package: `PyJWT` (PyPI, ecosystem `pip`)\n- Files: `jwt/api_jwk.py` (`PyJWK.__init__`, `PyJWKSet.__init__`), `jwt/algorithms.py` (`RSAAlgorithm.from_jwk`)\n- Confirmed present in the `master` branch as of 2026-09-05 (commit `7144e4534c34810f4525dc4578a32addd8212cff`, tag `2.13.0`). Directly verified identical in tags `2.9.0`, `2.10.0`, `2.11.0`, `2.12.0`, `2.12.1`, `2.13.0` -- the vulnerable call and the `except PyJWTError` guard are unchanged across all six releases. Not verified against any release prior to `2.9.0`.\n\n## Details\n\n`PyJWKSet.__init__` (`jwt/api_jwk.py:145-152`) iterates each key in a JWK Set:\n\n```python\nfor key in keys:\n    try:\n        self.keys.append(PyJWK(key))\n    except PyJWTError as error:\n        if isinstance(error, MissingCryptographyError):\n            raise error\n        # skip unusable keys\n        continue\n```\n\n`PyJWK.__init__` (`api_jwk.py:82`) calls `self.Algorithm.from_jwk(self._jwk_data)` with no try/except of its own. For an RSA JWK, this dispatches to `RSAAlgorithm.from_jwk` (`jwt/algorithms.py:539-586`). When the JWK supplies `d`, `e`, `n` without the CRT parameters (`p`, `q`, `dp`, `dq`, `qi`), `from_jwk` calls `cryptography`'s `rsa_recover_prime_factors(public_numbers.n, d, public_numbers.e)` (`algorithms.py:572-574`) to derive the key. If `d` is not the correct private exponent for that `n`/`e` pair, `rsa_recover_prime_factors` raises a plain `ValueError`.\n\n`ValueError` is a built-in Python exception and is not a subclass of `jwt.exceptions.PyJWTError` (`PyJWTError(Exception)` is the root of PyJWT's own exception hierarchy). It is therefore not caught by `PyJWKSet.__init__`'s `except PyJWTError`, and propagates out of the constructor, aborting the `for key in keys:` loop before any subsequent key in the list is processed.\n\n`PyJWKSet.from_dict`/`from_json` and `PyJWK.from_dict`/`from_json` are exported public API (`jwt/__init__.py`). `PyJWKClient.get_jwk_set` (`jwt/jwks_client.py:158`) feeds a fetched JWKS HTTP response directly into `PyJWKSet.from_dict` with no per-key pre-validation, so this is reachable through the documented `PyJWKClient` flow whenever the fetched JWKS contains a malformed key alongside valid ones.\n\n## Proof of concept\n\n```python\nimport jwt\n\ngood_jwk = {\n    \"kty\": \"RSA\",\n    \"n\": \"<a valid base64url-encoded RSA modulus, e.g. from a real 2048-bit public key>\",\n    \"e\": \"AQAB\",\n}\n\nbad_jwk = {\n    \"kty\": \"RSA\",\n    \"n\": good_jwk[\"n\"],\n    \"e\": \"AQAB\",\n    \"d\": \"AAAAAA\",  # not the true private exponent for n/e, no CRT params present\n}\n\njwks_doc = {\"keys\": [bad_jwk, good_jwk]}\n\njwt.PyJWKSet.from_dict(jwks_doc)\n# raises: ValueError: Unable to compute factors p and q from exponent d.\n# (uncaught -- PyJWKSet.__init__ never returns, `good_jwk` is never added)\n```\n\n## Impact\n\n`PyJWKSet.__init__` raises before completing, so no key in the JWK Set is added to the resulting set, including keys unrelated to the malformed entry. This requires the malformed key to already be present in a JWK Set the application parses (e.g. one entry in an aggregated/federated key set, or a key affected by transit corruption before signature verification of the JWKS transport itself). Applications that vet each key individually before adding it to a trusted set are not affected. The failure is an uncaught `ValueError`, not one of PyJWT's documented `jwt.exceptions.*` types, so exception handling written against PyJWT's documented contract (`except jwt.exceptions.PyJWTError`) will not catch it either.\n\n## Suggested remediation\n\nWrap the key-construction call in `PyJWK.__init__` (`api_jwk.py:82`) so a `ValueError` is converted into `InvalidKeyError` (a `PyJWTError` subclass):\n\n```python\ntry:\n    self.key = self.Algorithm.from_jwk(self._jwk_data)\nexcept ValueError as e:\n    raise InvalidKeyError(f\"Unable to construct key from JWK: {e}\") from e\n```\n\nThis lets `PyJWKSet.__init__`'s existing `except PyJWTError: continue` skip the one malformed key as its own comment already states is intended.\n\n## Responsible Disclosure Timeline\n\nPer the [OWASP Vulnerability Disclosure Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html) and [Google Project Zero's 2020 disclosure policy](https://projectzero.google/2020/01/policy-and-disclosure-2020-edition.html):\n\n- **Day 0** (date of submission): to be set to the actual API response's `created_at` timestamp when this report is submitted. If submitted on the date of this draft (2026-09-05), Day 0 = 2026-09-05.\n- **Day 90** (full-public-disclosure deadline, regardless of fix status): Day 0 + 90 days -- 2026-12-04 if Day 0 is 2026-09-05.\n- A brief mutually-agreed extension (standard 14-day grace period) is available if a fix is scheduled but not yet shipped by Day 90 -- extending to 2026-12-18 in that case.\n- This window may shorten instead of extend if the issue is confirmed under active exploitation.\n\n## Try It Yourself (Sandbox)\n\nReproduce the PoC above, and attempt your own fix, in an isolated sandbox with no access to production systems, secrets, or real data.\n\n- Sandbox: [jwt.py](https://play.secdim.com/game/python/challenge/jwtpy)\n- Course: [Tenant Zero: AI & SaaS Authz Failures](https://learn.secdim.com/course/tenant-zero)\n\n## Credit\n\nDiscovered and reported by SecDim Security Research: secdim.com, @secdim, security@secdim.com.\n\n## Maintainer update — 2026-09-08\n\nWe reproduced the reported behavior on PyJWT 2.13.0 with `cryptography` installed: a malformed RSA private JWK containing an invalid `d` value and no CRT parameters raised a plain `ValueError` while parsing a JWK set. Because `PyJWKSet` skips `PyJWTError` instances only, that exception aborted parsing before subsequent valid keys were loaded. The impact is a conditional availability failure for applications that parse a key set containing a malformed entry; it is not a signature-forgery or claims-verification bypass.\n\nThe independently verified affected range is `>= 2.9.0, <= 2.13.0`; earlier releases were not checked. A narrow fix has been prepared in commit `8915570` based on master commit `5fa7594`: `PyJWK` converts key-construction `ValueError` exceptions to `InvalidKeyError`, allowing the existing `PyJWKSet` skip path to continue. Regression coverage verifies that a malformed RSA key is skipped while a valid key in the same set remains usable. The full suite passes with 370 tests and 4 intentional cryptography-environment skips; formatting, lint, and targeted type checks pass. The advisory remains in triage while the fix goes through release planning.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "PyJWT"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.9.0"
      }, {
        "fixed" : "2.14.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 2.13.0"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-w6j9-cwv2-h6wq"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-102274"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/jpadilla/pyjwt/commit/8915570a0bfda9f0ff0e34e7fb09bdb9d71580cf"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/jpadilla/pyjwt"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/jpadilla/pyjwt/releases/tag/2.14.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-755" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-09-29T18:23:01Z",
    "nvd_published_at" : "2026-09-28T21:17:15Z"
  }
}