PHP2Shell: Read our blog on the critical RCE vulnerabilities affecting 70% of the Internet
Vulnerability DatabaseCVE-2026-103001

CVE-2026-103001: 
CBL Mariner vulnerability analysis and mitigation

Overview

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).

Technical details

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).

Impact

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).

Exploitability

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).

Exploitation steps

  1. Identify a vulnerable target: Locate an application using PyJWT versions 2.11.0–2.13.0 that reuses a mutable options dictionary (e.g., a module-level constant, config object, or instance attribute) across multiple jwt.decode() or jwt.decode_complete() calls with different verify_signature intents.
  2. Obtain or craft a signed JWT with invalid claims: Create or obtain a validly signed token (using the application's known or leaked secret) with invalid registered claims — for example, an expired exp timestamp, a wrong aud, iss, sub, or jti value.
  3. Trigger the first decode with 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.
  4. Trigger the second decode with 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.
  5. Achieve authentication bypass: The decode succeeds without raising 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).

Indicators of compromise

  • Logs: Application logs showing successful JWT decode operations for tokens with past 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.
  • Application Behavior: Absence of jwt.ExpiredSignatureError, jwt.InvalidAudienceError, jwt.InvalidIssuerError, or similar PyJWT claim exceptions in application error logs for tokens that should have triggered them.
  • Code/Configuration Audit: Presence of a shared, module-level, or instance-level mutable options dict (e.g., 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.
  • Runtime Inspection: If debug logging is enabled, the options dict passed to jwt.decode() containing both "verify_signature": True and "verify_exp": False (or other verify_*: False keys) simultaneously — indicating prior mutation (PyJWT Security Advisory).

Mitigation and workarounds

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).

Community reactions

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).

Additional resources

Linux Distribution fix status

Fix availability across major Linux distributions and their releases.

Debian

Fixed

bookworm

pyjwt

Fixed

sid

pyjwt: 2.14.0-1

Fixed

trixie

pyjwt

Fixed

Ubuntu

Unknown

bionic (esm-infra)

pyjwt

Unknown

devel

pyjwt

Unknown

focal (esm-infra)

pyjwt

Unknown

jammy

pyjwt

Unknown

noble

pyjwt

Unknown

resolute

pyjwt

Unknown

xenial (esm-infra-legacy)

pyjwt

Unknown

RHEL / CentOS

Affected

RHEL 8

python-jwt.src

Affected

RHEL 9

fence-agents.src

Affected

RHEL 10

fence-agents.src

Affected

Source: This report was generated using AI

Related CBL Mariner vulnerabilities:

CVE ID

Severity

Score

Technologies

Component name

CISA KEV exploit

Has fix

Published date

CVE-2026-98383HIGH7.1
  • Linux Kernel logoLinux Kernel
  • kernel-64k-modules-internal
NoYesOct 09, 2026
CVE-2026-98381HIGH7.1
  • Linux Kernel logoLinux Kernel
  • kernel-zfcpdump-modules-partner
NoYesOct 09, 2026
CVE-2026-98382MEDIUM5.1
  • Linux Kernel logoLinux Kernel
  • kernel-64k-debug
NoYesOct 09, 2026
CVE-2026-98380MEDIUM5.1
  • Linux Kernel logoLinux Kernel
  • linux-nvidia-tegra
NoYesOct 09, 2026
CVE-2026-98384LOW3.3
  • Linux Kernel logoLinux Kernel
  • linux-drivers-sound
NoYesOct 09, 2026

Free Vulnerability Assessment

Benchmark your Cloud Security Posture

Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.

Request assessment

Get a personalized demo

Ready to see Wiz in action?

"Best User Experience I have ever seen, provides full visibility to cloud workloads."
David EstlickCISO
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
Adam FletcherChief Security Officer
"We know that if Wiz identifies something as critical, it actually is."
Greg PoniatowskiHead of Threat and Vulnerability Management