{"id":"CVE-2026-48525","aliases":["AZL-93042","BELL-CVE-2026-48525","CLSA-2026-1783299703","CLSA-2026-1787038724","DEBIAN-CVE-2026-48525","EUVD-2026-32919","GHSA-w7vc-732c-9m39","PYSEC-2026-178","ROOT-APP-PYPI-CVE-2026-48525","SUSE-SU-2026:22138-1","SUSE-SU-2026:22170-1","SUSE-SU-2026:22220-1","SUSE-SU-2026:22238-1","SUSE-SU-2026:2626-1","SUSE-SU-2026:2627-1","UBUNTU-CVE-2026-48525","USN-8823-1","openSUSE-SU-2026:11024-1","openSUSE-SU-2026:21095-1"],"status":"published","title":"PyJWT: Unauthenticated DoS via unbounded Base64URL decoding of unused payload segment in b64=false detached JWS","description":"PyJWT is a JSON Web Token implementation in Python. From 2.8.0 to 2.12.1, when verifying detached JWS tokens using the unencoded-payload option (\"b64\": false, RFC 7797), PyJWT performs Base64URL decoding of the compact-serialization payload segment before enforcing the detached-payload rules. For b64=false, PyJWT later discards that decoded payload and replaces it with the caller-provided detached_payload. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces CPU work + memory allocations even if the signature is invalid. This creates an unauthenticated DoS vector against any endpoint that verifies detached JWS using PyJWT. This vulnerability is fixed in 2.13.0.","severity":{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.0037,"percentile":0.31248,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-400"],"published":"2026-05-28T15:11:12Z","modified":"2026-10-28T10:15:02Z","fixes":[{"ecosystem":"PyPI","package":"pyjwt","fixed_in":["2.13.0"],"sources":["ghsa","osv"]},{"ecosystem":"Azure Linux:3","package":"python-jwt","fixed_in":["2.13.0-1"],"sources":["osv"]},{"ecosystem":"Alpaquita:25","package":"py3-jwt","fixed_in":["2.13.0-r0"],"sources":["osv"]},{"ecosystem":"Alpaquita:stream","package":"py3-jwt","fixed_in":["2.13.0-r0"],"sources":["osv"]},{"ecosystem":"Debian:14","package":"pyjwt","fixed_in":["2.13.0-1"],"sources":["osv"]},{"ecosystem":"Root:PyPI","package":"PyJWT","fixed_in":["2.9.0+aikido.6","2.10.1+aikido.6","2.8.0+aikido.5","2.12.0+aikido.3","2.12.1+aikido.3","2.10.1+aikido.7","2.12.0+aikido.4","2.12.0+aikido.5","2.8.0+aikido.6","2.12.1+aikido.4","2.12.1+aikido.5"],"sources":["osv"]},{"ecosystem":"Ubuntu:24.04:LTS","package":"pyjwt","fixed_in":["2.7.0-1ubuntu0.2"],"sources":["osv"]},{"ecosystem":"Ubuntu:26.04:LTS","package":"pyjwt","fixed_in":["2.10.1-4ubuntu1.1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.8.0"},{"last_affected":"2.12.1"},{"fixed":"2.13.0"}]}]},{"source":"osv","ecosystem":"Azure Linux:3","package":"python-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.13.0-1"}]}]},{"source":"osv","ecosystem":"Alpaquita:25","package":"py3-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.10.1-r0"},{"fixed":"2.13.0-r0"}]}]},{"source":"osv","ecosystem":"Alpaquita:stream","package":"py3-jwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.8.0-r1"},{"fixed":"2.13.0-r0"}]}]},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.10.1"},{"fixed":"2.10.1.post2+tuxcare"}]}],"versions":["2.10.1","2.10.1.post1+tuxcare"],"bundled":4},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.7.1"},{"fixed":"1.7.1.post3+tuxcare"}]}],"versions":["1.7.1","1.7.1.post1+tuxcare","1.7.1.post2+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.13.0-1"}]}],"versions":["2.10.1-2","2.10.1-3","2.10.1-4","2.11.0-2","2.12.1-1"]},{"source":"osv","ecosystem":"PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.8.0"},{"fixed":"2.13.0"}]}],"versions":["2.10.0","2.10.1","2.11.0","2.12.0","2.12.1","2.8.0","2.9.0"]},{"source":"osv","ecosystem":"PyPI","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.8.0"},{"fixed":"2.13.0"}]}],"versions":["2.10.0","2.10.1","2.11.0","2.12.0","2.12.1","2.8.0","2.9.0"]},{"source":"osv","ecosystem":"Root:PyPI","package":"PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.9.0+aikido.6"},{"fixed":"2.10.1+aikido.6"},{"fixed":"2.8.0+aikido.5"},{"fixed":"2.12.0+aikido.3"},{"fixed":"2.12.1+aikido.3"},{"fixed":"2.10.1+aikido.7"},{"fixed":"2.12.0+aikido.4"},{"fixed":"2.12.0+aikido.5"},{"fixed":"2.8.0+aikido.6"},{"fixed":"2.12.1+aikido.4"},{"fixed":"2.12.1+aikido.5"}]}]},{"source":"osv","ecosystem":"SUSE:Linux Micro 6.1","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.12.1-slfo.1.1_2.1"}]}],"bundled":5},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 16.0","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.12.1-160000.2.1"}]}],"bundled":5},{"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.2.1"}]}],"bundled":5},{"source":"osv","ecosystem":"SUSE:Linux Micro 6.2","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.12.1-160000.2.1"}]}],"bundled":5},{"source":"osv","ecosystem":"SUSE:Linux Micro 6.0","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.9.0-3.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"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.13.1"}]}],"bundled":5},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Micro 5.5","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Module for Basesystem 15 SP7","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise High Performance Computing 15 SP4-ESPOS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise High Performance Computing 15 SP4-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise High Performance Computing 15 SP5-ESPOS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise High Performance Computing 15 SP5-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 15 SP4-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 15 SP5-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 15 SP6-LTSS","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP Applications 15 SP4","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP Applications 15 SP5","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP Applications 15 SP6","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.4.0-150200.3.14.1"}]}],"bundled":4},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.7.0-1ubuntu0.2"}]}],"versions":["2.7.0-1","2.7.0-1ubuntu0.1"]},{"source":"osv","ecosystem":"Ubuntu:25.10","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.10.1-2","2.10.1-2ubuntu0.1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.10.1-4ubuntu1.1"}]}],"versions":["2.10.1-2","2.10.1-3","2.10.1-3build1","2.10.1-4","2.10.1-4ubuntu1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:16.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.0-1ubuntu0.1+esm2"}]}],"versions":["1.0.0-0ubuntu1","1.3.0-1","1.3.0-1ubuntu0.1","1.3.0-1ubuntu0.1+esm1"],"bundled":5},{"source":"osv","ecosystem":"Ubuntu:Pro:18.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.5.3+ds1-1ubuntu0.1+esm2"}]}],"versions":["1.4.2-1.1","1.5.3+ds1-1","1.5.3+ds1-1ubuntu0.1","1.5.3+ds1-1ubuntu0.1+esm1"],"bundled":5},{"source":"osv","ecosystem":"Ubuntu:Pro:20.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.7.1-2ubuntu2.1+esm2"}]}],"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"],"bundled":5},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.3.0-1ubuntu0.4"}]}],"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"],"bundled":5},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.7.0-1ubuntu0.2"}]}],"versions":["2.7.0-1","2.7.0-1ubuntu0.1"],"bundled":5},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"pyjwt","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.10.1-4ubuntu1.1"}]}],"versions":["2.10.1-2","2.10.1-3","2.10.1-3build1","2.10.1-4","2.10.1-4ubuntu1"],"bundled":5},{"source":"osv","ecosystem":"openSUSE:Tumbleweed","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.13.0-1.1"}]}],"bundled":5},{"source":"osv","ecosystem":"openSUSE:Leap 16.0","package":"python-PyJWT","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.12.1-160000.2.1"}]}],"bundled":5}],"products":[{"vendor":"jpadilla","product":"pyjwt"},{"vendor":"pyjwt_project","product":"pyjwt"}],"references":[{"url":"https://github.com/jpadilla/pyjwt/security/advisories/GHSA-w7vc-732c-9m39","tags":["x_refsource_CONFIRM","exploit","Exploit","Mitigation","Vendor Advisory","advisory","web","evidence","report"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-48525","tags":["web","advisory"]},{"url":"https://github.com/pypa/advisory-database/tree/main/vulns/pyjwt/PYSEC-2026-178.yaml","tags":["web"]},{"url":"https://github.com/advisories/GHSA-w7vc-732c-9m39"},{"url":"https://docs.bell-sw.com/security/cves/CVE-2026-48525","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/48xxx/CVE-2026-48525.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-48525","tags":["advisory"]},{"url":"https://github.com/jpadilla/pyjwt","tags":["package"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202622138-1/","tags":["advisory"]},{"url":"https://bugzilla.suse.com/1266798","tags":["report"]},{"url":"https://bugzilla.suse.com/1266799","tags":["report"]},{"url":"https://bugzilla.suse.com/1266800","tags":["report"]},{"url":"https://bugzilla.suse.com/1266801","tags":["report"]},{"url":"https://bugzilla.suse.com/1266802","tags":["report"]},{"url":"https://www.suse.com/security/cve/CVE-2026-48522","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-48523","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-48524","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-48525","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-48526","tags":["web"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202622170-1/","tags":["advisory"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202622220-1/","tags":["advisory"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202622238-1/","tags":["advisory"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-20262626-1/","tags":["advisory"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-20262627-1/","tags":["advisory"]},{"url":"https://ubuntu.com/security/CVE-2026-48525","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-48525","tags":["report"]},{"url":"https://ubuntu.com/security/notices/USN-8823-1","tags":["advisory"]},{"url":"https://ubuntu.com/security/CVE-2026-48522","tags":["report"]},{"url":"https://ubuntu.com/security/CVE-2026-48523","tags":["report"]},{"url":"https://ubuntu.com/security/CVE-2026-48524","tags":["report"]},{"url":"https://ubuntu.com/security/CVE-2026-48526","tags":["report"]},{"url":"","tags":["advisory"]}],"sources":{"cve":[{"id":"CVE-2026-48525","title":"PyJWT: Unauthenticated DoS via unbounded Base64URL decoding of unused payload segment in b64=false detached JWS","description":"PyJWT is a JSON Web Token implementation in Python. From 2.8.0 to 2.12.1, when verifying detached JWS tokens using the unencoded-payload option (\"b64\": false, RFC 7797), PyJWT performs Base64URL decoding of the compact-serialization payload segment before enforcing the detached-payload rules. For b64=false, PyJWT later discards that decoded payload and replaces it with the caller-provided detached_payload. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces CPU work + memory allocations even if the signature is invalid. This creates an unauthenticated DoS vector against any endpoint that verifies detached JWS using PyJWT. This vulnerability is fixed in 2.13.0.","status":"published","published":"2026-05-28T15:11:12.483Z","modified":"2026-06-23T15:54:09.003Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium","assessor":"cna"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T13:50:38.739766Z"}],"euvd":[{"id":"EUVD-2026-32919","description":"PyJWT is a JSON Web Token implementation in Python. From 2.8.0 to 2.12.1, when verifying detached JWS tokens using the unencoded-payload option (\"b64\": false, RFC 7797), PyJWT performs Base64URL decoding of the compact-serialization payload segment before enforcing the detached-payload rules. For b64=false, PyJWT later discards that decoded payload and replaces it with the caller-provided detached_payload. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces CPU work + memory allocations even if the signature is invalid. This creates an unauthenticated DoS vector against any endpoint that verifies detached JWS using PyJWT. This vulnerability is fixed in 2.13.0.","status":"published","published":"2026-05-28T15:11:12Z","modified":"2026-06-23T15:54:09Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"updated_at":"2026-11-02T18:20:32.768333Z"}],"ghsa":[{"id":"GHSA-w7vc-732c-9m39","title":"PyJWT: Unauthenticated DoS via unbounded Base64URL decoding of unused payload segment in b64=false detached JWS","description":"\u003e [!NOTE]\n\u003e Practical impact depends on whether request body-size limits are enforced upstream (proxy/web-server/framework). Deployments with typical body-size caps (≤2 MB) bound the amplifier significantly; deployments accepting larger token inputs are more exposed.\n\nWhen verifying detached JWS tokens using the unencoded-payload option (`\"b64\": false`, RFC 7797), PyJWT performs **Base64URL decoding of the compact-serialization payload segment** *before* enforcing the detached-payload rules.\n\nFor `b64=false`, PyJWT later **discards** that decoded payload and replaces it with the caller-provided `detached_payload`. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces **CPU work + memory allocations** even if the signature is invalid.\n\nThis creates an **unauthenticated DoS** vector against any endpoint that verifies detached JWS using PyJWT.\n\n---\n\n## Affected Component(s)\n\n* `jwt/api_jws.py`\n\n  * `PyJWS.decode()` / `PyJWS.decode_complete()`\n  * `_load()` (parsing and Base64URL decoding)\n\n---\n\n## Root Cause (exact logic flaw)\n\n### What happens in the code\n\nIn `jwt/api_jws.py`, `decode_complete()` does the following (order matters):\n\n* Calls `_load(jwt)` first, which decodes the token segments\n* Only after that, checks `header.get(\"b64\")` and if `False`, it replaces `payload = detached_payload` and rebuilds the signing input\n\nThis behavior is visible in `decode_complete()`:\n\n* `_load(jwt)` happens **before** the `b64=false` handling\n* then `payload = detached_payload` and `signing_input = ... detached_payload` happens afterward ([GitHub][1])\n\nInside `_load()`, PyJWT unconditionally performs:\n\n* `payload = base64url_decode(payload_segment)`\n  This is the expensive step the attacker can amplify ([GitHub][1])\n\n### Why this becomes a vulnerability\n\nFor `b64=false` detached JWS, the payload segment in compact form is effectively **not needed** for verification in PyJWT’s own logic (since the library uses `detached_payload` as the real payload). Yet PyJWT still decodes it first, meaning:\n\n* cost is paid **even when signature is invalid**\n* the decoded bytes are **discarded**\n* attacker controls the size of this cost via token length\n\n---\n\n## Impact (evidence-driven)\n\n### Security impact\n\n* **Unauthenticated remote DoS**: decoding work happens before signature rejection → attacker does not need signing key.\n* **CPU amplification**: Base64URL decode time scales linearly with payload segment size.\n* **Memory amplification**: decoded output allocates large byte buffers (tens of MB per request).\n* **Operational impact**: request queueing / worker starvation under modest concurrency bursts.\n\n### Standards context (RFC 7797)\n\nRFC 7797 explicitly notes this option is used when payload is large and/or detached, and discusses interoperability requirements around marking it critical (“crit” with “b64”). ([IETF Datatracker][2])\n(PyJWT supports `crit` validation, but the issue here is decode order / unbounded decode of an unused segment.)\n\n---\n\n## Affected Versions\n\n* **Confirmed affected:** PyJWT **2.12.1** (tested from your local editable install and repo).\n* **Likely affected:** all versions that include detached payload support for JWS decoding, which was introduced in **2.4.0** (“Add detached payload support for JWS encoding and decoding”). ([pyjwt.readthedocs.io][3])\n\n(For GHSA, this phrasing is strong: “confirmed” + “likely since feature introduction”.)\n\n---\n\n# Threat Model \n\n### Typical real deployment\n\nA service verifies signed HTTP requests or webhooks using detached JWS:\n\n* token is provided in JSON body / query / header\n* actual payload is the HTTP request body passed as `detached_payload`\n\n### Attacker\n\n* remote unauthenticated client\n* can send requests to verify endpoint\n* does **not** need a valid signature (invalid signature still triggers the expensive decode path)\n\n### Attack chain\n\n1. Attacker crafts a JWS compact token with header containing `\"b64\": false` and `crit:[\"b64\"]`.\n2. Attacker inflates the **payload segment** (middle segment) to millions of Base64URL characters.\n3. Server calls `PyJWS.decode(...detached_payload=...)`.\n4. PyJWT decodes the inflated segment (CPU + memory).\n5. Signature is rejected afterward (401) — but resources already consumed.\n6. Repeated requests or bursts cause queueing/worker starvation → DoS.\n\n---\n\n# Proof of Concept - file names + results\n\n## PoC placement \n\n* [server_localhost.py](https://github.com/user-attachments/files/26132755/server_localhost.py)\n\n* [client_localhost.py](https://github.com/user-attachments/files/26132757/client_localhost.py)\n\n* [flood_localhost.py](https://github.com/user-attachments/files/26132760/flood_localhost.py)\n\n\n---\n\n## PoC # 1 - Localhost verification server\n\n**File:** [server_localhost.py](https://github.com/user-attachments/files/26132755/server_localhost.py)\n\n**Purpose:** real HTTP endpoint (`POST /verify`) that calls PyJWT detached verification and prints:\n`ok / time_ms / peak_bytes / token_len / error`.\n\n### Results (server console output)\n\n```text\n[+] Listening on http://127.0.0.1:8000\n[+] POST /verify  JSON: {\"token\": \"...\"}\n\n[127.0.0.1] ok=True  time_ms=0.102 peak_bytes=2624     token_len=117      err=None\n[127.0.0.1] ok=False time_ms=2.012 peak_bytes=2000983  token_len=500078   err=InvalidSignatureError\n[127.0.0.1] ok=True  time_ms=1.591 peak_bytes=2001061  token_len=500117   err=None\n\n[127.0.0.1] ok=True  time_ms=0.065 peak_bytes=2304     token_len=117      err=None\n[127.0.0.1] ok=False time_ms=7.534 peak_bytes=8000983  token_len=2000078  err=InvalidSignatureError\n[127.0.0.1] ok=True  time_ms=6.347 peak_bytes=8001061  token_len=2000117  err=None\n\n[127.0.0.1] ok=True  time_ms=0.066 peak_bytes=2304     token_len=117      err=None\n[127.0.0.1] ok=False time_ms=23.034 peak_bytes=32000983 token_len=8000078 err=InvalidSignatureError\n[127.0.0.1] ok=True  time_ms=22.097 peak_bytes=32001061 token_len=8000117 err=None\n```\n\n**Key takeaways from these results**\n\n* At **8,000,000 chars**, a single invalid-signature request still causes:\n\n  * **~23 ms** server work\n  * **~32 MB** peak allocations\n  * returns **401** (invalid signature) → attacker does not need key.\n\n---\n\n## PoC # 2 - Localhost network client\n\n**File:** [client_localhost.py](https://github.com/user-attachments/files/26132757/client_localhost.py)\n**Purpose:** generates baseline + (invalid signature) + (valid signature) tokens and sends them over HTTP to localhost server.\n\n### Results (client output)\n\n#### payload-chars = 500,000\n\n```text\n=== BASELINE (valid b64=false token) ===\nHTTP: 200\nclient_wall_ms: 6.3499...\nserver_time_ms: 0.10197...\nserver_peak_bytes: 2624\n\n=== ATTACK (INVALID signature - attacker needs no key) ===\nHTTP: 401\nclient_wall_ms: 4.1010...\nserver_time_ms: 2.01217...\nserver_peak_bytes: 2000983\nerror: InvalidSignatureError\n\n=== ATTACK (VALID signature - accepted path still wastes) ===\nHTTP: 200\nclient_wall_ms: 3.6586...\nserver_time_ms: 1.59092...\nserver_peak_bytes: 2001061\n```\n\n#### payload-chars = 2,000,000\n\n```text\n=== BASELINE ===\nHTTP: 200\nserver_time_ms: 0.06527...\nserver_peak_bytes: 2304\n\n=== ATTACK (INVALID signature) ===\nHTTP: 401\nserver_time_ms: 7.53430...\nserver_peak_bytes: 8000983\n\n=== ATTACK (VALID signature) ===\nHTTP: 200\nserver_time_ms: 6.34682...\nserver_peak_bytes: 8001061\n```\n\n#### payload-chars = 8,000,000\n\n```text\n=== BASELINE ===\nHTTP: 200\nserver_time_ms: 0.06573...\nserver_peak_bytes: 2304\n\n=== ATTACK (INVALID signature) ===\nHTTP: 401\nserver_time_ms: 23.03403...\nserver_peak_bytes: 32000983\n\n=== ATTACK (VALID signature) ===\nHTTP: 200\nserver_time_ms: 22.09702...\nserver_peak_bytes: 32001061\n```\n\n**Why this is strong evidence**\n\n* The server clearly does heavy work **before** rejecting invalid signatures.\n* The “valid signature” case shows even accepted requests waste resources due to unused payload segment.\n\n---\n\n## PoC # 3 - Localhost flood / burst concurrency\n\n**File:** [flood_localhost.py](https://github.com/user-attachments/files/26132760/flood_localhost.py)\n**Purpose:** sends **N concurrent** invalid-signature requests over HTTP to demonstrate queueing/worker starvation.\n\n### Results (your run: 20 concurrent @ 8,000,000 chars)\n\n```text\ntotal_wall_ms: 1374.5405770000616\n\n(16, 401, 1156.4504789998864, 21.350951999920653, 32000983, 'InvalidSignatureError')\n(19, 401, 1151.2852699997893, 21.208721999755653, 32000983, 'InvalidSignatureError')\n(18, 401, 1102.7211239997996, 21.685218999664357, 32000983, 'InvalidSignatureError')\n(13, 401, 1102.0718189997751, 21.26572200040755, 32000983, 'InvalidSignatureError')\n(11, 401, 1095.9345460000804, 20.586017000368884, 32000983, 'InvalidSignatureError')\n(17, 401, 1085.2552810001725, 22.893039000337012, 32000983, 'InvalidSignatureError')\n(10, 401, 1078.3629560000918, 22.737160999895423, 32000983, 'InvalidSignatureError')\n(7,  401, 1048.2011740000416, 22.476282000297942, 32000983, 'InvalidSignatureError')\n(8,  401, 378.93017700025666, 21.377330999712285, 32000983, 'InvalidSignatureError')\n(1,  401, 281.45106800002395, 21.34223099983501, 32000983, 'InvalidSignatureError')\n```\n\n**Interpretation**\n\n* Each request still costs ~**20–23 ms** server processing and **~32 MB** peak allocations.\n* But client-observed latency rises up to **~1.15 seconds** because requests queue behind each other → clear worker starvation/HoL blocking.\n* All were rejected with **401 InvalidSignatureError** → still unauthenticated.\n\n---\n\n# Fix \n\n### Goal\n\nPrevent unbounded resource consumption from an attacker-controlled payload segment that is unused in `b64=false` detached flow.\n\n### Minimal change strategy\n\nIn `_load()` (or by refactoring parse order), **do not Base64-decode `payload_segment` until after you know whether `b64=false` applies**.\n\nTwo safe options:\n\n1. **Reject non-empty payload segment when `b64=false`**\n\n   * Parse header first\n   * If `b64` is false and `payload_segment` is non-empty → raise `DecodeError` *before* decoding\n   * Then verification uses `detached_payload` only\n\n2. **Skip decoding payload segment entirely when `b64=false`**\n\n   * Keep payload segment as raw bytes or empty\n   * Use detached payload for signing input\n\nThis aligns with the idea that detached payload is the trusted payload input for verification; the compact payload segment should not become a resource amplification vector.\n\n(Implementation context: the current decode order and unconditional `base64url_decode(payload_segment)` are visible in the file and line region around `_load()` and `decode_complete()` ([GitHub][1]).)\n\n---\n\n# Workarounds\n\n* Enforce strict **max token length** at the HTTP boundary (proxy/gateway).\n* Apply rate limiting on verification endpoints.\n* If detached JWS (`b64=false`) is not needed in your app, reject tokens where header includes `\"b64\": false`.","status":"published","published":"2026-06-15T19:29:12Z","modified":"2026-06-15T19:29:13Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:40:27.329706Z"}],"nvd":[{"id":"CVE-2026-48525","description":"PyJWT is a JSON Web Token implementation in Python. From 2.8.0 to 2.12.1, when verifying detached JWS tokens using the unencoded-payload option (\"b64\": false, RFC 7797), PyJWT performs Base64URL decoding of the compact-serialization payload segment before enforcing the detached-payload rules. For b64=false, PyJWT later discards that decoded payload and replaces it with the caller-provided detached_payload. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces CPU work + memory allocations even if the signature is invalid. This creates an unauthenticated DoS vector against any endpoint that verifies detached JWS using PyJWT. This vulnerability is fixed in 2.13.0.","status":"published","published":"2026-05-28T16:16:29.533Z","modified":"2026-10-25T23:10:00.167Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium","assessor":"secondary"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:12:21.244426Z"}],"osv":[{"id":"BELL-CVE-2026-48525","status":"published","published":"2026-06-02T06:11:45.971904Z","modified":"2026-06-23T07:15:07.708628172Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"updated_at":"2026-11-02T15:28:24.391068Z"},{"id":"CVE-2026-48525","title":"PyJWT: Unauthenticated DoS via unbounded Base64URL decoding of unused payload segment in b64=false detached JWS","description":"PyJWT is a JSON Web Token implementation in Python. From 2.8.0 to 2.12.1, when verifying detached JWS tokens using the unencoded-payload option (\"b64\": false, RFC 7797), PyJWT performs Base64URL decoding of the compact-serialization payload segment before enforcing the detached-payload rules. For b64=false, PyJWT later discards that decoded payload and replaces it with the caller-provided detached_payload. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces CPU work + memory allocations even if the signature is invalid. This creates an unauthenticated DoS vector against any endpoint that verifies detached JWS using PyJWT. This vulnerability is fixed in 2.13.0.","status":"published","published":"2026-05-28T15:11:12.483Z","modified":"2026-10-28T02:31:11.107633216Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:45:06.701612Z"},{"id":"DEBIAN-CVE-2026-48525","description":"PyJWT is a JSON Web Token implementation in Python. From 2.8.0 to 2.12.1, when verifying detached JWS tokens using the unencoded-payload option (\"b64\": false, RFC 7797), PyJWT performs Base64URL decoding of the compact-serialization payload segment before enforcing the detached-payload rules. For b64=false, PyJWT later discards that decoded payload and replaces it with the caller-provided detached_payload. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces CPU work + memory allocations even if the signature is invalid. This creates an unauthenticated DoS vector against any endpoint that verifies detached JWS using PyJWT. This vulnerability is fixed in 2.13.0.","status":"published","published":"2026-05-28T16:16:29.533Z","modified":"2026-10-26T11:47:24.585177344Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"updated_at":"2026-11-02T15:45:56.444225Z"},{"id":"GHSA-w7vc-732c-9m39","title":"PyJWT: Unauthenticated DoS via unbounded Base64URL decoding of unused payload segment in b64=false detached JWS","description":"\u003e [!NOTE]\n\u003e Practical impact depends on whether request body-size limits are enforced upstream (proxy/web-server/framework). Deployments with typical body-size caps (≤2 MB) bound the amplifier significantly; deployments accepting larger token inputs are more exposed.\n\nWhen verifying detached JWS tokens using the unencoded-payload option (`\"b64\": false`, RFC 7797), PyJWT performs **Base64URL decoding of the compact-serialization payload segment** *before* enforcing the detached-payload rules.\n\nFor `b64=false`, PyJWT later **discards** that decoded payload and replaces it with the caller-provided `detached_payload`. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces **CPU work + memory allocations** even if the signature is invalid.\n\nThis creates an **unauthenticated DoS** vector against any endpoint that verifies detached JWS using PyJWT.\n\n---\n\n## Affected Component(s)\n\n* `jwt/api_jws.py`\n\n  * `PyJWS.decode()` / `PyJWS.decode_complete()`\n  * `_load()` (parsing and Base64URL decoding)\n\n---\n\n## Root Cause (exact logic flaw)\n\n### What happens in the code\n\nIn `jwt/api_jws.py`, `decode_complete()` does the following (order matters):\n\n* Calls `_load(jwt)` first, which decodes the token segments\n* Only after that, checks `header.get(\"b64\")` and if `False`, it replaces `payload = detached_payload` and rebuilds the signing input\n\nThis behavior is visible in `decode_complete()`:\n\n* `_load(jwt)` happens **before** the `b64=false` handling\n* then `payload = detached_payload` and `signing_input = ... detached_payload` happens afterward ([GitHub][1])\n\nInside `_load()`, PyJWT unconditionally performs:\n\n* `payload = base64url_decode(payload_segment)`\n  This is the expensive step the attacker can amplify ([GitHub][1])\n\n### Why this becomes a vulnerability\n\nFor `b64=false` detached JWS, the payload segment in compact form is effectively **not needed** for verification in PyJWT’s own logic (since the library uses `detached_payload` as the real payload). Yet PyJWT still decodes it first, meaning:\n\n* cost is paid **even when signature is invalid**\n* the decoded bytes are **discarded**\n* attacker controls the size of this cost via token length\n\n---\n\n## Impact (evidence-driven)\n\n### Security impact\n\n* **Unauthenticated remote DoS**: decoding work happens before signature rejection → attacker does not need signing key.\n* **CPU amplification**: Base64URL decode time scales linearly with payload segment size.\n* **Memory amplification**: decoded output allocates large byte buffers (tens of MB per request).\n* **Operational impact**: request queueing / worker starvation under modest concurrency bursts.\n\n### Standards context (RFC 7797)\n\nRFC 7797 explicitly notes this option is used when payload is large and/or detached, and discusses interoperability requirements around marking it critical (“crit” with “b64”). ([IETF Datatracker][2])\n(PyJWT supports `crit` validation, but the issue here is decode order / unbounded decode of an unused segment.)\n\n---\n\n## Affected Versions\n\n* **Confirmed affected:** PyJWT **2.12.1** (tested from your local editable install and repo).\n* **Likely affected:** all versions that include detached payload support for JWS decoding, which was introduced in **2.4.0** (“Add detached payload support for JWS encoding and decoding”). ([pyjwt.readthedocs.io][3])\n\n(For GHSA, this phrasing is strong: “confirmed” + “likely since feature introduction”.)\n\n---\n\n# Threat Model \n\n### Typical real deployment\n\nA service verifies signed HTTP requests or webhooks using detached JWS:\n\n* token is provided in JSON body / query / header\n* actual payload is the HTTP request body passed as `detached_payload`\n\n### Attacker\n\n* remote unauthenticated client\n* can send requests to verify endpoint\n* does **not** need a valid signature (invalid signature still triggers the expensive decode path)\n\n### Attack chain\n\n1. Attacker crafts a JWS compact token with header containing `\"b64\": false` and `crit:[\"b64\"]`.\n2. Attacker inflates the **payload segment** (middle segment) to millions of Base64URL characters.\n3. Server calls `PyJWS.decode(...detached_payload=...)`.\n4. PyJWT decodes the inflated segment (CPU + memory).\n5. Signature is rejected afterward (401) — but resources already consumed.\n6. Repeated requests or bursts cause queueing/worker starvation → DoS.\n\n---\n\n# Proof of Concept - file names + results\n\n## PoC placement \n\n* [server_localhost.py](https://github.com/user-attachments/files/26132755/server_localhost.py)\n\n* [client_localhost.py](https://github.com/user-attachments/files/26132757/client_localhost.py)\n\n* [flood_localhost.py](https://github.com/user-attachments/files/26132760/flood_localhost.py)\n\n\n---\n\n## PoC # 1 - Localhost verification server\n\n**File:** [server_localhost.py](https://github.com/user-attachments/files/26132755/server_localhost.py)\n\n**Purpose:** real HTTP endpoint (`POST /verify`) that calls PyJWT detached verification and prints:\n`ok / time_ms / peak_bytes / token_len / error`.\n\n### Results (server console output)\n\n```text\n[+] Listening on http://127.0.0.1:8000\n[+] POST /verify  JSON: {\"token\": \"...\"}\n\n[127.0.0.1] ok=True  time_ms=0.102 peak_bytes=2624     token_len=117      err=None\n[127.0.0.1] ok=False time_ms=2.012 peak_bytes=2000983  token_len=500078   err=InvalidSignatureError\n[127.0.0.1] ok=True  time_ms=1.591 peak_bytes=2001061  token_len=500117   err=None\n\n[127.0.0.1] ok=True  time_ms=0.065 peak_bytes=2304     token_len=117      err=None\n[127.0.0.1] ok=False time_ms=7.534 peak_bytes=8000983  token_len=2000078  err=InvalidSignatureError\n[127.0.0.1] ok=True  time_ms=6.347 peak_bytes=8001061  token_len=2000117  err=None\n\n[127.0.0.1] ok=True  time_ms=0.066 peak_bytes=2304     token_len=117      err=None\n[127.0.0.1] ok=False time_ms=23.034 peak_bytes=32000983 token_len=8000078 err=InvalidSignatureError\n[127.0.0.1] ok=True  time_ms=22.097 peak_bytes=32001061 token_len=8000117 err=None\n```\n\n**Key takeaways from these results**\n\n* At **8,000,000 chars**, a single invalid-signature request still causes:\n\n  * **~23 ms** server work\n  * **~32 MB** peak allocations\n  * returns **401** (invalid signature) → attacker does not need key.\n\n---\n\n## PoC # 2 - Localhost network client\n\n**File:** [client_localhost.py](https://github.com/user-attachments/files/26132757/client_localhost.py)\n**Purpose:** generates baseline + (invalid signature) + (valid signature) tokens and sends them over HTTP to localhost server.\n\n### Results (client output)\n\n#### payload-chars = 500,000\n\n```text\n=== BASELINE (valid b64=false token) ===\nHTTP: 200\nclient_wall_ms: 6.3499...\nserver_time_ms: 0.10197...\nserver_peak_bytes: 2624\n\n=== ATTACK (INVALID signature - attacker needs no key) ===\nHTTP: 401\nclient_wall_ms: 4.1010...\nserver_time_ms: 2.01217...\nserver_peak_bytes: 2000983\nerror: InvalidSignatureError\n\n=== ATTACK (VALID signature - accepted path still wastes) ===\nHTTP: 200\nclient_wall_ms: 3.6586...\nserver_time_ms: 1.59092...\nserver_peak_bytes: 2001061\n```\n\n#### payload-chars = 2,000,000\n\n```text\n=== BASELINE ===\nHTTP: 200\nserver_time_ms: 0.06527...\nserver_peak_bytes: 2304\n\n=== ATTACK (INVALID signature) ===\nHTTP: 401\nserver_time_ms: 7.53430...\nserver_peak_bytes: 8000983\n\n=== ATTACK (VALID signature) ===\nHTTP: 200\nserver_time_ms: 6.34682...\nserver_peak_bytes: 8001061\n```\n\n#### payload-chars = 8,000,000\n\n```text\n=== BASELINE ===\nHTTP: 200\nserver_time_ms: 0.06573...\nserver_peak_bytes: 2304\n\n=== ATTACK (INVALID signature) ===\nHTTP: 401\nserver_time_ms: 23.03403...\nserver_peak_bytes: 32000983\n\n=== ATTACK (VALID signature) ===\nHTTP: 200\nserver_time_ms: 22.09702...\nserver_peak_bytes: 32001061\n```\n\n**Why this is strong evidence**\n\n* The server clearly does heavy work **before** rejecting invalid signatures.\n* The “valid signature” case shows even accepted requests waste resources due to unused payload segment.\n\n---\n\n## PoC # 3 - Localhost flood / burst concurrency\n\n**File:** [flood_localhost.py](https://github.com/user-attachments/files/26132760/flood_localhost.py)\n**Purpose:** sends **N concurrent** invalid-signature requests over HTTP to demonstrate queueing/worker starvation.\n\n### Results (your run: 20 concurrent @ 8,000,000 chars)\n\n```text\ntotal_wall_ms: 1374.5405770000616\n\n(16, 401, 1156.4504789998864, 21.350951999920653, 32000983, 'InvalidSignatureError')\n(19, 401, 1151.2852699997893, 21.208721999755653, 32000983, 'InvalidSignatureError')\n(18, 401, 1102.7211239997996, 21.685218999664357, 32000983, 'InvalidSignatureError')\n(13, 401, 1102.0718189997751, 21.26572200040755, 32000983, 'InvalidSignatureError')\n(11, 401, 1095.9345460000804, 20.586017000368884, 32000983, 'InvalidSignatureError')\n(17, 401, 1085.2552810001725, 22.893039000337012, 32000983, 'InvalidSignatureError')\n(10, 401, 1078.3629560000918, 22.737160999895423, 32000983, 'InvalidSignatureError')\n(7,  401, 1048.2011740000416, 22.476282000297942, 32000983, 'InvalidSignatureError')\n(8,  401, 378.93017700025666, 21.377330999712285, 32000983, 'InvalidSignatureError')\n(1,  401, 281.45106800002395, 21.34223099983501, 32000983, 'InvalidSignatureError')\n```\n\n**Interpretation**\n\n* Each request still costs ~**20–23 ms** server processing and **~32 MB** peak allocations.\n* But client-observed latency rises up to **~1.15 seconds** because requests queue behind each other → clear worker starvation/HoL blocking.\n* All were rejected with **401 InvalidSignatureError** → still unauthenticated.\n\n---\n\n# Fix \n\n### Goal\n\nPrevent unbounded resource consumption from an attacker-controlled payload segment that is unused in `b64=false` detached flow.\n\n### Minimal change strategy\n\nIn `_load()` (or by refactoring parse order), **do not Base64-decode `payload_segment` until after you know whether `b64=false` applies**.\n\nTwo safe options:\n\n1. **Reject non-empty payload segment when `b64=false`**\n\n   * Parse header first\n   * If `b64` is false and `payload_segment` is non-empty → raise `DecodeError` *before* decoding\n   * Then verification uses `detached_payload` only\n\n2. **Skip decoding payload segment entirely when `b64=false`**\n\n   * Keep payload segment as raw bytes or empty\n   * Use detached payload for signing input\n\nThis aligns with the idea that detached payload is the trusted payload input for verification; the compact payload segment should not become a resource amplification vector.\n\n(Implementation context: the current decode order and unconditional `base64url_decode(payload_segment)` are visible in the file and line region around `_load()` and `decode_complete()` ([GitHub][1]).)\n\n---\n\n# Workarounds\n\n* Enforce strict **max token length** at the HTTP boundary (proxy/gateway).\n* Apply rate limiting on verification endpoints.\n* If detached JWS (`b64=false`) is not needed in your app, reject tokens where header includes `\"b64\": false`.","status":"published","published":"2026-06-15T19:29:12Z","modified":"2026-09-10T03:50:50.801195449Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:46:58.401149Z"},{"id":"PYSEC-2026-178","description":"PyJWT is a JSON Web Token implementation in Python. From 2.8.0 to 2.12.1, when verifying detached JWS tokens using the unencoded-payload option (\"b64\": false, RFC 7797), PyJWT performs Base64URL decoding of the compact-serialization payload segment before enforcing the detached-payload rules. For b64=false, PyJWT later discards that decoded payload and replaces it with the caller-provided detached_payload. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces CPU work + memory allocations even if the signature is invalid. This creates an unauthenticated DoS vector against any endpoint that verifies detached JWS using PyJWT. This vulnerability is fixed in 2.13.0.","status":"published","published":"2026-05-28T16:16:29.533Z","modified":"2026-06-02T12:15:10.739334903Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"updated_at":"2026-11-02T15:52:55.06457Z"},{"id":"UBUNTU-CVE-2026-48525","description":"PyJWT is a JSON Web Token implementation in Python. From 2.8.0 to 2.12.1, when verifying detached JWS tokens using the unencoded-payload option (\"b64\": false, RFC 7797), PyJWT performs Base64URL decoding of the compact-serialization payload segment before enforcing the detached-payload rules. For b64=false, PyJWT later discards that decoded payload and replaces it with the caller-provided detached_payload. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces CPU work + memory allocations even if the signature is invalid. This creates an unauthenticated DoS vector against any endpoint that verifies detached JWS using PyJWT. This vulnerability is fixed in 2.13.0.","status":"published","published":"2026-05-28T16:16:00Z","modified":"2026-09-28T11:30:27.845073567Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"updated_at":"2026-11-02T16:56:33.391918Z"},{"id":"AZL-93042","title":"CVE-2026-48525 affecting package python-jwt for versions less than 2.13.0-1","description":"PyJWT is a JSON Web Token implementation in Python. From 2.8.0 to 2.12.1, when verifying detached JWS tokens using the unencoded-payload option (\"b64\": false, RFC 7797), PyJWT performs Base64URL decoding of the compact-serialization payload segment before enforcing the detached-payload rules. For b64=false, PyJWT later discards that decoded payload and replaces it with the caller-provided detached_payload. In practice, this turns the middle segment into an attacker-controlled “work amplifier”: a remote client can supply an arbitrarily large Base64URL payload segment that forces CPU work + memory allocations even if the signature is invalid. This creates an unauthenticated DoS vector against any endpoint that verifies detached JWS using PyJWT. This vulnerability is fixed in 2.13.0.","status":"published","published":"2026-05-28T16:16:29Z","modified":"2026-08-28T17:56:50.95522Z","updated_at":"2026-11-02T15:28:11.723718Z"},{"id":"CLSA-2026-1783299703","title":"TuxCare security update for pyjwt (4 CVEs)","description":"TuxCare rebuilt pyjwt to address 4 CVEs. Fixed in 2.10.1.post2+tuxcare.","status":"published","published":"2026-08-04T14:52:42Z","modified":"2026-10-15T09:48:05.500671074Z","updated_at":"2026-11-02T15:43:45.794021Z"},{"id":"CLSA-2026-1787038724","title":"TuxCare security update for pyjwt (3 CVEs)","description":"TuxCare rebuilt pyjwt to address 3 CVEs. Fixed in 1.7.1.post3+tuxcare.","status":"published","published":"2026-08-18T07:38:44Z","modified":"2026-10-15T09:48:05.669987837Z","updated_at":"2026-11-02T15:43:47.20725Z"},{"id":"openSUSE-SU-2026:11024-1","title":"python311-PyJWT-2.13.0-1.1 on GA media","description":"These are all security issues fixed in the python311-PyJWT-2.13.0-1.1 package on the GA media of openSUSE Tumbleweed.","status":"published","published":"2026-06-13T00:00:00Z","modified":"2026-06-14T14:00:06.198800158Z","updated_at":"2026-11-02T16:58:38.95195Z"},{"id":"openSUSE-SU-2026:21095-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues\n\n- CVE-2026-48522: `PyJWKClient` passes URI arguments directly to `urllib.request.urlopen()` and allows for SSRF and\n  token forgery (bsc#1266798).\n- CVE-2026-48523: verifier-side algorithm allow-list bypass when `jwt.decode()` or `jwt.decode_complete()` are called\n  with a PyJWK key (bsc#1266799).\n- CVE-2026-48524: unlimited processing of JWTs with unknown kid values by `PyJWKClient.get_signing_key()` leads to\n  unbounded JWKS endpoint requests and DoS (bsc#1266800).\n- CVE-2026-48525: unbounded Base64URL decoding of unused payload segment in `b64=false` detached JWS allows for DoS\n  (bsc#1266801).\n- CVE-2026-48526: no validation of use of JSON Web Keys in HMAC algorithm when decoding JSON Web Tokens allows for\n  forged HS256 tokens (bsc#1266802).\n","status":"published","published":"2026-06-19T05:43:05Z","modified":"2026-06-30T18:24:40.576597716Z","updated_at":"2026-11-02T16:58:41.885092Z"},{"id":"ROOT-APP-PYPI-CVE-2026-48525","title":"CVE-2026-48525 in PyJWT - Patched by Root","description":"Root has patched CVE-2026-48525 in the PyJWT package for Root:PyPI. Multiple fixed versions available.","status":"published","published":"2026-10-28T09:09:40Z","modified":"2026-10-28T10:15:02.818047324Z","updated_at":"2026-11-02T15:53:28.259095Z"},{"id":"SUSE-SU-2026:22138-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues\n\n- CVE-2026-48522: `PyJWKClient` passes URI arguments directly to `urllib.request.urlopen()` and allows for SSRF and\n  token forgery (bsc#1266798).\n- CVE-2026-48523: verifier-side algorithm allow-list bypass when `jwt.decode()` or `jwt.decode_complete()` are called\n  with a PyJWK key (bsc#1266799).\n- CVE-2026-48524: unlimited processing of JWTs with unknown kid values by `PyJWKClient.get_signing_key()` leads to\n  unbounded JWKS endpoint requests and DoS (bsc#1266800).\n- CVE-2026-48525: unbounded Base64URL decoding of unused payload segment in `b64=false` detached JWS allows for DoS\n  (bsc#1266801).\n- CVE-2026-48526: no validation of use of JSON Web Keys in HMAC algorithm when decoding JSON Web Tokens allows for\n  forged HS256 tokens (bsc#1266802).\n","status":"published","published":"2026-06-16T09:09:33Z","modified":"2026-06-18T18:24:17.888913553Z","updated_at":"2026-11-02T15:54:02.654057Z"},{"id":"SUSE-SU-2026:22170-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues\n\n- CVE-2026-48522: `PyJWKClient` passes URI arguments directly to `urllib.request.urlopen()` and allows for SSRF and\n  token forgery (bsc#1266798).\n- CVE-2026-48523: verifier-side algorithm allow-list bypass when `jwt.decode()` or `jwt.decode_complete()` are called\n  with a PyJWK key (bsc#1266799).\n- CVE-2026-48524: unlimited processing of JWTs with unknown kid values by `PyJWKClient.get_signing_key()` leads to\n  unbounded JWKS endpoint requests and DoS (bsc#1266800).\n- CVE-2026-48525: unbounded Base64URL decoding of unused payload segment in `b64=false` detached JWS allows for DoS\n  (bsc#1266801).\n- CVE-2026-48526: no validation of use of JSON Web Keys in HMAC algorithm when decoding JSON Web Tokens allows for\n  forged HS256 tokens (bsc#1266802).\n","status":"published","published":"2026-06-19T05:44:26Z","modified":"2026-06-24T18:24:17.973821173Z","updated_at":"2026-11-02T15:54:02.654057Z"},{"id":"SUSE-SU-2026:22220-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues\n\n- CVE-2026-48522: `PyJWKClient` passes URI arguments directly to `urllib.request.urlopen()` and allows for SSRF and\n  token forgery (bsc#1266798).\n- CVE-2026-48523: verifier-side algorithm allow-list bypass when `jwt.decode()` or `jwt.decode_complete()` are called\n  with a PyJWK key (bsc#1266799).\n- CVE-2026-48524: unlimited processing of JWTs with unknown kid values by `PyJWKClient.get_signing_key()` leads to\n  unbounded JWKS endpoint requests and DoS (bsc#1266800).\n- CVE-2026-48525: unbounded Base64URL decoding of unused payload segment in `b64=false` detached JWS allows for DoS\n  (bsc#1266801).\n- CVE-2026-48526: no validation of use of JSON Web Keys in HMAC algorithm when decoding JSON Web Tokens allows for\n  forged HS256 tokens (bsc#1266802).\n","status":"published","published":"2026-06-19T05:44:26Z","modified":"2026-06-24T18:24:19.894935573Z","updated_at":"2026-11-02T15:54:02.654057Z"},{"id":"SUSE-SU-2026:22238-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues\n\n- CVE-2026-48522: `PyJWKClient` passes URI arguments directly to `urllib.request.urlopen()` and allows for SSRF and\n  token forgery (bsc#1266798).\n- CVE-2026-48523: verifier-side algorithm allow-list bypass when `jwt.decode()` or `jwt.decode_complete()` are called\n  with a PyJWK key (bsc#1266799).\n- CVE-2026-48524: unlimited processing of JWTs with unknown kid values by `PyJWKClient.get_signing_key()` leads to\n  unbounded JWKS endpoint requests and DoS (bsc#1266800).\n- CVE-2026-48525: unbounded Base64URL decoding of unused payload segment in `b64=false` detached JWS allows for DoS\n  (bsc#1266801).\n- CVE-2026-48526: no validation of use of JSON Web Keys in HMAC algorithm when decoding JSON Web Tokens allows for\n  forged HS256 tokens (bsc#1266802).\n","status":"published","published":"2026-06-16T08:55:32Z","modified":"2026-06-26T18:24:08.739071893Z","updated_at":"2026-11-02T15:54:02.654057Z"},{"id":"SUSE-SU-2026:2626-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues\n\n- CVE-2026-48522: `PyJWKClient` passes URI arguments directly to `urllib.request.urlopen()` and allows for SSRF and\n  token forgery (bsc#1266798).\n- CVE-2026-48523: verifier-side algorithm allow-list bypass when `jwt.decode()` or `jwt.decode_complete()` are called\n  with a PyJWK key (bsc#1266799).\n- CVE-2026-48524: unlimited processing of JWTs with unknown kid values by `PyJWKClient.get_signing_key()` leads to\n  unbounded JWKS endpoint requests and DoS (bsc#1266800).\n- CVE-2026-48525: unbounded Base64URL decoding of unused payload segment in `b64=false` detached JWS allows for DoS\n  (bsc#1266801).\n- CVE-2026-48526: no validation of use of JSON Web Keys in HMAC algorithm when decoding JSON Web Tokens allows for\n  forged HS256 tokens (bsc#1266802).\n","status":"published","published":"2026-06-25T08:11:34Z","modified":"2026-06-26T08:15:09.085476812Z","updated_at":"2026-11-02T15:54:04.519245Z"},{"id":"SUSE-SU-2026:2627-1","title":"Security update for python-PyJWT","description":"This update for python-PyJWT fixes the following issues\n\n- CVE-2026-48522: `PyJWKClient` passes URI arguments directly to `urllib.request.urlopen()` and allows for SSRF and\n  token forgery (bsc#1266798).\n- CVE-2026-48523: verifier-side algorithm allow-list bypass when `jwt.decode()` or `jwt.decode_complete()` are called\n  with a PyJWK key (bsc#1266799).\n- CVE-2026-48525: unbounded Base64URL decoding of unused payload segment in `b64=false` detached JWS allows for DoS\n  (bsc#1266801).\n- CVE-2026-48526: no validation of use of JSON Web Keys in HMAC algorithm when decoding JSON Web Tokens allows for\n  forged HS256 tokens (bsc#1266802).\n","status":"published","published":"2026-06-25T08:13:14Z","modified":"2026-06-26T08:15:07.639218361Z","updated_at":"2026-11-02T15:54:04.519245Z"},{"id":"USN-8823-1","title":"pyjwt vulnerabilities","description":"It was discovered that PyJWT incorrectly handled certain URIs when using\nPyJWKClient. A remote attacker could possibly use this issue to perform\nserver-side request forgery (SSRF) or expose sensitive local files.\nThis issue only affected Ubuntu 22.04 LTS, Ubuntu 24.04 LTS and Ubuntu\n26.04 LTS. (CVE-2026-48522)\n\nIt was discovered that PyJWT incorrectly verified cryptographic signatures\nwhen decoding tokens with PyJWK keys. A remote attacker could possibly\nuse this issue to bypass signature verification and forge valid JSON Web\nTokens. This issue only affected Ubuntu 26.04 LTS. (CVE-2026-48523)\n\nIt was discovered that PyJWT incorrectly handled unknown key identifiers\nin PyJWKClient. A remote attacker could possibly use this issue to cause\nPyJWT to make excessive network requests, resulting in a denial of service.\nThis issue only affected Ubuntu 24.04 LTS and Ubuntu 26.04 LTS.\n(CVE-2026-48524)\n\nIt was discovered that PyJWT incorrectly handled payload decoding during\ndetached JWS token verification. A remote attacker could possibly use\nthis issue to cause PyJWT to consume excessive resources, resulting in\na denial of service. This issue only affected Ubuntu 24.04 LTS and\nUbuntu 26.04 LTS. (CVE-2026-48525)\n\nIt was discovered that PyJWT incorrectly validated JSON Web Keys when\ndecoding tokens with both HMAC and asymmetric algorithms enabled. A remote\nattacker could possibly use this issue to forge valid tokens, resulting\nin an authentication bypass. (CVE-2026-48526)","status":"published","published":"2026-09-25T07:49:33Z","modified":"2026-09-28T11:42:45.323412495Z","updated_at":"2026-11-02T16:58:26.907654Z"}]}}
