{"id":"CVE-2026-102269","aliases":["AZL-104985","BELL-CVE-2026-102269","CLSA-2026-1790839846","CLSA-2026-1790843991","CLSA-2026-1791271338","CLSA-2026-1791272933","DEBIAN-CVE-2026-102269","EUVD-2026-88536","GHSA-hxm8-2xgr-2p9m","OESA-2026-4453","OESA-2026-4454","OESA-2026-4455","OESA-2026-4456","PYSEC-2026-4147","SUSE-SU-2026:24126-1","SUSE-SU-2026:24134-1","SUSE-SU-2026:24219-1","SUSE-SU-2026:24377-1","SUSE-SU-2026:5064-1","UBUNTU-CVE-2026-102269","openSUSE-SU-2026:11994-1","openSUSE-SU-2026:22074-1"],"status":"published","title":"PyJWT: Non-canonical signature segments enable raw-token revocation bypass","description":"PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.","severity":{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00224,"percentile":0.13115,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-180"],"published":"2026-09-28T20:27:54Z","modified":"2026-11-07T07:15:02Z","fixes":[{"ecosystem":"PyPI","package":"pyjwt","fixed_in":["2.14.0"],"sources":["ghsa","osv"]},{"ecosystem":"Azure Linux:3","package":"python-jwt","fixed_in":["2.15.1-1"],"sources":["osv"]},{"ecosystem":"Alpaquita:25","package":"py3-jwt","fixed_in":["2.15.1-r0"],"sources":["osv"]},{"ecosystem":"Alpaquita:stream","package":"py3-jwt","fixed_in":["2.15.1-r0"],"sources":["osv"]},{"ecosystem":"Debian:14","package":"pyjwt","fixed_in":["2.14.0-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"2.13.0"},{"fixed":"2.14.0"}]}]},{"source":"osv","ecosystem":"Azure Linux:3","package":"python-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.15.1-1"}]}]},{"source":"osv","ecosystem":"Alpaquita:25","package":"py3-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.10.1-r0"},{"fixed":"2.15.1-r0"}]}]},{"source":"osv","ecosystem":"Alpaquita:stream","package":"py3-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.8.0-r1"},{"fixed":"2.15.1-r0"}]}]},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.10.1"},{"fixed":"2.10.1.post5+tuxcare"}]}],"versions":["2.10.1","2.10.1.post1+tuxcare","2.10.1.post2+tuxcare","2.10.1.post3+tuxcare","2.10.1.post4+tuxcare"],"bundled":6},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.8.0"},{"fixed":"2.8.0.post4+tuxcare"}]}],"versions":["2.8.0","2.8.0.post1+tuxcare","2.8.0.post2+tuxcare","2.8.0.post3+tuxcare"],"bundled":6},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.3.0"},{"fixed":"2.3.0.post4+tuxcare"}]}],"versions":["2.3.0","2.3.0.post1+tuxcare","2.3.0.post2+tuxcare","2.3.0.post3+tuxcare"],"bundled":7},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.7.1"},{"fixed":"1.7.1.post4+tuxcare"}]}],"versions":["1.7.1","1.7.1.post1+tuxcare","1.7.1.post2+tuxcare","1.7.1.post3+tuxcare"],"bundled":3},{"source":"osv","ecosystem":"Debian:12","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.10.1-1","2.10.1-2","2.10.1-3","2.10.1-4","2.11.0-2","2.12.1-1","2.13.0-1","2.14.0-1","2.15.0-1","2.15.1-1","2.6.0-1","2.6.0-1+deb12u1","2.7.0-1"]},{"source":"osv","ecosystem":"Debian:13","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.10.1-2","2.10.1-2+deb13u1","2.10.1-3","2.10.1-4","2.11.0-2","2.12.1-1","2.13.0-1","2.14.0-1","2.15.0-1","2.15.1-1"]},{"source":"osv","ecosystem":"Debian:14","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.14.0-1"}]}],"versions":["2.10.1-2","2.10.1-3","2.10.1-4","2.11.0-2","2.12.1-1","2.13.0-1"]},{"source":"osv","ecosystem":"PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.14.0"}]}],"versions":["0.1.1","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","0.1.9","0.2.0","0.2.1","0.2.3","0.3.0","0.3.1","0.3.2","0.4.0","0.4.1","0.4.2","0.4.3","1.0.0","1.0.1","1.1.0","1.3.0","1.4.0","1.4.1","1.4.2","1.5.0","1.5.1","1.5.2","1.5.3","1.6.0","1.6.1","1.6.3","1.6.4","1.7.0","1.7.1","2.0.0","2.0.0a1","2.0.0a2","2.0.1","2.1.0","2.10.0","2.10.1","2.11.0","2.12.0","2.12.1","2.13.0","2.2.0","2.3.0","2.4.0","2.5.0","2.6.0","2.7.0","2.8.0","2.9.0"]},{"source":"osv","ecosystem":"openEuler:24.03-LTS-SP4","package":"python-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-11.oe2403sp4"}]}],"bundled":5},{"source":"osv","ecosystem":"openEuler:22.03-LTS-SP4","package":"python-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.3.0-12.oe2203sp4"}]}],"bundled":4},{"source":"osv","ecosystem":"openEuler:24.03-LTS-SP1","package":"python-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-11.oe2403sp1"}]}],"bundled":5},{"source":"osv","ecosystem":"openEuler:24.03-LTS-SP3","package":"python-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-11.oe2403sp3"}]}],"bundled":5},{"source":"osv","ecosystem":"PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.14.0"}]}],"versions":["0.1.1","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","0.1.9","0.2.0","0.2.1","0.2.3","0.3.0","0.3.1","0.3.2","0.4.0","0.4.1","0.4.2","0.4.3","1.0.0","1.0.1","1.1.0","1.3.0","1.4.0","1.4.1","1.4.2","1.5.0","1.5.1","1.5.2","1.5.3","1.6.0","1.6.1","1.6.3","1.6.4","1.7.0","1.7.1","2.0.0","2.0.0a1","2.0.0a2","2.0.1","2.1.0","2.10.0","2.10.1","2.11.0","2.12.0","2.12.1","2.13.0","2.2.0","2.3.0","2.4.0","2.5.0","2.6.0","2.7.0","2.8.0","2.9.0"]},{"source":"osv","ecosystem":"SUSE:Linux Micro 6.2","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.12.1-160000.3.1"}]}],"bundled":10},{"source":"osv","ecosystem":"SUSE:Linux Micro 6.0","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.9.0-159998.1.1"}]}],"bundled":9},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 16.0","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.12.1-160000.3.1"}]}],"bundled":10},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP applications 16.0","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.12.1-160000.3.1"}]}],"bundled":10},{"source":"osv","ecosystem":"SUSE:Linux Micro 6.1","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.12.1-slfo.1.1_3.1"}]}],"bundled":10},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Module for Public Cloud 15 SP4","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Module for Python 3 15 SP7","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise High Performance Computing 15 SP4-ESPOS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise High Performance Computing 15 SP4-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise High Performance Computing 15 SP5-ESPOS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise High Performance Computing 15 SP5-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 15 SP4-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 15 SP5-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 15 SP6-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP Applications 15 SP4","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP Applications 15 SP5","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP Applications 15 SP6","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.8.0-150400.8.20.1"}]}],"bundled":8},{"source":"osv","ecosystem":"Ubuntu:Pro:16.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.0-0ubuntu1","1.3.0-1","1.3.0-1ubuntu0.1","1.3.0-1ubuntu0.1+esm1","1.3.0-1ubuntu0.1+esm2","1.3.0-1ubuntu0.1+esm3","1.3.0-1ubuntu0.1+esm4"]},{"source":"osv","ecosystem":"Ubuntu:Pro:18.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.4.2-1.1","1.5.3+ds1-1","1.5.3+ds1-1ubuntu0.1","1.5.3+ds1-1ubuntu0.1+esm1","1.5.3+ds1-1ubuntu0.1+esm2","1.5.3+ds1-1ubuntu0.1+esm3","1.5.3+ds1-1ubuntu0.1+esm4"]},{"source":"osv","ecosystem":"Ubuntu:Pro:20.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.7.0-2","1.7.1-1","1.7.1-2ubuntu1","1.7.1-2ubuntu2","1.7.1-2ubuntu2.1","1.7.1-2ubuntu2.1+esm1","1.7.1-2ubuntu2.1+esm2","1.7.1-2ubuntu2.1+esm3","1.7.1-2ubuntu2.1+esm4"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.7.1-2ubuntu2","2.1.0-1","2.3.0-1","2.3.0-1ubuntu0.1","2.3.0-1ubuntu0.2","2.3.0-1ubuntu0.3","2.3.0-1ubuntu0.4","2.3.0-1ubuntu0.5","2.3.0-1ubuntu0.6"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.7.0-1","2.7.0-1ubuntu0.1","2.7.0-1ubuntu0.2","2.7.0-1ubuntu0.3","2.7.0-1ubuntu0.4"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.10.1-4ubuntu1","2.12.1-1","2.13.0-1","2.13.0-1ubuntu1","2.13.0-1ubuntu1.1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.10.1-2","2.10.1-3","2.10.1-3build1","2.10.1-4","2.10.1-4ubuntu1","2.10.1-4ubuntu1.1","2.10.1-4ubuntu1.2","2.10.1-4ubuntu1.3"]},{"source":"osv","ecosystem":"openSUSE:Tumbleweed","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.15.1-1.1"}]}],"bundled":9},{"source":"osv","ecosystem":"openSUSE:Leap 16.0","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.12.1-160000.3.1"}]}],"bundled":10}],"products":[{"vendor":"jpadilla","product":"pyjwt"},{"vendor":"pyjwt_project","product":"pyjwt"}],"references":[{"url":"https://github.com/jpadilla/pyjwt/security/advisories/GHSA-hxm8-2xgr-2p9m","tags":["x_refsource_CONFIRM","Exploit","Mitigation","Vendor Advisory","advisory","web","report"]},{"url":"https://github.com/jpadilla/pyjwt/commit/e6f48401001609a8f99e71fcaf355fb895d508a8","tags":["x_refsource_MISC","Patch","fix","web","report"]},{"url":"https://github.com/jpadilla/pyjwt/releases/tag/2.14.0","tags":["x_refsource_MISC","Product","Release Notes","web","report"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-102269","tags":["web","advisory"]},{"url":"https://github.com/advisories/GHSA-hxm8-2xgr-2p9m","tags":["advisory"]},{"url":"https://docs.bell-sw.com/security/cves/CVE-2026-102269","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/102xxx/CVE-2026-102269.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-102269","tags":["advisory"]},{"url":"https://github.com/jpadilla/pyjwt","tags":["package"]},{"url":"https://www.openeuler.org/zh/security/security-bulletins/detail/?id=openEuler-SA-2026-4453","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-101917","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-101918","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-102265","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-102275","tags":["advisory"]},{"url":"https://www.openeuler.org/zh/security/security-bulletins/detail/?id=openEuler-SA-2026-4454","tags":["advisory"]},{"url":"https://www.openeuler.org/zh/security/security-bulletins/detail/?id=openEuler-SA-2026-4455","tags":["advisory"]},{"url":"https://www.openeuler.org/zh/security/security-bulletins/detail/?id=openEuler-SA-2026-4456","tags":["advisory"]},{"url":"https://pypi.org/project/pyjwt","tags":["package"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202624126-1/","tags":["advisory"]},{"url":"https://bugzilla.suse.com/1283060","tags":["report"]},{"url":"https://bugzilla.suse.com/1283061","tags":["report"]},{"url":"https://bugzilla.suse.com/1283064","tags":["report"]},{"url":"https://bugzilla.suse.com/1283065","tags":["report"]},{"url":"https://bugzilla.suse.com/1283066","tags":["report"]},{"url":"https://bugzilla.suse.com/1283067","tags":["report"]},{"url":"https://bugzilla.suse.com/1283068","tags":["report"]},{"url":"https://bugzilla.suse.com/1283071","tags":["report"]},{"url":"https://bugzilla.suse.com/1283072","tags":["report"]},{"url":"https://bugzilla.suse.com/1283829","tags":["report"]},{"url":"https://www.suse.com/security/cve/CVE-2026-101917","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-101918","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-102267","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-102268","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-102269","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-102270","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-102271","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-102274","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-102275","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-103001","tags":["web"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202624134-1/","tags":["advisory"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202624219-1/","tags":["advisory"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202624377-1/","tags":["advisory"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-20265064-1/","tags":["advisory"]},{"url":"https://ubuntu.com/security/CVE-2026-102269","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-102269","tags":["report"]},{"url":"","tags":["advisory"]},{"url":"https://www.suse.com/security/cve/CVE-2026-102266","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-102272","tags":["web"]}],"sources":{"cve":[{"id":"CVE-2026-102269","title":"PyJWT: Non-canonical signature segments enable raw-token revocation bypass","description":"PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.","status":"published","published":"2026-09-28T20:27:54.521Z","modified":"2026-09-29T13:49:47.562Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-180"],"updated_at":"2026-11-02T13:38:54.394896Z"}],"euvd":[{"id":"EUVD-2026-88536","description":"PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.","status":"published","published":"2026-09-28T20:27:54Z","modified":"2026-09-29T13:49:47Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T17:27:52.331616Z"}],"ghsa":[{"id":"GHSA-hxm8-2xgr-2p9m","title":"PyJWT: Non-canonical signature segments enable raw-token revocation bypass","description":"## 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 -\u003e logout -\u003e 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.","status":"published","published":"2026-09-29T23:17:55Z","modified":"2026-09-29T23:17:56Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-180"],"updated_at":"2026-11-02T14:41:22.654187Z"}],"nvd":[{"id":"CVE-2026-102269","description":"PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.","status":"published","published":"2026-09-28T21:17:14.773Z","modified":"2026-10-06T18:54:08.46Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium","assessor":"secondary"},{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N","level":"medium","assessor":"primary"}],"cwes":["CWE-180"],"updated_at":"2026-11-02T14:12:52.413234Z"}],"osv":[{"id":"BELL-CVE-2026-102269","status":"published","published":"2026-10-01T06:08:32.909665Z","modified":"2026-11-07T07:15:02.311037557Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-07T07:40:28.517244Z"},{"id":"CVE-2026-102269","title":"PyJWT: Non-canonical signature segments enable raw-token revocation bypass","description":"PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.","status":"published","published":"2026-09-28T20:27:54.521Z","modified":"2026-11-03T22:56:56.749675778Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-180"],"updated_at":"2026-11-03T23:33:40.031248Z"},{"id":"DEBIAN-CVE-2026-102269","description":"PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.","status":"published","published":"2026-09-28T21:17:14.773Z","modified":"2026-10-07T10:00:08.377764036Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T15:45:48.440897Z"},{"id":"GHSA-hxm8-2xgr-2p9m","title":"PyJWT: Non-canonical signature segments enable raw-token revocation bypass","description":"## 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 -\u003e logout -\u003e 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.","status":"published","published":"2026-09-29T23:17:55Z","modified":"2026-10-01T17:55:59.204638768Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-180"],"updated_at":"2026-11-02T15:46:49.621585Z"},{"id":"OESA-2026-4453","title":"python-jwt security update","description":"setup.py test %endif\r\n\r\nSecurity Fix(es):\n\nPyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT get_signing_key_from_jwt is affected because unknown kid misses force refreshes without a negative cache or minimum refresh interval. This occurs when unauthenticated tokens repeatedly use the same unknown kid or varying kid values absent from the cached JWKS. As a result, each cache miss causes PyJWKClient to refresh the JWKS. Consequently, attacker traffic can amplify outbound requests to the configured JWKS endpoint. This issue is fixed in version 2.14.0.(CVE-2026-101917)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.0.0a1 until 2.15.0, PyJWT PyJWKClient.get_signing_key_from_jwt is affected because payload parser catches ValueError but not RecursionError. This occurs when an attacker-controlled recursively nested payload reaches json.loads. As a result, documented PyJWT exception handling does not contain the failure. Consequently, an unauthenticated request can raise an exception that may produce an HTTP 500 response. The advisory-defined affected implementation also includes jwt/api_jwt.py, verify_signature=False. This issue is fixed in version 2.15.0.(CVE-2026-101918)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.13.0 until 2.14.0, PyJWS._load in jwt/api_jws.py is affected because parser catches ValueError but not RecursionError. This occurs when a deeply nested token header reaches json.loads. As a result, RecursionError escapes the documented PyJWT error hierarchy. Consequently, an unauthenticated malformed token can cause a request-level failure and HTTP 500. This issue is fixed in version 2.14.0.(CVE-2026-102265)\n\nPyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.(CVE-2026-102269)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.1.0 until 2.15.0, PyJWT OKPAlgorithm.from_jwk in  jwt/algorithms.py is affected because private-JWK import path does not compare the public key derived from d with x. This occurs when an OKP private JWK supplies non-corresponding x and d components. As a result, identity derived from x can differ from operations performed with d. Consequently, if an integration also accepts private key parameters from a proof header without rejecting them, an attacker may use a stolen sender-constrained token without the legitimate private key. This issue is fixed in version 2.15.0.(CVE-2026-102275)","status":"published","published":"2026-10-22T14:35:19Z","modified":"2026-10-22T14:45:05.435029174Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"medium"}],"updated_at":"2026-11-02T15:52:47.981622Z"},{"id":"OESA-2026-4454","title":"python-jwt security update","description":"setup.py test %endif\r\n\r\nSecurity Fix(es):\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.0.0a1 until 2.15.0, PyJWT PyJWKClient.get_signing_key_from_jwt is affected because payload parser catches ValueError but not RecursionError. This occurs when an attacker-controlled recursively nested payload reaches json.loads. As a result, documented PyJWT exception handling does not contain the failure. Consequently, an unauthenticated request can raise an exception that may produce an HTTP 500 response. The advisory-defined affected implementation also includes jwt/api_jwt.py, verify_signature=False. This issue is fixed in version 2.15.0.(CVE-2026-101918)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.13.0 until 2.14.0, PyJWS._load in jwt/api_jws.py is affected because parser catches ValueError but not RecursionError. This occurs when a deeply nested token header reaches json.loads. As a result, RecursionError escapes the documented PyJWT error hierarchy. Consequently, an unauthenticated malformed token can cause a request-level failure and HTTP 500. This issue is fixed in version 2.14.0.(CVE-2026-102265)\n\nPyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.(CVE-2026-102269)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.1.0 until 2.15.0, PyJWT OKPAlgorithm.from_jwk in  jwt/algorithms.py is affected because private-JWK import path does not compare the public key derived from d with x. This occurs when an OKP private JWK supplies non-corresponding x and d components. As a result, identity derived from x can differ from operations performed with d. Consequently, if an integration also accepts private key parameters from a proof header without rejecting them, an attacker may use a stolen sender-constrained token without the legitimate private key. This issue is fixed in version 2.15.0.(CVE-2026-102275)","status":"published","published":"2026-10-22T14:35:19Z","modified":"2026-10-22T14:45:06.183628906Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"medium"}],"updated_at":"2026-11-02T15:52:47.981622Z"},{"id":"OESA-2026-4455","title":"python-jwt security update","description":"setup.py test %endif\r\n\r\nSecurity Fix(es):\n\nPyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT get_signing_key_from_jwt is affected because unknown kid misses force refreshes without a negative cache or minimum refresh interval. This occurs when unauthenticated tokens repeatedly use the same unknown kid or varying kid values absent from the cached JWKS. As a result, each cache miss causes PyJWKClient to refresh the JWKS. Consequently, attacker traffic can amplify outbound requests to the configured JWKS endpoint. This issue is fixed in version 2.14.0.(CVE-2026-101917)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.0.0a1 until 2.15.0, PyJWT PyJWKClient.get_signing_key_from_jwt is affected because payload parser catches ValueError but not RecursionError. This occurs when an attacker-controlled recursively nested payload reaches json.loads. As a result, documented PyJWT exception handling does not contain the failure. Consequently, an unauthenticated request can raise an exception that may produce an HTTP 500 response. The advisory-defined affected implementation also includes jwt/api_jwt.py, verify_signature=False. This issue is fixed in version 2.15.0.(CVE-2026-101918)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.13.0 until 2.14.0, PyJWS._load in jwt/api_jws.py is affected because parser catches ValueError but not RecursionError. This occurs when a deeply nested token header reaches json.loads. As a result, RecursionError escapes the documented PyJWT error hierarchy. Consequently, an unauthenticated malformed token can cause a request-level failure and HTTP 500. This issue is fixed in version 2.14.0.(CVE-2026-102265)\n\nPyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.(CVE-2026-102269)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.1.0 until 2.15.0, PyJWT OKPAlgorithm.from_jwk in  jwt/algorithms.py is affected because private-JWK import path does not compare the public key derived from d with x. This occurs when an OKP private JWK supplies non-corresponding x and d components. As a result, identity derived from x can differ from operations performed with d. Consequently, if an integration also accepts private key parameters from a proof header without rejecting them, an attacker may use a stolen sender-constrained token without the legitimate private key. This issue is fixed in version 2.15.0.(CVE-2026-102275)","status":"published","published":"2026-10-22T14:35:20Z","modified":"2026-10-22T14:45:05.985764214Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"medium"}],"updated_at":"2026-11-02T15:52:47.981622Z"},{"id":"OESA-2026-4456","title":"python-jwt security update","description":"setup.py test %endif\r\n\r\nSecurity Fix(es):\n\nPyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT get_signing_key_from_jwt is affected because unknown kid misses force refreshes without a negative cache or minimum refresh interval. This occurs when unauthenticated tokens repeatedly use the same unknown kid or varying kid values absent from the cached JWKS. As a result, each cache miss causes PyJWKClient to refresh the JWKS. Consequently, attacker traffic can amplify outbound requests to the configured JWKS endpoint. This issue is fixed in version 2.14.0.(CVE-2026-101917)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.0.0a1 until 2.15.0, PyJWT PyJWKClient.get_signing_key_from_jwt is affected because payload parser catches ValueError but not RecursionError. This occurs when an attacker-controlled recursively nested payload reaches json.loads. As a result, documented PyJWT exception handling does not contain the failure. Consequently, an unauthenticated request can raise an exception that may produce an HTTP 500 response. The advisory-defined affected implementation also includes jwt/api_jwt.py, verify_signature=False. This issue is fixed in version 2.15.0.(CVE-2026-101918)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.13.0 until 2.14.0, PyJWS._load in jwt/api_jws.py is affected because parser catches ValueError but not RecursionError. This occurs when a deeply nested token header reaches json.loads. As a result, RecursionError escapes the documented PyJWT error hierarchy. Consequently, an unauthenticated malformed token can cause a request-level failure and HTTP 500. This issue is fixed in version 2.14.0.(CVE-2026-102265)\n\nPyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.(CVE-2026-102269)\n\nPyJWT is a Python implementation of JSON Web Token standards. From 2.1.0 until 2.15.0, PyJWT OKPAlgorithm.from_jwk in  jwt/algorithms.py is affected because private-JWK import path does not compare the public key derived from d with x. This occurs when an OKP private JWK supplies non-corresponding x and d components. As a result, identity derived from x can differ from operations performed with d. Consequently, if an integration also accepts private key parameters from a proof header without rejecting them, an attacker may use a stolen sender-constrained token without the legitimate private key. This issue is fixed in version 2.15.0.(CVE-2026-102275)","status":"published","published":"2026-10-22T14:35:21Z","modified":"2026-10-22T14:45:06.681086792Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N","level":"medium"}],"updated_at":"2026-11-02T15:52:47.981622Z"},{"id":"PYSEC-2026-4147","title":"PyJWT: Non-canonical signature segments enable raw-token revocation bypass","description":"## 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 -\u003e logout -\u003e 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.","status":"published","published":"2026-10-01T16:38:40.657304Z","modified":"2026-10-01T17:45:17.859342382Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T15:52:57.255153Z"},{"id":"UBUNTU-CVE-2026-102269","description":"PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.","status":"published","published":"2026-09-28T21:17:00Z","modified":"2026-10-20T10:45:29.271718682Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium"},{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T16:55:58.519486Z"},{"id":"AZL-104985","title":"CVE-2026-102269 affecting package python-jwt for versions less than 2.15.1-1","description":"PyJWT is a Python implementation of JSON Web Token standards. Prior to 2.14.0, PyJWT signature segment is affected because signature segment decoding accepts characters outside the canonical Base64URL representation. This occurs when non-Base64URL characters are appended to a valid compact JWS signature segment. As a result, base64url_decode produces the same signature bytes for different serialized segments. Consequently, raw-token revocation checks can fail to recognize an equivalent modified token. This issue is fixed in version 2.14.0.","status":"published","published":"2026-09-28T21:17:14Z","modified":"2026-11-02T14:15:51.48829648Z","updated_at":"2026-11-02T15:28:01.09948Z"},{"id":"CLSA-2026-1790839846","title":"TuxCare security update for pyjwt (6 CVEs)","description":"TuxCare rebuilt pyjwt to address 6 CVEs. Fixed in 2.10.1.post5+tuxcare.","status":"published","published":"2026-10-01T07:30:46Z","modified":"2026-10-15T09:47:42.038069757Z","updated_at":"2026-11-02T15:43:48.65893Z"},{"id":"CLSA-2026-1790843991","title":"TuxCare security update for pyjwt (6 CVEs)","description":"TuxCare rebuilt pyjwt to address 6 CVEs. Fixed in 2.8.0.post4+tuxcare.","status":"published","published":"2026-10-01T08:39:51Z","modified":"2026-10-15T09:48:02.364278194Z","updated_at":"2026-11-02T15:43:48.65893Z"},{"id":"CLSA-2026-1791271338","title":"TuxCare security update for pyjwt (7 CVEs)","description":"TuxCare rebuilt pyjwt to address 7 CVEs. Fixed in 2.3.0.post4+tuxcare.","status":"published","published":"2026-10-06T07:22:18Z","modified":"2026-10-15T09:48:07.255854927Z","updated_at":"2026-11-02T15:43:49.016976Z"},{"id":"CLSA-2026-1791272933","title":"TuxCare security update for pyjwt (3 CVEs)","description":"TuxCare rebuilt pyjwt to address 3 CVEs. Fixed in 1.7.1.post4+tuxcare.","status":"published","published":"2026-10-06T07:48:53Z","modified":"2026-10-15T09:48:07.256462998Z","updated_at":"2026-11-02T15:43:49.016976Z"},{"id":"openSUSE-SU-2026:11994-1","title":"python313-PyJWT-2.15.1-1.1 on GA media","description":"These are all security issues fixed in the python313-PyJWT-2.15.1-1.1 package on the GA media of openSUSE Tumbleweed.","status":"published","published":"2026-10-04T00:00:00Z","modified":"2026-10-05T10:15:15.811415752Z","updated_at":"2026-11-02T16:58:40.245664Z"},{"id":"openSUSE-SU-2026:22074-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues:\n\n- CVE-2026-101917: attacker traffic can amplify outbound requests to the configured JWKS endpoint (bsc#1283060).\n- CVE-2026-101918: unauthenticated request can raise an exception that may produce an HTTP 500 response (bsc#1283061).\n- CVE-2026-102267: forwarded credentials may be disclosed or verification keys may be substituted (bsc#1283064).\n- CVE-2026-102268: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283065).\n- CVE-2026-102269: raw-token revocation checks can fail to recognize an equivalent modified token (bsc#1283066).\n- CVE-2026-102270: an attacker can cause intensive CPU consumption (bsc#1283067).\n- CVE-2026-102271: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283068).\n- CVE-2026-102274: Malformed RSA JWK aborts parsing of an entire JWK Set (bsc#1283071).\n- CVE-2026-102275: an attacker may use a stolen sender-constrained token without the legitimate private key\n  (bsc#1283072).\n- CVE-2026-103001: reused mutable options mapping can cause verification checks bypass on subsequent decode calls\n  (bsc#1283829).\n","status":"published","published":"2026-10-09T12:17:21Z","modified":"2026-10-19T17:23:12.597526095Z","updated_at":"2026-11-02T16:58:42.426393Z"},{"id":"SUSE-SU-2026:24126-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues:\n\n- CVE-2026-101917: attacker traffic can amplify outbound requests to the configured JWKS endpoint (bsc#1283060).\n- CVE-2026-101918: unauthenticated request can raise an exception that may produce an HTTP 500 response (bsc#1283061).\n- CVE-2026-102267: forwarded credentials may be disclosed or verification keys may be substituted (bsc#1283064).\n- CVE-2026-102268: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283065).\n- CVE-2026-102269: raw-token revocation checks can fail to recognize an equivalent modified token (bsc#1283066).\n- CVE-2026-102270: an attacker can cause intensive CPU consumption (bsc#1283067).\n- CVE-2026-102271: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283068).\n- CVE-2026-102274: Malformed RSA JWK aborts parsing of an entire JWK Set (bsc#1283071).\n- CVE-2026-102275: an attacker may use a stolen sender-constrained token without the legitimate private key\n  (bsc#1283072).\n- CVE-2026-103001: reused mutable options mapping can cause verification checks bypass on subsequent decode calls\n  (bsc#1283829).\n","status":"published","published":"2026-10-09T12:13:13Z","modified":"2026-10-13T17:23:10.206484436Z","updated_at":"2026-11-02T15:54:04.519245Z"},{"id":"SUSE-SU-2026:24134-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues:\n\n- CVE-2026-101917: attacker traffic can amplify outbound requests to the configured JWKS endpoint (bsc#1283060).\n- CVE-2026-101918: unauthenticated request can raise an exception that may produce an HTTP 500 response (bsc#1283061).\n- CVE-2026-102267: forwarded credentials may be disclosed or verification keys may be substituted (bsc#1283064).\n- CVE-2026-102268: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283065).\n- CVE-2026-102269: raw-token revocation checks can fail to recognize an equivalent modified token (bsc#1283066).\n- CVE-2026-102270: an attacker can cause intensive CPU consumption (bsc#1283067).\n- CVE-2026-102271: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283068).\n- CVE-2026-102274: Malformed RSA JWK aborts parsing of an entire JWK Set (bsc#1283071).\n- CVE-2026-102275: an attacker may use a stolen sender-constrained token without the legitimate private key\n  (bsc#1283072).\n","status":"published","published":"2026-10-09T11:50:01Z","modified":"2026-10-15T17:23:11.683497084Z","updated_at":"2026-11-02T15:54:04.519245Z"},{"id":"SUSE-SU-2026:24219-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues:\n\n- CVE-2026-101917: attacker traffic can amplify outbound requests to the configured JWKS endpoint (bsc#1283060).\n- CVE-2026-101918: unauthenticated request can raise an exception that may produce an HTTP 500 response (bsc#1283061).\n- CVE-2026-102267: forwarded credentials may be disclosed or verification keys may be substituted (bsc#1283064).\n- CVE-2026-102268: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283065).\n- CVE-2026-102269: raw-token revocation checks can fail to recognize an equivalent modified token (bsc#1283066).\n- CVE-2026-102270: an attacker can cause intensive CPU consumption (bsc#1283067).\n- CVE-2026-102271: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283068).\n- CVE-2026-102274: Malformed RSA JWK aborts parsing of an entire JWK Set (bsc#1283071).\n- CVE-2026-102275: an attacker may use a stolen sender-constrained token without the legitimate private key\n  (bsc#1283072).\n- CVE-2026-103001: reused mutable options mapping can cause verification checks bypass on subsequent decode calls\n  (bsc#1283829).\n","status":"published","published":"2026-10-09T12:13:13Z","modified":"2026-10-21T17:23:10.838650728Z","updated_at":"2026-11-02T15:54:04.519245Z"},{"id":"SUSE-SU-2026:24377-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues:\n\n- CVE-2026-101917: attacker traffic can amplify outbound requests to the configured JWKS endpoint (bsc#1283060).\n- CVE-2026-101918: unauthenticated request can raise an exception that may produce an HTTP 500 response (bsc#1283061).\n- CVE-2026-102267: forwarded credentials may be disclosed or verification keys may be substituted (bsc#1283064).\n- CVE-2026-102268: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283065).\n- CVE-2026-102269: raw-token revocation checks can fail to recognize an equivalent modified token (bsc#1283066).\n- CVE-2026-102270: an attacker can cause intensive CPU consumption (bsc#1283067).\n- CVE-2026-102271: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283068).\n- CVE-2026-102274: Malformed RSA JWK aborts parsing of an entire JWK Set (bsc#1283071).\n- CVE-2026-102275: an attacker may use a stolen sender-constrained token without the legitimate private key\n  (bsc#1283072).\n- CVE-2026-103001: reused mutable options mapping can cause verification checks bypass on subsequent decode calls\n  (bsc#1283829).\n","status":"published","published":"2026-10-20T15:12:20Z","modified":"2026-10-27T17:23:19.403372208Z","updated_at":"2026-11-02T15:54:04.519245Z"},{"id":"SUSE-SU-2026:5064-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues:\n\n- CVE-2026-101917: attacker traffic can amplify outbound requests to the configured JWKS endpoint (bsc#1283060).\n- CVE-2026-101918: unauthenticated request can raise an exception that may produce an HTTP 500 response (bsc#1283061).\n- CVE-2026-102267: forwarded credentials may be disclosed or verification keys may be substituted (bsc#1283064).\n- CVE-2026-102268: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283065).\n- CVE-2026-102269: raw-token revocation checks can fail to recognize an equivalent modified token (bsc#1283066).\n- CVE-2026-102270: an attacker can cause intensive CPU consumption (bsc#1283067).\n- CVE-2026-102271: an attacker who knows the public key can forge authenticated HMAC tokens (bsc#1283068).\n- CVE-2026-102275: an attacker may use a stolen sender-constrained token without the legitimate private key\n  (bsc#1283072).\n","status":"published","published":"2026-11-03T07:32:38Z","modified":"2026-11-03T22:45:04.564059533Z","updated_at":"2026-11-03T22:59:30.419462Z"}]}}
