CVE-2026-102269: PyJWT Non-canonical Signature Segments Enable Raw-token Vulnerability

MODERATE
Advisory Identifier: GHSA-hxm8-2xgr-2p9m
CVSS 4.8
CWE-180
Published
Advisory Summary

CVE-2026-102269 is a MODERATE (CVSS 4.8) non-canonical signature segments enable raw-token vulnerability (CWE-180: Incorrect Behavior Order: Validate Before Canonicalize) affecting PyJWT libraries in the PyPI ecosystem. The issue involves non-canonical signature segments enable raw-token revocation bypass. Remediation is available by upgrading affected packages to version 2.14.0.

CVE-2026-102269 at a Glance

CVE Identifier CVE-2026-102269
GHSA Identifier GHSA-hxm8-2xgr-2p9m
Severity MODERATE
CVSS v3.1 Base Score 4.8
CWE Classification CWE-180: Incorrect Behavior Order: Validate Before Canonicalize
Affected Product PyJWT
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 4.8

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:L/I:L/A:N).

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 Low (C:L)
Integrity Low (I:L)
Availability None (A:N)

How to Fix CVE-2026-102269

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-102269, 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-180: Incorrect Behavior Order: Validate Before Canonicalize, where the product validates input before it is canonicalized, which prevents the product from detecting data that becomes invalid after the canonicalization step. 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 PyJWT 2.13.0 accepts compact JWS signature segments containing characters that are not in the Base64URL alphabet. Appending `!!!!` to a valid signature does not change the decoded signature bytes or authenticated claims, but it changes the serialized token and its SHA-256 hash. An application that indexes logout or revocation state by the raw token therefore rejects the logged-out token and accepts its no-key alternate serialization. This report separates two issues: permissive compact-JWS decoding is the library behavior, while using raw serialized JWT text as a security identity is a dangerous application composition. A `jti`-indexed control blocks both representations. ## Affected Version Confirmed with PyJWT 2.13.0 on CPython 3.13. The mutation changes only the signature segment and requires no signing key. ## End-to-End Reproduction ```bash .venv-research/bin/python 06-practical-testing/revocation-bypass-lab.py ``` The loopback-only lab performs: 1. Issue a valid HS256 admin token. 2. Access `/protected` successfully. 3. Log out the canonical token and store `SHA256(raw_token)`. 4. Confirm canonical replay returns HTTP 401. 5. Append `!!!!` to only the signature segment. 6. Confirm the mutated token has the same verified claims and signature bytes, a different raw hash, and receives HTTP 200. 7. Repeat with `jti`-indexed revocation and confirm both forms return HTTP 401. Observed statuses for raw-token revocation were `200 -> logout -> 401` for the canonical token and `200` for the alternate serialization. The `jti` control returned `401` for both replays after logout. ## Security Boundaries and Severity This is not signature forgery: the attacker starts with a valid token and cannot alter its claims. Impact requires a consumer to bind revocation, replay state, rate limits, or cache identity to the raw compact string. Under that composition, an authenticated user can bypass logout or token-specific revocation without the signing key. Medium is suggested for triage because the security impact is real but application-dependent. ## Recommended Fix - Reject signature segments containing characters outside `[A-Za-z0-9_-]`. - Reject padding and other non-canonical Base64URL encodings for compact JWS. - Add regression tests proving malformed signature encodings fail before or during verification. - Document that applications must use a semantic identifier such as `jti`, not the raw serialized token, for revocation and replay state. ## Maintainer update — 2026-09-09 We reproduced the reported behavior on the verified `master` commit `8915570`: appending `!!!!` to a valid compact-JWS signature segment is accepted, produces the same decoded signature bytes and claims, and changes the serialized token hash. Existing JWS verification controls pass. The behavior is present in the tested release range represented by tags `2.4.0`, `2.8.0`, `2.10.1`, `2.12.1`, and `2.13.0`. The security impact is conditional on an application using the raw serialized token as the identity for logout, revocation, replay, rate-limit, or cache state. Under that composition, an attacker who already holds a valid token can use an alternate serialization after the canonical string is revoked. This is not signature forgery or claim alteration; applications should use a semantic identifier such as `jti` for token identity. The report is ready to be accepted as a draft for continued remediation. A narrow fix assessment recommends rejecting non-canonical Base64URL characters and padding in compact-JWS segments. No fix commit or release is claimed yet. ### Maintainer update — 2026-09-09 The fix has been rebased onto the current `master` as `e6f48401001609a8f99e71fcaf355fb895d508a8` and is ready to be pushed. Compact-JWS header, payload, and signature segments are now required to use the unpadded Base64URL alphabet and canonical re-encoding; a signature segment containing non-canonical characters such as `!!!!` is rejected before verification. Valid detached `b64=false` handling remains supported. Verification on the rebased fix commit passed 372 tests with 4 intentional cryptography-environment skips; Ruff formatting/lint and `git diff --check` passed. The compressed-payload regression fixture was updated to valid canonical compact-JWS encoding. No release containing the fix has been published yet, so the patched version remains unset pending release planning. The advisory remains medium severity and unpublished. ## Classification update — 2026-09-10 We completed the advisory classification review. The proposed CVSS 3.1 vector is `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N` and the proposed CWE classification is CWE-180. The issue permits alternate non-canonical serializations of an already valid signed token to bypass applications that identify tokens by their raw serialization; it does not forge signatures or alter claims. CWE-180 captures validation/canonicalization ordering. These metadata changes do not alter the affected range, fix status, or lifecycle state. ## Maintainer update — 2026-09-11 The verified fix for this advisory is included in PyJWT 2.14.0, released on 2026-09-11 and available on PyPI. PyJWT 2.14.0 is the first release containing the fix. This advisory is now published with 2.14.0 recorded as the patched version.

