
Cloud Vulnerability DB
A community-led vulnerabilities database
CVE-2026-103001 is a claims-verification bypass vulnerability in PyJWT, a Python implementation of JSON Web Token standards. The flaw exists in PyJWT._merge_options(), which silently mutates a caller-supplied mutable options dictionary when verify_signature is False, causing subsequent decode() calls that re-enable signature verification to inherit disabled claim checks. Affected versions are PyJWT 2.11.0 through 2.13.0 (inclusive); applications that create a fresh options mapping per call are not affected. The vulnerability was first reported on September 8, 2026, and published to the GitHub Advisory Database on September 30, 2026. It carries a CVSS v3.1 base score of 6.5 (Medium) (GitHub Advisory, Red Hat Bugzilla).
The root cause is classified as CWE-471 (Modification of Assumed-Immutable Data / MAID) and CWE-358 (Improperly Implemented Security Check for Standard). In jwt/api_jwt.py, the _merge_options() method directly writes verify_exp, verify_nbf, verify_iat, verify_aud, verify_iss, verify_sub, and verify_jti keys (all set to False) onto the caller's dict object when verify_signature is falsy — without first making a copy. This is a regression introduced by PR #1045 (merged 2025-03-05), which dropped the options = dict(options or {}) shallow-copy line that had been added in PR #743 (2022) to fix the originally reported issue #679 (2021). An attacker exploits this by supplying a signed JWT with invalid registered claims (e.g., expired, wrong audience/issuer/subject) to an application that reuses the same mutable options dict across two decode() calls — one with verify_signature=False and a subsequent one with verify_signature=True (GitHub Advisory, PyJWT Security Advisory).
Successful exploitation results in an authentication/claims-verification bypass: a signed but claims-invalid token (expired, wrong audience, wrong issuer, wrong subject, or wrong JWT ID) is silently accepted as fully valid without raising any exception, while signature verification itself remains intact. The primary impact is high integrity compromise, as an attacker can reuse or forge tokens with invalid claims to impersonate users, extend session lifetimes beyond expiration, or bypass audience/issuer restrictions — potentially enabling unauthorized access to protected resources. Confidentiality impact is rated low (limited data exposure from bypassed access controls), and availability is unaffected (GitHub Advisory, Red Hat Bugzilla).
A proof-of-concept Python script is publicly available in the GitHub Security Advisory (GHSA-gvp8-978c-rx2q), demonstrating deterministic reproduction of the bypass by reusing an options dict across two jwt.decode() calls. The exploit requires no authentication and no user interaction, but does require the specific application code pattern of reusing a mutable options dict — making it a conditional rather than universal exploit. There is no evidence of in-the-wild exploitation at this time, and no threat actor attribution has been reported. The EPSS score is approximately 0.36% (Feedly data) to 0.241% (GitHub Advisory), placing it in a low exploitation-probability tier. The vulnerability is not currently listed in the CISA KEV catalog (GitHub Advisory, PyJWT Security Advisory).
jwt.decode() or jwt.decode_complete() calls with different verify_signature intents.exp timestamp, a wrong aud, iss, sub, or jti value.verify_signature=False: Submit the token to the application endpoint that performs an "insecure peek" decode (e.g., jwt.decode(token, options={"verify_signature": False})). This causes _merge_options() to mutate the shared options dict, adding verify_exp: False, verify_nbf: False, verify_iat: False, verify_aud: False, verify_iss: False, verify_sub: False, and verify_jti: False directly onto the caller's dict object.verify_signature=True: Submit the same invalid token to the application endpoint that performs full verification (e.g., jwt.decode(token, secret, algorithms=["HS256"], options=SHARED_OPTIONS)). Because the shared dict already contains the verify_exp: False (and other) keys from step 3, _merge_options() does not re-apply defaults — the claim checks remain disabled.ExpiredSignatureError (or equivalent claim errors), and the application accepts the invalid token as fully verified. The attacker gains access as the token's claimed identity, bypassing expiration and other claim-based access controls (PyJWT Security Advisory, GitHub Advisory).exp timestamps (more than a few seconds in the past), or tokens with mismatched aud, iss, sub, or jti values that should have been rejected.jwt.ExpiredSignatureError, jwt.InvalidAudienceError, jwt.InvalidIssuerError, or similar PyJWT claim exceptions in application error logs for tokens that should have triggered them.DECODE_OPTIONS = {"verify_signature": False}) that is reused across multiple jwt.decode() calls in application source code running PyJWT 2.11.0–2.13.0.jwt.decode() containing both "verify_signature": True and "verify_exp": False (or other verify_*: False keys) simultaneously — indicating prior mutation (PyJWT Security Advisory).The PyJWT maintainers have prepared a fix (commit 0c87c8c) that restores a shallow copy of the options mapping inside _merge_options() before applying defaults, preventing mutation of the caller's dict. Users should upgrade to a PyJWT version newer than 2.13.0 once a patched release is published. As an immediate workaround, applications should ensure they create a fresh options dictionary for every decode() or decode_complete() call rather than reusing a shared mutable mapping — for example, by passing options=dict(SHARED_OPTIONS) or constructing a new dict literal each time. Using an immutable mapping type (e.g., types.MappingProxyType) for the options object will also cause PyJWT to raise a TypeError on mutation, surfacing the bug rather than silently bypassing claims (PyJWT Commit, GitHub Advisory).
The PyJWT maintainers confirmed and reproduced the reported behavior on PyJWT 2.13.0, characterizing it as "a conditional claim-verification bypass, not a signature-forgery issue," and prepared a narrow fix restoring the shallow copy in _merge_options(). The advisory notes this is a regression of a previously fixed issue (#679, originally reported in 2021 and fixed in 2022), reintroduced by a typing/logic refactor PR (#1045) merged in March 2025. Red Hat tracked the issue via Bugzilla (Bug 2544369) and SUSE issued a security update (SUSE-SU-2026:24126-1), indicating broad downstream distribution attention. Coverage appeared across vulnerability aggregators and security news outlets shortly after disclosure (PyJWT Security Advisory, Red Hat Bugzilla).
Fix availability across major Linux distributions and their releases.
bionic (esm-infra)
pyjwt
devel
pyjwt
focal (esm-infra)
pyjwt
jammy
pyjwt
noble
pyjwt
resolute
pyjwt
xenial (esm-infra-legacy)
pyjwt
Source: This report was generated using AI
Free Vulnerability Assessment
Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.
Get a personalized demo
"Best User Experience I have ever seen, provides full visibility to cloud workloads."
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
"We know that if Wiz identifies something as critical, it actually is."