CVE-2026-102269 Affected PyJWT Versions

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

References

Frequently Asked Questions

What is CVE-2026-102269?
CVE-2026-102269 is a security vulnerability in PyJWT involving Non-canonical signature segments enable raw-token revocation bypass under CWE-180: Incorrect Behavior Order: Validate Before Canonicalize.
Which PyJWT versions are affected?
Affected versions include releases prior to 2.14.0 across affected packages: PyJWT.
What version fixes CVE-2026-102269?
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-102269?
CVE-2026-102269 has a CVSS v3.1 base score of 4.8 (MODERATE) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N.
What CWE is associated with CVE-2026-102269?
CVE-2026-102269 is classified under CWE-180: Incorrect Behavior Order: Validate Before Canonicalize (The product validates input before it is canonicalized, which prevents the product from detecting data that becomes invalid after the canonicalization step.).
How can I check PyPI dependencies for CVE-2026-102269?
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-hxm8-2xgr-2p9m",
  "modified" : "2026-09-29T23:17:55Z",
  "published" : "2026-09-29T23:17:55Z",
  "aliases" : [ "CVE-2026-102269" ],
  "summary" : "PyJWT: Non-canonical signature segments enable raw-token revocation bypass",
  "details" : "## Summary\n\nPyJWT 2.13.0 accepts compact JWS signature segments containing characters that\nare not in the Base64URL alphabet. Appending `!!!!` to a valid signature does\nnot change the decoded signature bytes or authenticated claims, but it changes\nthe serialized token and its SHA-256 hash. An application that indexes logout\nor revocation state by the raw token therefore rejects the logged-out token and\naccepts its no-key alternate serialization.\n\nThis report separates two issues: permissive compact-JWS decoding is the\nlibrary behavior, while using raw serialized JWT text as a security identity is\na dangerous application composition. A `jti`-indexed control blocks both\nrepresentations.\n\n## Affected Version\n\nConfirmed with PyJWT 2.13.0 on CPython 3.13. The mutation changes only the\nsignature segment and requires no signing key.\n\n## End-to-End Reproduction\n\n```bash\n.venv-research/bin/python 06-practical-testing/revocation-bypass-lab.py\n```\n\nThe loopback-only lab performs:\n\n1. Issue a valid HS256 admin token.\n2. Access `/protected` successfully.\n3. Log out the canonical token and store `SHA256(raw_token)`.\n4. Confirm canonical replay returns HTTP 401.\n5. Append `!!!!` to only the signature segment.\n6. Confirm the mutated token has the same verified claims and signature bytes,\n   a different raw hash, and receives HTTP 200.\n7. Repeat with `jti`-indexed revocation and confirm both forms return HTTP 401.\n\nObserved statuses for raw-token revocation were `200 -> logout -> 401` for the\ncanonical token and `200` for the alternate serialization. The `jti` control\nreturned `401` for both replays after logout.\n\n## Security Boundaries and Severity\n\nThis is not signature forgery: the attacker starts with a valid token and\ncannot alter its claims. Impact requires a consumer to bind revocation, replay\nstate, rate limits, or cache identity to the raw compact string. Under that\ncomposition, an authenticated user can bypass logout or token-specific\nrevocation without the signing key. Medium is suggested for triage because the\nsecurity impact is real but application-dependent.\n\n## Recommended Fix\n\n- Reject signature segments containing characters outside `[A-Za-z0-9_-]`.\n- Reject padding and other non-canonical Base64URL encodings for compact JWS.\n- Add regression tests proving malformed signature encodings fail before or\n  during verification.\n- Document that applications must use a semantic identifier such as `jti`, not\n  the raw serialized token, for revocation and replay state.\n\n## Maintainer update — 2026-09-09\n\nWe reproduced the reported behavior on the verified `master` commit `8915570`: appending `!!!!` to a valid compact-JWS signature segment is accepted, produces the same decoded signature bytes and claims, and changes the serialized token hash. Existing JWS verification controls pass. The behavior is present in the tested release range represented by tags `2.4.0`, `2.8.0`, `2.10.1`, `2.12.1`, and `2.13.0`.\n\nThe security impact is conditional on an application using the raw serialized token as the identity for logout, revocation, replay, rate-limit, or cache state. Under that composition, an attacker who already holds a valid token can use an alternate serialization after the canonical string is revoked. This is not signature forgery or claim alteration; applications should use a semantic identifier such as `jti` for token identity.\n\nThe report is ready to be accepted as a draft for continued remediation. A narrow fix assessment recommends rejecting non-canonical Base64URL characters and padding in compact-JWS segments. No fix commit or release is claimed yet.\n\n### Maintainer update — 2026-09-09\n\nThe fix has been rebased onto the current `master` as `e6f48401001609a8f99e71fcaf355fb895d508a8` and is ready to be pushed. Compact-JWS header, payload, and signature segments are now required to use the unpadded Base64URL alphabet and canonical re-encoding; a signature segment containing non-canonical characters such as `!!!!` is rejected before verification. Valid detached `b64=false` handling remains supported.\n\nVerification on the rebased fix commit passed 372 tests with 4 intentional cryptography-environment skips; Ruff formatting/lint and `git diff --check` passed. The compressed-payload regression fixture was updated to valid canonical compact-JWS encoding. No release containing the fix has been published yet, so the patched version remains unset pending release planning.\n\nThe advisory remains medium severity and unpublished.\n\n## Classification update — 2026-09-10\n\nWe completed the advisory classification review. The proposed CVSS 3.1 vector is `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N` and the proposed CWE classification is CWE-180. The issue permits alternate non-canonical serializations of an already valid signed token to bypass applications that identify tokens by their raw serialization; it does not forge signatures or alter claims. CWE-180 captures validation/canonicalization ordering.\n\nThese metadata changes do not alter the affected range, fix status, or lifecycle state.\n\n## Maintainer update — 2026-09-11\n\nThe verified fix for this advisory is included in PyJWT 2.14.0, released on 2026-09-11 and available on PyPI. PyJWT 2.14.0 is the first release containing the fix. This advisory is now published with 2.14.0 recorded as the patched version.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "PyJWT"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "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-hxm8-2xgr-2p9m"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-102269"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/jpadilla/pyjwt/commit/e6f48401001609a8f99e71fcaf355fb895d508a8"
  }, {
    "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-180" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-09-29T23:17:55Z",
    "nvd_published_at" : "2026-09-28T21:17:14Z"
  }
}