Action not permitted
Modal body text goes here.
Modal Title
Modal Body
CVE-2026-103001 (GCVE-0-2026-103001)
Vulnerability from cvelistv5 – Published: 2026-09-30 21:15 – Updated: 2026-09-30 21:17- CWE-471 - Modification of Assumed-Immutable Data (MAID)
| URL | Tags |
|---|---|
| https://github.com/jpadilla/pyjwt/security/adviso… | x_refsource_CONFIRM |
| https://github.com/jpadilla/pyjwt/issues/679 | x_refsource_MISC |
| https://github.com/jpadilla/pyjwt/commit/0c87c8c8… | x_refsource_MISC |
{
"containers": {
"cna": {
"affected": [
{
"product": "pyjwt",
"vendor": "jpadilla",
"versions": [
{
"status": "affected",
"version": "\u003e= 2.11.0, \u003c= 2.13.0"
}
]
}
],
"descriptions": [
{
"lang": "en",
"value": "PyJWT is a Python implementation of JSON Web Token standards. From 2.11.0 through 2.13.0, PyJWT\u0027s PyJWT._merge_options() method can modify a caller-supplied mutable options mapping when verify_signature is false. If an application reuses that same mapping for a later decode() or decode_complete() call and changes verify_signature to true, the mapping can retain false values for expiration, not-before, issued-at, audience, issuer, subject, and JWT ID checks. A signed token with invalid registered claims can then be accepted without disabling signature verification, but applications that create a fresh options mapping for each call are not affected."
}
],
"metrics": [
{
"cvssV3_1": {
"attackComplexity": "HIGH",
"attackVector": "NETWORK",
"availabilityImpact": "NONE",
"baseScore": 6.5,
"baseSeverity": "MEDIUM",
"confidentialityImpact": "LOW",
"integrityImpact": "HIGH",
"privilegesRequired": "NONE",
"scope": "UNCHANGED",
"userInteraction": "NONE",
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N",
"version": "3.1"
}
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-471",
"description": "CWE-471: Modification of Assumed-Immutable Data (MAID)",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-09-30T21:17:58.116Z",
"orgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"shortName": "GitHub_M"
},
"references": [
{
"name": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-gvp8-978c-rx2q",
"tags": [
"x_refsource_CONFIRM"
],
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-gvp8-978c-rx2q"
},
{
"name": "https://github.com/jpadilla/pyjwt/issues/679",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/jpadilla/pyjwt/issues/679"
},
{
"name": "https://github.com/jpadilla/pyjwt/commit/0c87c8c8b1a74cac99ad8115f3050efcb7fbed35",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/jpadilla/pyjwt/commit/0c87c8c8b1a74cac99ad8115f3050efcb7fbed35"
}
],
"source": {
"advisory": "GHSA-gvp8-978c-rx2q",
"discovery": "UNKNOWN"
},
"title": "PyJWT.decode() reintroduces options-dict mutation, enabling silent claim-verification bypass on dict reuse"
}
},
"cveMetadata": {
"assignerOrgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"assignerShortName": "GitHub_M",
"cveId": "CVE-2026-103001",
"datePublished": "2026-09-30T21:15:12.862Z",
"dateReserved": "2026-09-29T20:46:08.335Z",
"dateUpdated": "2026-09-30T21:17:58.116Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2",
"vulnerability-lookup:meta": {
"epss": {
"cve": "CVE-2026-103001",
"date": "2026-10-02",
"epss": "0.00236",
"percentile": "0.13182"
},
"nvd": {
"cve": {
"affected": [
{
"affectedData": [
{
"product": "pyjwt",
"vendor": "jpadilla",
"versions": [
{
"status": "affected",
"version": "\u003e= 2.11.0, \u003c= 2.13.0"
}
]
}
],
"source": "security-advisories@github.com"
}
],
"cveTags": [],
"descriptions": [
{
"lang": "en",
"value": "PyJWT is a Python implementation of JSON Web Token standards. From 2.11.0 through 2.13.0, PyJWT\u0027s PyJWT._merge_options() method can modify a caller-supplied mutable options mapping when verify_signature is false. If an application reuses that same mapping for a later decode() or decode_complete() call and changes verify_signature to true, the mapping can retain false values for expiration, not-before, issued-at, audience, issuer, subject, and JWT ID checks. A signed token with invalid registered claims can then be accepted without disabling signature verification, but applications that create a fresh options mapping for each call are not affected."
}
],
"id": "CVE-2026-103001",
"lastModified": "2026-10-01T02:17:43.350",
"metrics": {
"cvssMetricV31": [
{
"cvssData": {
"attackComplexity": "HIGH",
"attackVector": "NETWORK",
"availabilityImpact": "NONE",
"baseScore": 6.5,
"baseSeverity": "MEDIUM",
"confidentialityImpact": "LOW",
"integrityImpact": "HIGH",
"privilegesRequired": "NONE",
"scope": "UNCHANGED",
"userInteraction": "NONE",
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N",
"version": "3.1"
},
"exploitabilityScore": 2.2,
"impactScore": 4.2,
"source": "security-advisories@github.com",
"type": "Secondary"
}
]
},
"published": "2026-09-30T22:16:33.537",
"references": [
{
"source": "security-advisories@github.com",
"url": "https://github.com/jpadilla/pyjwt/commit/0c87c8c8b1a74cac99ad8115f3050efcb7fbed35"
},
{
"source": "security-advisories@github.com",
"url": "https://github.com/jpadilla/pyjwt/issues/679"
},
{
"source": "security-advisories@github.com",
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-gvp8-978c-rx2q"
}
],
"sourceIdentifier": "security-advisories@github.com",
"vulnStatus": "Awaiting Analysis",
"weaknesses": [
{
"description": [
{
"lang": "en",
"value": "CWE-471"
}
],
"source": "security-advisories@github.com",
"type": "Primary"
}
]
}
},
"redhat_vex": {
"aggregate_severity": "Moderate",
"current_release_date": "2026-10-01T17:34:25+00:00",
"cve": "CVE-2026-103001",
"id": "CVE-2026-103001",
"initial_release_date": "2026-09-30T21:15:12.862000+00:00",
"product_status:known_not_affected": "4",
"source": "Red Hat CSAF VEX",
"status": "final",
"title": "pyjwt: pyjwt: Token validation bypass via options dictionary mutation",
"url": "https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-103001.json",
"version": "3"
},
"suse_vex": {
"aggregate_severity": "moderate",
"current_release_date": "2026-10-01T15:59:15Z",
"cve": "CVE-2026-103001",
"id": "CVE-2026-103001",
"initial_release_date": "2026-10-01T15:59:15Z",
"product_status:known_affected": "13",
"product_status:known_not_affected": "113",
"source": "SUSE CSAF VEX",
"status": "interim",
"title": "SUSE CVE CVE-2026-103001",
"url": "https://ftp.suse.com/pub/projects/security/csaf-vex/cve-2026-103001.json",
"version": "2"
}
}
}
BREW-SNYK-AGENT-SCAN-CVE… (GHSA-GVP8-978C-RX2Q)
Vulnerability from osv_homebrew – Published: 2026-10-01 09:44 – Updated: 2026-10-01 09:44 – Source websiteSummary
PyJWT.decode()/decode_complete() mutates a caller-supplied options dict in place whenever verify_signature is falsy, adding verify_exp/verify_nbf/verify_iat/verify_aud/verify_iss/verify_sub/verify_jti keys directly onto that object. If application code reuses the same options dict across calls (a config object, a module-level constant, a wrapper's self.options), a later call that explicitly sets verify_signature=True for a full, normal verification silently inherits the leftover verify_exp=False etc. from an earlier verify_signature=False call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.
This is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of options), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.
Details
File: jwt/api_jwt.py, PyJWT._merge_options() (called from decode_complete(), which backs both jwt.decode() and jwt.decode_complete()):
def _merge_options(self, options=None):
if options is None:
return self.options
if not options.get("verify_signature", True):
options["verify_exp"] = options.get("verify_exp", False)
options["verify_nbf"] = options.get("verify_nbf", False)
# ...5 more, same pattern -- all written directly onto the caller's dict
return {**self.options, **options}
No dict(options) copy occurs before these writes, unlike the pre-2025-03-05 code (see #743's fix, options = dict(options or {})). Confirmed via gh api repos/jpadilla/pyjwt/commits/29fbfc36 that #1045's diff removes that line. Confirmed still present on current main via a live fetch of jwt/api_jwt.py.
PoC
Two scripts, each run twice (separate processes), identical output both times (except Python object id()), saved locally and available on request:
import jwt, time
INSECURE_OPTIONS = {"verify_signature": False}
secret = "s3cr3t"
expired_token = jwt.encode(
{"exp": int(time.time()) - 3600, "data": "x"}, secret, algorithm="HS256"
)
jwt.decode(expired_token, options=INSECURE_OPTIONS)
print(INSECURE_OPTIONS)
# The dict has been silently mutated with 7 new keys, including verify_exp: False.
INSECURE_OPTIONS["verify_signature"] = True
result = jwt.decode(expired_token, secret, algorithms=["HS256"], options=INSECURE_OPTIONS)
print(result)
# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True
# was explicitly requested on this call.
Reproduced deterministically across 2 independent process runs.
Impact
Preconditions: the calling application must reuse a mutable options dict object across two or more decode()/decode_complete() calls with different intent (e.g. one "insecure peek" call followed by a "fully verified" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh options dict per call.
When triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.
Scope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.
Suggested fix: restore a shallow copy inside _merge_options() itself (rather than relying on a caller-side copy), so every caller of _merge_options is covered:
def _merge_options(self, options=None):
if options is None:
return self.options
options = dict(options) # never mutate the caller's dict
if not options.get("verify_signature", True):
options.setdefault("verify_exp", False)
options.setdefault("verify_nbf", False)
options.setdefault("verify_iat", False)
options.setdefault("verify_aud", False)
options.setdefault("verify_iss", False)
options.setdefault("verify_sub", False)
options.setdefault("verify_jti", False)
return {**self.options, **options}
Found via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.
Maintainer update — 2026-09-08
We reproduced the reported behavior on PyJWT 2.13.0 at source SHA 7144e4534c34810f4525dc4578a32addd8212cff. A caller-supplied mutable options mapping was modified when verify_signature was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.
A narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes decode(), decode_complete(), constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.
The advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "pyjwt",
"resource_purl": "pkg:pypi/pyjwt@2.15.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "snyk-agent-scan",
"purl": "pkg:brew/snyk-agent-scan"
},
"ranges": [
{
"events": [
{
"introduced": "0.4.3"
},
{
"fixed": "0.6.4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/pyjwt@2.15.1",
"name": "pyjwt",
"resource": "pyjwt",
"strategy": "registry",
"subject_version": "2.15.1"
}
]
},
"details": "### Summary\n\n`PyJWT.decode()`/`decode_complete()` mutates a caller-supplied `options` dict in place whenever `verify_signature` is falsy, adding `verify_exp`/`verify_nbf`/`verify_iat`/`verify_aud`/`verify_iss`/`verify_sub`/`verify_jti` keys directly onto that object. If application code reuses the same `options` dict across calls (a config object, a module-level constant, a wrapper\u0027s `self.options`), a later call that explicitly sets `verify_signature=True` for a full, normal verification silently inherits the leftover `verify_exp=False` etc. from an earlier `verify_signature=False` call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.\n\nThis is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of `options`), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.\n\n### Details\n\nFile: `jwt/api_jwt.py`, `PyJWT._merge_options()` (called from `decode_complete()`, which backs both `jwt.decode()` and `jwt.decode_complete()`):\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n if not options.get(\"verify_signature\", True):\n options[\"verify_exp\"] = options.get(\"verify_exp\", False)\n options[\"verify_nbf\"] = options.get(\"verify_nbf\", False)\n # ...5 more, same pattern -- all written directly onto the caller\u0027s dict\n return {**self.options, **options}\n```\n\nNo `dict(options)` copy occurs before these writes, unlike the pre-2025-03-05 code (see #743\u0027s fix, `options = dict(options or {})`). Confirmed via `gh api repos/jpadilla/pyjwt/commits/29fbfc36` that #1045\u0027s diff removes that line. Confirmed still present on current `main` via a live fetch of `jwt/api_jwt.py`.\n\n### PoC\n\nTwo scripts, each run twice (separate processes), identical output both times (except Python object `id()`), saved locally and available on request:\n\n```python\nimport jwt, time\n\nINSECURE_OPTIONS = {\"verify_signature\": False}\n\nsecret = \"s3cr3t\"\nexpired_token = jwt.encode(\n {\"exp\": int(time.time()) - 3600, \"data\": \"x\"}, secret, algorithm=\"HS256\"\n)\n\njwt.decode(expired_token, options=INSECURE_OPTIONS)\nprint(INSECURE_OPTIONS)\n# The dict has been silently mutated with 7 new keys, including verify_exp: False.\n\nINSECURE_OPTIONS[\"verify_signature\"] = True\nresult = jwt.decode(expired_token, secret, algorithms=[\"HS256\"], options=INSECURE_OPTIONS)\nprint(result)\n# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True\n# was explicitly requested on this call.\n```\n\nReproduced deterministically across 2 independent process runs.\n\n### Impact\n\nPreconditions: the calling application must reuse a mutable `options` dict object across two or more `decode()`/`decode_complete()` calls with different intent (e.g. one \"insecure peek\" call followed by a \"fully verified\" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh `options` dict per call.\n\nWhen triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.\n\nScope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.\n\nSuggested fix: restore a shallow copy inside `_merge_options()` itself (rather than relying on a caller-side copy), so every caller of `_merge_options` is covered:\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n options = dict(options) # never mutate the caller\u0027s dict\n if not options.get(\"verify_signature\", True):\n options.setdefault(\"verify_exp\", False)\n options.setdefault(\"verify_nbf\", False)\n options.setdefault(\"verify_iat\", False)\n options.setdefault(\"verify_aud\", False)\n options.setdefault(\"verify_iss\", False)\n options.setdefault(\"verify_sub\", False)\n options.setdefault(\"verify_jti\", False)\n return {**self.options, **options}\n```\n\nFound via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.\n\n## Maintainer update \u2014 2026-09-08\n\nWe reproduced the reported behavior on PyJWT 2.13.0 at source SHA `7144e4534c34810f4525dc4578a32addd8212cff`. A caller-supplied mutable `options` mapping was modified when `verify_signature` was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.\n\nA narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes `decode()`, `decode_complete()`, constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.\n\nThe advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.",
"id": "BREW-snyk-agent-scan-CVE-2026-103001",
"modified": "2026-10-01T09:44:03Z",
"published": "2026-10-01T09:44:03Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-gvp8-978c-rx2q"
},
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/commit/0c87c8c8b1a74cac99ad8115f3050efcb7fbed35"
},
{
"type": "PACKAGE",
"url": "https://github.com/jpadilla/pyjwt"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "PyJWT.decode() reintroduces options-dict mutation, enabling silent claim-verification bypass on dict reuse",
"upstream": [
"GHSA-gvp8-978c-rx2q",
"CVE-2026-103001"
]
}
BREW-STRANDS-AGENTS-SOPS… (GHSA-GVP8-978C-RX2Q)
Vulnerability from osv_homebrew – Published: 2026-10-01 10:13 – Updated: 2026-10-01 10:13 – Source websiteSummary
PyJWT.decode()/decode_complete() mutates a caller-supplied options dict in place whenever verify_signature is falsy, adding verify_exp/verify_nbf/verify_iat/verify_aud/verify_iss/verify_sub/verify_jti keys directly onto that object. If application code reuses the same options dict across calls (a config object, a module-level constant, a wrapper's self.options), a later call that explicitly sets verify_signature=True for a full, normal verification silently inherits the leftover verify_exp=False etc. from an earlier verify_signature=False call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.
This is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of options), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.
Details
File: jwt/api_jwt.py, PyJWT._merge_options() (called from decode_complete(), which backs both jwt.decode() and jwt.decode_complete()):
def _merge_options(self, options=None):
if options is None:
return self.options
if not options.get("verify_signature", True):
options["verify_exp"] = options.get("verify_exp", False)
options["verify_nbf"] = options.get("verify_nbf", False)
# ...5 more, same pattern -- all written directly onto the caller's dict
return {**self.options, **options}
No dict(options) copy occurs before these writes, unlike the pre-2025-03-05 code (see #743's fix, options = dict(options or {})). Confirmed via gh api repos/jpadilla/pyjwt/commits/29fbfc36 that #1045's diff removes that line. Confirmed still present on current main via a live fetch of jwt/api_jwt.py.
PoC
Two scripts, each run twice (separate processes), identical output both times (except Python object id()), saved locally and available on request:
import jwt, time
INSECURE_OPTIONS = {"verify_signature": False}
secret = "s3cr3t"
expired_token = jwt.encode(
{"exp": int(time.time()) - 3600, "data": "x"}, secret, algorithm="HS256"
)
jwt.decode(expired_token, options=INSECURE_OPTIONS)
print(INSECURE_OPTIONS)
# The dict has been silently mutated with 7 new keys, including verify_exp: False.
INSECURE_OPTIONS["verify_signature"] = True
result = jwt.decode(expired_token, secret, algorithms=["HS256"], options=INSECURE_OPTIONS)
print(result)
# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True
# was explicitly requested on this call.
Reproduced deterministically across 2 independent process runs.
Impact
Preconditions: the calling application must reuse a mutable options dict object across two or more decode()/decode_complete() calls with different intent (e.g. one "insecure peek" call followed by a "fully verified" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh options dict per call.
When triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.
Scope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.
Suggested fix: restore a shallow copy inside _merge_options() itself (rather than relying on a caller-side copy), so every caller of _merge_options is covered:
def _merge_options(self, options=None):
if options is None:
return self.options
options = dict(options) # never mutate the caller's dict
if not options.get("verify_signature", True):
options.setdefault("verify_exp", False)
options.setdefault("verify_nbf", False)
options.setdefault("verify_iat", False)
options.setdefault("verify_aud", False)
options.setdefault("verify_iss", False)
options.setdefault("verify_sub", False)
options.setdefault("verify_jti", False)
return {**self.options, **options}
Found via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.
Maintainer update — 2026-09-08
We reproduced the reported behavior on PyJWT 2.13.0 at source SHA 7144e4534c34810f4525dc4578a32addd8212cff. A caller-supplied mutable options mapping was modified when verify_signature was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.
A narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes decode(), decode_complete(), constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.
The advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "pyjwt",
"resource_purl": "pkg:pypi/pyjwt@2.15.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "strands-agents-sops",
"purl": "pkg:brew/strands-agents-sops"
},
"ranges": [
{
"events": [
{
"introduced": "1.0.6"
},
{
"fixed": "1.1.3_2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/pyjwt@2.15.1",
"name": "pyjwt",
"resource": "pyjwt",
"strategy": "registry",
"subject_version": "2.15.1"
}
]
},
"details": "### Summary\n\n`PyJWT.decode()`/`decode_complete()` mutates a caller-supplied `options` dict in place whenever `verify_signature` is falsy, adding `verify_exp`/`verify_nbf`/`verify_iat`/`verify_aud`/`verify_iss`/`verify_sub`/`verify_jti` keys directly onto that object. If application code reuses the same `options` dict across calls (a config object, a module-level constant, a wrapper\u0027s `self.options`), a later call that explicitly sets `verify_signature=True` for a full, normal verification silently inherits the leftover `verify_exp=False` etc. from an earlier `verify_signature=False` call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.\n\nThis is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of `options`), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.\n\n### Details\n\nFile: `jwt/api_jwt.py`, `PyJWT._merge_options()` (called from `decode_complete()`, which backs both `jwt.decode()` and `jwt.decode_complete()`):\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n if not options.get(\"verify_signature\", True):\n options[\"verify_exp\"] = options.get(\"verify_exp\", False)\n options[\"verify_nbf\"] = options.get(\"verify_nbf\", False)\n # ...5 more, same pattern -- all written directly onto the caller\u0027s dict\n return {**self.options, **options}\n```\n\nNo `dict(options)` copy occurs before these writes, unlike the pre-2025-03-05 code (see #743\u0027s fix, `options = dict(options or {})`). Confirmed via `gh api repos/jpadilla/pyjwt/commits/29fbfc36` that #1045\u0027s diff removes that line. Confirmed still present on current `main` via a live fetch of `jwt/api_jwt.py`.\n\n### PoC\n\nTwo scripts, each run twice (separate processes), identical output both times (except Python object `id()`), saved locally and available on request:\n\n```python\nimport jwt, time\n\nINSECURE_OPTIONS = {\"verify_signature\": False}\n\nsecret = \"s3cr3t\"\nexpired_token = jwt.encode(\n {\"exp\": int(time.time()) - 3600, \"data\": \"x\"}, secret, algorithm=\"HS256\"\n)\n\njwt.decode(expired_token, options=INSECURE_OPTIONS)\nprint(INSECURE_OPTIONS)\n# The dict has been silently mutated with 7 new keys, including verify_exp: False.\n\nINSECURE_OPTIONS[\"verify_signature\"] = True\nresult = jwt.decode(expired_token, secret, algorithms=[\"HS256\"], options=INSECURE_OPTIONS)\nprint(result)\n# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True\n# was explicitly requested on this call.\n```\n\nReproduced deterministically across 2 independent process runs.\n\n### Impact\n\nPreconditions: the calling application must reuse a mutable `options` dict object across two or more `decode()`/`decode_complete()` calls with different intent (e.g. one \"insecure peek\" call followed by a \"fully verified\" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh `options` dict per call.\n\nWhen triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.\n\nScope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.\n\nSuggested fix: restore a shallow copy inside `_merge_options()` itself (rather than relying on a caller-side copy), so every caller of `_merge_options` is covered:\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n options = dict(options) # never mutate the caller\u0027s dict\n if not options.get(\"verify_signature\", True):\n options.setdefault(\"verify_exp\", False)\n options.setdefault(\"verify_nbf\", False)\n options.setdefault(\"verify_iat\", False)\n options.setdefault(\"verify_aud\", False)\n options.setdefault(\"verify_iss\", False)\n options.setdefault(\"verify_sub\", False)\n options.setdefault(\"verify_jti\", False)\n return {**self.options, **options}\n```\n\nFound via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.\n\n## Maintainer update \u2014 2026-09-08\n\nWe reproduced the reported behavior on PyJWT 2.13.0 at source SHA `7144e4534c34810f4525dc4578a32addd8212cff`. A caller-supplied mutable `options` mapping was modified when `verify_signature` was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.\n\nA narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes `decode()`, `decode_complete()`, constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.\n\nThe advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.",
"id": "BREW-strands-agents-sops-CVE-2026-103001",
"modified": "2026-10-01T10:13:48Z",
"published": "2026-10-01T10:13:48Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-gvp8-978c-rx2q"
},
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/commit/0c87c8c8b1a74cac99ad8115f3050efcb7fbed35"
},
{
"type": "PACKAGE",
"url": "https://github.com/jpadilla/pyjwt"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "PyJWT.decode() reintroduces options-dict mutation, enabling silent claim-verification bypass on dict reuse",
"upstream": [
"GHSA-gvp8-978c-rx2q",
"CVE-2026-103001"
]
}
BREW-SYSAIDMIN-CVE-2026-103001 (GHSA-GVP8-978C-RX2Q)
Vulnerability from osv_homebrew – Published: 2026-10-01 10:23 – Updated: 2026-10-01 10:23 – Source websiteSummary
PyJWT.decode()/decode_complete() mutates a caller-supplied options dict in place whenever verify_signature is falsy, adding verify_exp/verify_nbf/verify_iat/verify_aud/verify_iss/verify_sub/verify_jti keys directly onto that object. If application code reuses the same options dict across calls (a config object, a module-level constant, a wrapper's self.options), a later call that explicitly sets verify_signature=True for a full, normal verification silently inherits the leftover verify_exp=False etc. from an earlier verify_signature=False call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.
This is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of options), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.
Details
File: jwt/api_jwt.py, PyJWT._merge_options() (called from decode_complete(), which backs both jwt.decode() and jwt.decode_complete()):
def _merge_options(self, options=None):
if options is None:
return self.options
if not options.get("verify_signature", True):
options["verify_exp"] = options.get("verify_exp", False)
options["verify_nbf"] = options.get("verify_nbf", False)
# ...5 more, same pattern -- all written directly onto the caller's dict
return {**self.options, **options}
No dict(options) copy occurs before these writes, unlike the pre-2025-03-05 code (see #743's fix, options = dict(options or {})). Confirmed via gh api repos/jpadilla/pyjwt/commits/29fbfc36 that #1045's diff removes that line. Confirmed still present on current main via a live fetch of jwt/api_jwt.py.
PoC
Two scripts, each run twice (separate processes), identical output both times (except Python object id()), saved locally and available on request:
import jwt, time
INSECURE_OPTIONS = {"verify_signature": False}
secret = "s3cr3t"
expired_token = jwt.encode(
{"exp": int(time.time()) - 3600, "data": "x"}, secret, algorithm="HS256"
)
jwt.decode(expired_token, options=INSECURE_OPTIONS)
print(INSECURE_OPTIONS)
# The dict has been silently mutated with 7 new keys, including verify_exp: False.
INSECURE_OPTIONS["verify_signature"] = True
result = jwt.decode(expired_token, secret, algorithms=["HS256"], options=INSECURE_OPTIONS)
print(result)
# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True
# was explicitly requested on this call.
Reproduced deterministically across 2 independent process runs.
Impact
Preconditions: the calling application must reuse a mutable options dict object across two or more decode()/decode_complete() calls with different intent (e.g. one "insecure peek" call followed by a "fully verified" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh options dict per call.
When triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.
Scope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.
Suggested fix: restore a shallow copy inside _merge_options() itself (rather than relying on a caller-side copy), so every caller of _merge_options is covered:
def _merge_options(self, options=None):
if options is None:
return self.options
options = dict(options) # never mutate the caller's dict
if not options.get("verify_signature", True):
options.setdefault("verify_exp", False)
options.setdefault("verify_nbf", False)
options.setdefault("verify_iat", False)
options.setdefault("verify_aud", False)
options.setdefault("verify_iss", False)
options.setdefault("verify_sub", False)
options.setdefault("verify_jti", False)
return {**self.options, **options}
Found via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.
Maintainer update — 2026-09-08
We reproduced the reported behavior on PyJWT 2.13.0 at source SHA 7144e4534c34810f4525dc4578a32addd8212cff. A caller-supplied mutable options mapping was modified when verify_signature was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.
A narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes decode(), decode_complete(), constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.
The advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "pyjwt",
"resource_purl": "pkg:pypi/pyjwt@2.15.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "sysaidmin",
"purl": "pkg:brew/sysaidmin"
},
"ranges": [
{
"events": [
{
"introduced": "0.2.5_11"
},
{
"fixed": "0.2.5_20"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/pyjwt@2.15.1",
"name": "pyjwt",
"resource": "pyjwt",
"strategy": "registry",
"subject_version": "2.15.1"
}
]
},
"details": "### Summary\n\n`PyJWT.decode()`/`decode_complete()` mutates a caller-supplied `options` dict in place whenever `verify_signature` is falsy, adding `verify_exp`/`verify_nbf`/`verify_iat`/`verify_aud`/`verify_iss`/`verify_sub`/`verify_jti` keys directly onto that object. If application code reuses the same `options` dict across calls (a config object, a module-level constant, a wrapper\u0027s `self.options`), a later call that explicitly sets `verify_signature=True` for a full, normal verification silently inherits the leftover `verify_exp=False` etc. from an earlier `verify_signature=False` call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.\n\nThis is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of `options`), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.\n\n### Details\n\nFile: `jwt/api_jwt.py`, `PyJWT._merge_options()` (called from `decode_complete()`, which backs both `jwt.decode()` and `jwt.decode_complete()`):\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n if not options.get(\"verify_signature\", True):\n options[\"verify_exp\"] = options.get(\"verify_exp\", False)\n options[\"verify_nbf\"] = options.get(\"verify_nbf\", False)\n # ...5 more, same pattern -- all written directly onto the caller\u0027s dict\n return {**self.options, **options}\n```\n\nNo `dict(options)` copy occurs before these writes, unlike the pre-2025-03-05 code (see #743\u0027s fix, `options = dict(options or {})`). Confirmed via `gh api repos/jpadilla/pyjwt/commits/29fbfc36` that #1045\u0027s diff removes that line. Confirmed still present on current `main` via a live fetch of `jwt/api_jwt.py`.\n\n### PoC\n\nTwo scripts, each run twice (separate processes), identical output both times (except Python object `id()`), saved locally and available on request:\n\n```python\nimport jwt, time\n\nINSECURE_OPTIONS = {\"verify_signature\": False}\n\nsecret = \"s3cr3t\"\nexpired_token = jwt.encode(\n {\"exp\": int(time.time()) - 3600, \"data\": \"x\"}, secret, algorithm=\"HS256\"\n)\n\njwt.decode(expired_token, options=INSECURE_OPTIONS)\nprint(INSECURE_OPTIONS)\n# The dict has been silently mutated with 7 new keys, including verify_exp: False.\n\nINSECURE_OPTIONS[\"verify_signature\"] = True\nresult = jwt.decode(expired_token, secret, algorithms=[\"HS256\"], options=INSECURE_OPTIONS)\nprint(result)\n# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True\n# was explicitly requested on this call.\n```\n\nReproduced deterministically across 2 independent process runs.\n\n### Impact\n\nPreconditions: the calling application must reuse a mutable `options` dict object across two or more `decode()`/`decode_complete()` calls with different intent (e.g. one \"insecure peek\" call followed by a \"fully verified\" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh `options` dict per call.\n\nWhen triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.\n\nScope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.\n\nSuggested fix: restore a shallow copy inside `_merge_options()` itself (rather than relying on a caller-side copy), so every caller of `_merge_options` is covered:\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n options = dict(options) # never mutate the caller\u0027s dict\n if not options.get(\"verify_signature\", True):\n options.setdefault(\"verify_exp\", False)\n options.setdefault(\"verify_nbf\", False)\n options.setdefault(\"verify_iat\", False)\n options.setdefault(\"verify_aud\", False)\n options.setdefault(\"verify_iss\", False)\n options.setdefault(\"verify_sub\", False)\n options.setdefault(\"verify_jti\", False)\n return {**self.options, **options}\n```\n\nFound via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.\n\n## Maintainer update \u2014 2026-09-08\n\nWe reproduced the reported behavior on PyJWT 2.13.0 at source SHA `7144e4534c34810f4525dc4578a32addd8212cff`. A caller-supplied mutable `options` mapping was modified when `verify_signature` was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.\n\nA narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes `decode()`, `decode_complete()`, constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.\n\nThe advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.",
"id": "BREW-sysaidmin-CVE-2026-103001",
"modified": "2026-10-01T10:23:58Z",
"published": "2026-10-01T10:23:58Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-gvp8-978c-rx2q"
},
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/commit/0c87c8c8b1a74cac99ad8115f3050efcb7fbed35"
},
{
"type": "PACKAGE",
"url": "https://github.com/jpadilla/pyjwt"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "PyJWT.decode() reintroduces options-dict mutation, enabling silent claim-verification bypass on dict reuse",
"upstream": [
"GHSA-gvp8-978c-rx2q",
"CVE-2026-103001"
]
}
CERTFR-2026-AVI-1249
Vulnerability from certfr_avis - Published: 2026-10-02 - Updated: 2026-10-02
De multiples vulnérabilités ont été découvertes dans les produits VMware. Elles permettent à un attaquant de provoquer un problème de sécurité non spécifié par l'éditeur.
Solutions
Se référer au bulletin de sécurité de l'éditeur pour l'obtention des correctifs (cf. section Documentation).
| Vendor | Product | Description | ||
|---|---|---|---|---|
| VMware | Tanzu | VMware Tanzu pour Valkey on Kubernetes versions antérieures à 3.5.1 | ||
| VMware | Tanzu | VMware Tanzu pour Postgres on Kubernetes versions antérieures à 4.5.2 | ||
| VMware | Spring Cloud Gateway | Spring Cloud Gateway pour Kubernetes versions antérieures à 2.2.17 | ||
| VMware | Tanzu | VMware Tanzu pour Valkey versions antérieures à 8.0.11 | ||
| VMware | Tanzu | VMware Tanzu pour Valkey versions 9.0.x antérieures à 9.0.6 | ||
| VMware | Tanzu | VMware Tanzu pour Valkey versions 8.1.x antérieures à 8.1.10 | ||
| VMware | Tanzu | VMware Tanzu pour Valkey versions 9.1.x antérieures à 9.1.2 | ||
| VMware | Spring Cloud Data Flow | Spring Cloud Data Flow pour Kubernetes versions antérieures à 1.6.16 |
| Title | Publication Time | Tags | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||||||||
{
"$ref": "https://www.cert.ssi.gouv.fr/openapi.json",
"affected_systems": [
{
"description": "VMware Tanzu pour Valkey on Kubernetes versions ant\u00e9rieures \u00e0 3.5.1",
"product": {
"name": "Tanzu",
"vendor": {
"name": "VMware",
"scada": false
}
}
},
{
"description": "VMware Tanzu pour Postgres on Kubernetes versions ant\u00e9rieures \u00e0 4.5.2",
"product": {
"name": "Tanzu",
"vendor": {
"name": "VMware",
"scada": false
}
}
},
{
"description": "Spring Cloud Gateway pour Kubernetes versions ant\u00e9rieures \u00e0 2.2.17",
"product": {
"name": "Spring Cloud Gateway",
"vendor": {
"name": "VMware",
"scada": false
}
}
},
{
"description": "VMware Tanzu pour Valkey versions ant\u00e9rieures \u00e0 8.0.11",
"product": {
"name": "Tanzu",
"vendor": {
"name": "VMware",
"scada": false
}
}
},
{
"description": "VMware Tanzu pour Valkey versions 9.0.x ant\u00e9rieures \u00e0 9.0.6",
"product": {
"name": "Tanzu",
"vendor": {
"name": "VMware",
"scada": false
}
}
},
{
"description": "VMware Tanzu pour Valkey versions 8.1.x ant\u00e9rieures \u00e0 8.1.10",
"product": {
"name": "Tanzu",
"vendor": {
"name": "VMware",
"scada": false
}
}
},
{
"description": "VMware Tanzu pour Valkey versions 9.1.x ant\u00e9rieures \u00e0 9.1.2",
"product": {
"name": "Tanzu",
"vendor": {
"name": "VMware",
"scada": false
}
}
},
{
"description": "Spring Cloud Data Flow pour Kubernetes versions ant\u00e9rieures \u00e0 1.6.16",
"product": {
"name": "Spring Cloud Data Flow",
"vendor": {
"name": "VMware",
"scada": false
}
}
}
],
"affected_systems_content": "",
"content": "## Solutions\n\nSe r\u00e9f\u00e9rer au bulletin de s\u00e9curit\u00e9 de l\u0027\u00e9diteur pour l\u0027obtention des correctifs (cf. section Documentation).",
"cves": [
{
"name": "CVE-2026-75595",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-75595"
},
{
"name": "CVE-2026-53910",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-53910"
},
{
"name": "CVE-2026-56404",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56404"
},
{
"name": "CVE-2026-58055",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-58055"
},
{
"name": "CVE-2025-61730",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61730"
},
{
"name": "CVE-2026-54369",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-54369"
},
{
"name": "CVE-2025-58183",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-58183"
},
{
"name": "CVE-2026-11940",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-11940"
},
{
"name": "CVE-2026-56862",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56862"
},
{
"name": "CVE-2026-53613",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-53613"
},
{
"name": "CVE-2026-102271",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102271"
},
{
"name": "CVE-2026-42507",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42507"
},
{
"name": "CVE-2026-33818",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-33818"
},
{
"name": "CVE-2026-39830",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39830"
},
{
"name": "CVE-2026-63384",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63384"
},
{
"name": "CVE-2026-34180",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-34180"
},
{
"name": "CVE-2026-68763",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-68763"
},
{
"name": "CVE-2026-33186",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-33186"
},
{
"name": "CVE-2026-39826",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39826"
},
{
"name": "CVE-2026-80489",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-80489"
},
{
"name": "CVE-2026-42766",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42766"
},
{
"name": "CVE-2026-13346",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-13346"
},
{
"name": "CVE-2026-56846",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56846"
},
{
"name": "CVE-2026-9076",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-9076"
},
{
"name": "CVE-2025-22872",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-22872"
},
{
"name": "CVE-2026-15310",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-15310"
},
{
"name": "CVE-2026-42508",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42508"
},
{
"name": "CVE-2026-84445",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-84445"
},
{
"name": "CVE-2026-1965",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-1965"
},
{
"name": "CVE-2026-34181",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-34181"
},
{
"name": "CVE-2026-97687",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-97687"
},
{
"name": "CVE-2026-81870",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-81870"
},
{
"name": "CVE-2026-58013",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-58013"
},
{
"name": "CVE-2026-32288",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-32288"
},
{
"name": "CVE-2025-47907",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-47907"
},
{
"name": "CVE-2026-59885",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-59885"
},
{
"name": "CVE-2026-42770",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42770"
},
{
"name": "CVE-2026-91776",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-91776"
},
{
"name": "CVE-2026-27138",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-27138"
},
{
"name": "CVE-2026-39822",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39822"
},
{
"name": "CVE-2026-39833",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39833"
},
{
"name": "CVE-2026-3783",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-3783"
},
{
"name": "CVE-2026-13221",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-13221"
},
{
"name": "CVE-2026-33814",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-33814"
},
{
"name": "CVE-2026-27456",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-27456"
},
{
"name": "CVE-2026-63074",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63074"
},
{
"name": "CVE-2026-68569",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-68569"
},
{
"name": "CVE-2026-59084",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-59084"
},
{
"name": "CVE-2025-58185",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-58185"
},
{
"name": "CVE-2026-65183",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-65183"
},
{
"name": "CVE-2026-14257",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-14257"
},
{
"name": "CVE-2025-61731",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61731"
},
{
"name": "CVE-2026-58015",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-58015"
},
{
"name": "CVE-2026-27137",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-27137"
},
{
"name": "CVE-2026-27143",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-27143"
},
{
"name": "CVE-2026-46600",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-46600"
},
{
"name": "CVE-2026-69152",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-69152"
},
{
"name": "CVE-2026-45445",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-45445"
},
{
"name": "CVE-2026-68525",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-68525"
},
{
"name": "CVE-2026-18508",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-18508"
},
{
"name": "CVE-2026-39832",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39832"
},
{
"name": "CVE-2026-39829",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39829"
},
{
"name": "CVE-2026-56392",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56392"
},
{
"name": "CVE-2026-65637",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-65637"
},
{
"name": "CVE-2025-15367",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-15367"
},
{
"name": "CVE-2026-58014",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-58014"
},
{
"name": "CVE-2024-45341",
"url": "https://www.cve.org/CVERecord?id=CVE-2024-45341"
},
{
"name": "CVE-2026-13595",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-13595"
},
{
"name": "CVE-2026-6357",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-6357"
},
{
"name": "CVE-2026-54515",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-54515"
},
{
"name": "CVE-2026-63374",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63374"
},
{
"name": "CVE-2026-13757",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-13757"
},
{
"name": "CVE-2026-15308",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-15308"
},
{
"name": "CVE-2026-27145",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-27145"
},
{
"name": "CVE-2026-68497",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-68497"
},
{
"name": "CVE-2026-39834",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39834"
},
{
"name": "CVE-2026-63073",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63073"
},
{
"name": "CVE-2022-40897",
"url": "https://www.cve.org/CVERecord?id=CVE-2022-40897"
},
{
"name": "CVE-2026-46595",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-46595"
},
{
"name": "CVE-2026-41992",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-41992"
},
{
"name": "CVE-2026-24051",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-24051"
},
{
"name": "CVE-2026-102265",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102265"
},
{
"name": "CVE-2026-7383",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-7383"
},
{
"name": "CVE-2026-39825",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39825"
},
{
"name": "CVE-2026-39821",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39821"
},
{
"name": "CVE-2026-27144",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-27144"
},
{
"name": "CVE-2026-4360",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-4360"
},
{
"name": "CVE-2026-58012",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-58012"
},
{
"name": "CVE-2026-56865",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56865"
},
{
"name": "CVE-2026-32283",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-32283"
},
{
"name": "CVE-2025-46686",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-46686"
},
{
"name": "CVE-2025-61727",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61727"
},
{
"name": "CVE-2026-11822",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-11822"
},
{
"name": "CVE-2025-22866",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-22866"
},
{
"name": "CVE-2026-59890",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-59890"
},
{
"name": "CVE-2026-14164",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-14164"
},
{
"name": "CVE-2026-14456",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-14456"
},
{
"name": "CVE-2026-56860",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56860"
},
{
"name": "CVE-2026-6368",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-6368"
},
{
"name": "CVE-2026-1703",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-1703"
},
{
"name": "CVE-2026-14457",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-14457"
},
{
"name": "CVE-2026-15534",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-15534"
},
{
"name": "CVE-2026-41991",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-41991"
},
{
"name": "CVE-2026-39883",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39883"
},
{
"name": "CVE-2026-6879",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-6879"
},
{
"name": "CVE-2026-102269",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102269"
},
{
"name": "CVE-2026-32281",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-32281"
},
{
"name": "CVE-2026-75596",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-75596"
},
{
"name": "CVE-2026-8763",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-8763"
},
{
"name": "CVE-2026-27142",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-27142"
},
{
"name": "CVE-2026-41989",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-41989"
},
{
"name": "CVE-2026-42504",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42504"
},
{
"name": "CVE-2026-56412",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56412"
},
{
"name": "CVE-2026-69247",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-69247"
},
{
"name": "CVE-2025-47906",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-47906"
},
{
"name": "CVE-2026-48864",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-48864"
},
{
"name": "CVE-2026-65182",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-65182"
},
{
"name": "CVE-2026-56858",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56858"
},
{
"name": "CVE-2026-12003",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-12003"
},
{
"name": "CVE-2026-53615",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-53615"
},
{
"name": "CVE-2026-15588",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-15588"
},
{
"name": "CVE-2026-59903",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-59903"
},
{
"name": "CVE-2026-102268",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102268"
},
{
"name": "CVE-2026-25589",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-25589"
},
{
"name": "CVE-2025-58188",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-58188"
},
{
"name": "CVE-2026-102267",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102267"
},
{
"name": "CVE-2025-4674",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-4674"
},
{
"name": "CVE-2026-57433",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-57433"
},
{
"name": "CVE-2026-39820",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39820"
},
{
"name": "CVE-2026-56854",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56854"
},
{
"name": "CVE-2026-84303",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-84303"
},
{
"name": "CVE-2025-5278",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-5278"
},
{
"name": "CVE-2026-32952",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-32952"
},
{
"name": "CVE-2026-73180",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-73180"
},
{
"name": "CVE-2026-39819",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39819"
},
{
"name": "CVE-2026-58010",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-58010"
},
{
"name": "CVE-2026-16118",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-16118"
},
{
"name": "CVE-2026-42769",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42769"
},
{
"name": "CVE-2025-6141",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-6141"
},
{
"name": "CVE-2026-19672",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-19672"
},
{
"name": "CVE-2024-45336",
"url": "https://www.cve.org/CVERecord?id=CVE-2024-45336"
},
{
"name": "CVE-2026-25588",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-25588"
},
{
"name": "CVE-2025-22868",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-22868"
},
{
"name": "CVE-2026-5435",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-5435"
},
{
"name": "CVE-2025-61724",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61724"
},
{
"name": "CVE-2026-3219",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-3219"
},
{
"name": "CVE-2026-11824",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-11824"
},
{
"name": "CVE-2026-2303",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-2303"
},
{
"name": "CVE-2025-61732",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61732"
},
{
"name": "CVE-2026-101917",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-101917"
},
{
"name": "CVE-2025-61723",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61723"
},
{
"name": "CVE-2026-5928",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-5928"
},
{
"name": "CVE-2026-39831",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39831"
},
{
"name": "CVE-2026-102273",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102273"
},
{
"name": "CVE-2026-39828",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39828"
},
{
"name": "CVE-2026-45447",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-45447"
},
{
"name": "CVE-2025-6170",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-6170"
},
{
"name": "CVE-2026-25679",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-25679"
},
{
"name": "CVE-2026-33811",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-33811"
},
{
"name": "CVE-2024-45337",
"url": "https://www.cve.org/CVERecord?id=CVE-2024-45337"
},
{
"name": "CVE-2025-61725",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61725"
},
{
"name": "CVE-2026-63075",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63075"
},
{
"name": "CVE-2026-25680",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-25680"
},
{
"name": "CVE-2026-87910",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-87910"
},
{
"name": "CVE-2026-39835",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39835"
},
{
"name": "CVE-2026-64847",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-64847"
},
{
"name": "CVE-2026-33815",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-33815"
},
{
"name": "CVE-2026-59884",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-59884"
},
{
"name": "CVE-2026-4539",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-4539"
},
{
"name": "CVE-2026-12087",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-12087"
},
{
"name": "CVE-2025-15366",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-15366"
},
{
"name": "CVE-2026-32289",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-32289"
},
{
"name": "CVE-2026-56848",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56848"
},
{
"name": "CVE-2026-45446",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-45446"
},
{
"name": "CVE-2026-91777",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-91777"
},
{
"name": "CVE-2026-58011",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-58011"
},
{
"name": "CVE-2026-63382",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63382"
},
{
"name": "CVE-2025-22874",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-22874"
},
{
"name": "CVE-2026-84304",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-84304"
},
{
"name": "CVE-2026-56405",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56405"
},
{
"name": "CVE-2026-63076",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63076"
},
{
"name": "CVE-2026-32280",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-32280"
},
{
"name": "CVE-2025-47912",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-47912"
},
{
"name": "CVE-2026-58016",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-58016"
},
{
"name": "CVE-2026-63383",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63383"
},
{
"name": "CVE-2024-2236",
"url": "https://www.cve.org/CVERecord?id=CVE-2024-2236"
},
{
"name": "CVE-2026-6653",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-6653"
},
{
"name": "CVE-2026-56859",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56859"
},
{
"name": "CVE-2025-61728",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61728"
},
{
"name": "CVE-2026-66422",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-66422"
},
{
"name": "CVE-2025-58186",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-58186"
},
{
"name": "CVE-2026-18798",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-18798"
},
{
"name": "CVE-2026-56853",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56853"
},
{
"name": "CVE-2026-34183",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-34183"
},
{
"name": "CVE-2025-8869",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-8869"
},
{
"name": "CVE-2025-58187",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-58187"
},
{
"name": "CVE-2026-77117",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-77117"
},
{
"name": "CVE-2026-13506",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-13506"
},
{
"name": "CVE-2026-42505",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42505"
},
{
"name": "CVE-2026-49844",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-49844"
},
{
"name": "CVE-2025-4673",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-4673"
},
{
"name": "CVE-2026-102266",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102266"
},
{
"name": "CVE-2026-54370",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-54370"
},
{
"name": "CVE-2025-22871",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-22871"
},
{
"name": "CVE-2026-29181",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-29181"
},
{
"name": "CVE-2026-39836",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39836"
},
{
"name": "CVE-2026-6791",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-6791"
},
{
"name": "CVE-2026-56408",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56408"
},
{
"name": "CVE-2026-6477",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-6477"
},
{
"name": "CVE-2026-42767",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42767"
},
{
"name": "CVE-2026-97689",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-97689"
},
{
"name": "CVE-2026-50219",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-50219"
},
{
"name": "CVE-2026-102270",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102270"
},
{
"name": "CVE-2026-6238",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-6238"
},
{
"name": "CVE-2026-0864",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-0864"
},
{
"name": "CVE-2026-63379",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63379"
},
{
"name": "CVE-2026-56403",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56403"
},
{
"name": "CVE-2026-17084",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-17084"
},
{
"name": "CVE-2026-39882",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39882"
},
{
"name": "CVE-2025-58181",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-58181"
},
{
"name": "CVE-2026-59886",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-59886"
},
{
"name": "CVE-2025-47914",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-47914"
},
{
"name": "CVE-2026-66299",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-66299"
},
{
"name": "CVE-2026-11979",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-11979"
},
{
"name": "CVE-2025-22869",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-22869"
},
{
"name": "CVE-2026-63385",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63385"
},
{
"name": "CVE-2025-58189",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-58189"
},
{
"name": "CVE-2026-63387",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63387"
},
{
"name": "CVE-2026-27140",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-27140"
},
{
"name": "CVE-2026-39817",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39817"
},
{
"name": "CVE-2026-65905",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-65905"
},
{
"name": "CVE-2026-42499",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42499"
},
{
"name": "CVE-2026-42764",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42764"
},
{
"name": "CVE-2026-82049",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-82049"
},
{
"name": "CVE-2025-22870",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-22870"
},
{
"name": "CVE-2026-75803",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-75803"
},
{
"name": "CVE-2026-8643",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-8643"
},
{
"name": "CVE-2026-19487",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-19487"
},
{
"name": "CVE-2026-41889",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-41889"
},
{
"name": "CVE-2026-27139",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-27139"
},
{
"name": "CVE-2026-42501",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42501"
},
{
"name": "CVE-2026-101918",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-101918"
},
{
"name": "CVE-2026-42250",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42250"
},
{
"name": "CVE-2026-65927",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-65927"
},
{
"name": "CVE-2026-46598",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-46598"
},
{
"name": "CVE-2025-48924",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-48924"
},
{
"name": "CVE-2026-33810",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-33810"
},
{
"name": "CVE-2026-42768",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-42768"
},
{
"name": "CVE-2026-15806",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-15806"
},
{
"name": "CVE-2026-57432",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-57432"
},
{
"name": "CVE-2026-32282",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-32282"
},
{
"name": "CVE-2026-63381",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63381"
},
{
"name": "CVE-2026-11972",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-11972"
},
{
"name": "CVE-2026-58043",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-58043"
},
{
"name": "CVE-2025-68121",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-68121"
},
{
"name": "CVE-2026-12912",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-12912"
},
{
"name": "CVE-2026-18477",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-18477"
},
{
"name": "CVE-2025-11065",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-11065"
},
{
"name": "CVE-2026-46597",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-46597"
},
{
"name": "CVE-2026-102274",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102274"
},
{
"name": "CVE-2025-61726",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61726"
},
{
"name": "CVE-2026-5704",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-5704"
},
{
"name": "CVE-2026-34182",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-34182"
},
{
"name": "CVE-2026-18503",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-18503"
},
{
"name": "CVE-2026-19542",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-19542"
},
{
"name": "CVE-2026-63388",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63388"
},
{
"name": "CVE-2026-9547",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-9547"
},
{
"name": "CVE-2026-19032",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-19032"
},
{
"name": "CVE-2026-33816",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-33816"
},
{
"name": "CVE-2026-56132",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56132"
},
{
"name": "CVE-2026-54874",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-54874"
},
{
"name": "CVE-2026-18938",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-18938"
},
{
"name": "CVE-2026-54272",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-54272"
},
{
"name": "CVE-2026-69192",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-69192"
},
{
"name": "CVE-2026-97688",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-97688"
},
{
"name": "CVE-2026-54371",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-54371"
},
{
"name": "CVE-2023-5752",
"url": "https://www.cve.org/CVERecord?id=CVE-2023-5752"
},
{
"name": "CVE-2026-39827",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39827"
},
{
"name": "CVE-2026-102272",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-102272"
},
{
"name": "CVE-2025-22873",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-22873"
},
{
"name": "CVE-2025-47273",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-47273"
},
{
"name": "CVE-2026-8286",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-8286"
},
{
"name": "CVE-2026-39823",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-39823"
},
{
"name": "CVE-2025-61729",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-61729"
},
{
"name": "CVE-2026-83557",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-83557"
},
{
"name": "CVE-2024-6345",
"url": "https://www.cve.org/CVERecord?id=CVE-2024-6345"
},
{
"name": "CVE-2026-63072",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-63072"
},
{
"name": "CVE-2026-77310",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-77310"
},
{
"name": "CVE-2026-103001",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-103001"
},
{
"name": "CVE-2026-59889",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-59889"
},
{
"name": "CVE-2026-56864",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-56864"
}
],
"initial_release_date": "2026-10-02T00:00:00",
"last_revision_date": "2026-10-02T00:00:00",
"links": [],
"reference": "CERTFR-2026-AVI-1249",
"revisions": [
{
"description": "Version initiale",
"revision_date": "2026-10-02T00:00:00.000000"
}
],
"risks": [
{
"description": "Non sp\u00e9cifi\u00e9 par l\u0027\u00e9diteur"
}
],
"summary": "De multiples vuln\u00e9rabilit\u00e9s ont \u00e9t\u00e9 d\u00e9couvertes dans les produits VMware. Elles permettent \u00e0 un attaquant de provoquer un probl\u00e8me de s\u00e9curit\u00e9 non sp\u00e9cifi\u00e9 par l\u0027\u00e9diteur.",
"title": "Multiples vuln\u00e9rabilit\u00e9s dans les produits VMware",
"vendor_advisories": [
{
"published_at": "2026-10-01",
"title": "Bulletin de s\u00e9curit\u00e9 VMware 39113",
"url": "https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/39113"
},
{
"published_at": "2026-10-01",
"title": "Bulletin de s\u00e9curit\u00e9 VMware 39112",
"url": "https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/39112"
},
{
"published_at": "2026-10-01",
"title": "Bulletin de s\u00e9curit\u00e9 VMware 39110",
"url": "https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/39110"
},
{
"published_at": "2026-10-01",
"title": "Bulletin de s\u00e9curit\u00e9 VMware 39115",
"url": "https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/39115"
},
{
"published_at": "2026-10-01",
"title": "Bulletin de s\u00e9curit\u00e9 VMware 39114",
"url": "https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/39114"
},
{
"published_at": "2026-10-01",
"title": "Bulletin de s\u00e9curit\u00e9 VMware 39109",
"url": "https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/39109"
},
{
"published_at": "2026-10-01",
"title": "Bulletin de s\u00e9curit\u00e9 VMware DSA-2026-31",
"url": "https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/39108"
},
{
"published_at": "2026-10-01",
"title": "Bulletin de s\u00e9curit\u00e9 VMware 39111",
"url": "https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/39111"
}
]
}
FKIE_CVE-2026-103001
Vulnerability from fkie_nvd - Published: 2026-09-30 22:16 - Updated: 2026-10-01 02:17| Vendor | Product | Version |
|---|
{
"affected": [
{
"affectedData": [
{
"product": "pyjwt",
"vendor": "jpadilla",
"versions": [
{
"status": "affected",
"version": "\u003e= 2.11.0, \u003c= 2.13.0"
}
]
}
],
"source": "security-advisories@github.com"
}
],
"cveTags": [],
"descriptions": [
{
"lang": "en",
"value": "PyJWT is a Python implementation of JSON Web Token standards. From 2.11.0 through 2.13.0, PyJWT\u0027s PyJWT._merge_options() method can modify a caller-supplied mutable options mapping when verify_signature is false. If an application reuses that same mapping for a later decode() or decode_complete() call and changes verify_signature to true, the mapping can retain false values for expiration, not-before, issued-at, audience, issuer, subject, and JWT ID checks. A signed token with invalid registered claims can then be accepted without disabling signature verification, but applications that create a fresh options mapping for each call are not affected."
}
],
"id": "CVE-2026-103001",
"lastModified": "2026-10-01T02:17:43.350",
"metrics": {
"cvssMetricV31": [
{
"cvssData": {
"attackComplexity": "HIGH",
"attackVector": "NETWORK",
"availabilityImpact": "NONE",
"baseScore": 6.5,
"baseSeverity": "MEDIUM",
"confidentialityImpact": "LOW",
"integrityImpact": "HIGH",
"privilegesRequired": "NONE",
"scope": "UNCHANGED",
"userInteraction": "NONE",
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N",
"version": "3.1"
},
"exploitabilityScore": 2.2,
"impactScore": 4.2,
"source": "security-advisories@github.com",
"type": "Secondary"
}
]
},
"published": "2026-09-30T22:16:33.537",
"references": [
{
"source": "security-advisories@github.com",
"url": "https://github.com/jpadilla/pyjwt/commit/0c87c8c8b1a74cac99ad8115f3050efcb7fbed35"
},
{
"source": "security-advisories@github.com",
"url": "https://github.com/jpadilla/pyjwt/issues/679"
},
{
"source": "security-advisories@github.com",
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-gvp8-978c-rx2q"
}
],
"sourceIdentifier": "security-advisories@github.com",
"vulnStatus": "Awaiting Analysis",
"weaknesses": [
{
"description": [
{
"lang": "en",
"value": "CWE-471"
}
],
"source": "security-advisories@github.com",
"type": "Primary"
}
]
}
GHSA-GVP8-978C-RX2Q
Vulnerability from github – Published: 2026-09-30 23:53 – Updated: 2026-09-30 23:53Summary
PyJWT.decode()/decode_complete() mutates a caller-supplied options dict in place whenever verify_signature is falsy, adding verify_exp/verify_nbf/verify_iat/verify_aud/verify_iss/verify_sub/verify_jti keys directly onto that object. If application code reuses the same options dict across calls (a config object, a module-level constant, a wrapper's self.options), a later call that explicitly sets verify_signature=True for a full, normal verification silently inherits the leftover verify_exp=False etc. from an earlier verify_signature=False call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.
This is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of options), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.
Details
File: jwt/api_jwt.py, PyJWT._merge_options() (called from decode_complete(), which backs both jwt.decode() and jwt.decode_complete()):
def _merge_options(self, options=None):
if options is None:
return self.options
if not options.get("verify_signature", True):
options["verify_exp"] = options.get("verify_exp", False)
options["verify_nbf"] = options.get("verify_nbf", False)
# ...5 more, same pattern -- all written directly onto the caller's dict
return {**self.options, **options}
No dict(options) copy occurs before these writes, unlike the pre-2025-03-05 code (see #743's fix, options = dict(options or {})). Confirmed via gh api repos/jpadilla/pyjwt/commits/29fbfc36 that #1045's diff removes that line. Confirmed still present on current main via a live fetch of jwt/api_jwt.py.
PoC
Two scripts, each run twice (separate processes), identical output both times (except Python object id()), saved locally and available on request:
import jwt, time
INSECURE_OPTIONS = {"verify_signature": False}
secret = "s3cr3t"
expired_token = jwt.encode(
{"exp": int(time.time()) - 3600, "data": "x"}, secret, algorithm="HS256"
)
jwt.decode(expired_token, options=INSECURE_OPTIONS)
print(INSECURE_OPTIONS)
# The dict has been silently mutated with 7 new keys, including verify_exp: False.
INSECURE_OPTIONS["verify_signature"] = True
result = jwt.decode(expired_token, secret, algorithms=["HS256"], options=INSECURE_OPTIONS)
print(result)
# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True
# was explicitly requested on this call.
Reproduced deterministically across 2 independent process runs.
Impact
Preconditions: the calling application must reuse a mutable options dict object across two or more decode()/decode_complete() calls with different intent (e.g. one "insecure peek" call followed by a "fully verified" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh options dict per call.
When triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.
Scope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.
Suggested fix: restore a shallow copy inside _merge_options() itself (rather than relying on a caller-side copy), so every caller of _merge_options is covered:
def _merge_options(self, options=None):
if options is None:
return self.options
options = dict(options) # never mutate the caller's dict
if not options.get("verify_signature", True):
options.setdefault("verify_exp", False)
options.setdefault("verify_nbf", False)
options.setdefault("verify_iat", False)
options.setdefault("verify_aud", False)
options.setdefault("verify_iss", False)
options.setdefault("verify_sub", False)
options.setdefault("verify_jti", False)
return {**self.options, **options}
Found via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.
Maintainer update — 2026-09-08
We reproduced the reported behavior on PyJWT 2.13.0 at source SHA 7144e4534c34810f4525dc4578a32addd8212cff. A caller-supplied mutable options mapping was modified when verify_signature was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.
A narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes decode(), decode_complete(), constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.
The advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "PyJWT"
},
"ranges": [
{
"events": [
{
"introduced": "2.11.0"
},
{
"last_affected": "2.13.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-103001"
],
"database_specific": {
"cwe_ids": [
"CWE-471"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-30T23:53:17Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\n`PyJWT.decode()`/`decode_complete()` mutates a caller-supplied `options` dict in place whenever `verify_signature` is falsy, adding `verify_exp`/`verify_nbf`/`verify_iat`/`verify_aud`/`verify_iss`/`verify_sub`/`verify_jti` keys directly onto that object. If application code reuses the same `options` dict across calls (a config object, a module-level constant, a wrapper\u0027s `self.options`), a later call that explicitly sets `verify_signature=True` for a full, normal verification silently inherits the leftover `verify_exp=False` etc. from an earlier `verify_signature=False` call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.\n\nThis is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of `options`), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.\n\n### Details\n\nFile: `jwt/api_jwt.py`, `PyJWT._merge_options()` (called from `decode_complete()`, which backs both `jwt.decode()` and `jwt.decode_complete()`):\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n if not options.get(\"verify_signature\", True):\n options[\"verify_exp\"] = options.get(\"verify_exp\", False)\n options[\"verify_nbf\"] = options.get(\"verify_nbf\", False)\n # ...5 more, same pattern -- all written directly onto the caller\u0027s dict\n return {**self.options, **options}\n```\n\nNo `dict(options)` copy occurs before these writes, unlike the pre-2025-03-05 code (see #743\u0027s fix, `options = dict(options or {})`). Confirmed via `gh api repos/jpadilla/pyjwt/commits/29fbfc36` that #1045\u0027s diff removes that line. Confirmed still present on current `main` via a live fetch of `jwt/api_jwt.py`.\n\n### PoC\n\nTwo scripts, each run twice (separate processes), identical output both times (except Python object `id()`), saved locally and available on request:\n\n```python\nimport jwt, time\n\nINSECURE_OPTIONS = {\"verify_signature\": False}\n\nsecret = \"s3cr3t\"\nexpired_token = jwt.encode(\n {\"exp\": int(time.time()) - 3600, \"data\": \"x\"}, secret, algorithm=\"HS256\"\n)\n\njwt.decode(expired_token, options=INSECURE_OPTIONS)\nprint(INSECURE_OPTIONS)\n# The dict has been silently mutated with 7 new keys, including verify_exp: False.\n\nINSECURE_OPTIONS[\"verify_signature\"] = True\nresult = jwt.decode(expired_token, secret, algorithms=[\"HS256\"], options=INSECURE_OPTIONS)\nprint(result)\n# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True\n# was explicitly requested on this call.\n```\n\nReproduced deterministically across 2 independent process runs.\n\n### Impact\n\nPreconditions: the calling application must reuse a mutable `options` dict object across two or more `decode()`/`decode_complete()` calls with different intent (e.g. one \"insecure peek\" call followed by a \"fully verified\" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh `options` dict per call.\n\nWhen triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.\n\nScope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.\n\nSuggested fix: restore a shallow copy inside `_merge_options()` itself (rather than relying on a caller-side copy), so every caller of `_merge_options` is covered:\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n options = dict(options) # never mutate the caller\u0027s dict\n if not options.get(\"verify_signature\", True):\n options.setdefault(\"verify_exp\", False)\n options.setdefault(\"verify_nbf\", False)\n options.setdefault(\"verify_iat\", False)\n options.setdefault(\"verify_aud\", False)\n options.setdefault(\"verify_iss\", False)\n options.setdefault(\"verify_sub\", False)\n options.setdefault(\"verify_jti\", False)\n return {**self.options, **options}\n```\n\nFound via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.\n\n## Maintainer update \u2014 2026-09-08\n\nWe reproduced the reported behavior on PyJWT 2.13.0 at source SHA `7144e4534c34810f4525dc4578a32addd8212cff`. A caller-supplied mutable `options` mapping was modified when `verify_signature` was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.\n\nA narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes `decode()`, `decode_complete()`, constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.\n\nThe advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.",
"id": "GHSA-gvp8-978c-rx2q",
"modified": "2026-09-30T23:53:17Z",
"published": "2026-09-30T23:53:17Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-gvp8-978c-rx2q"
},
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/commit/0c87c8c8b1a74cac99ad8115f3050efcb7fbed35"
},
{
"type": "PACKAGE",
"url": "https://github.com/jpadilla/pyjwt"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "PyJWT.decode() reintroduces options-dict mutation, enabling silent claim-verification bypass on dict reuse"
}
PYSEC-2026-4146
Vulnerability from pysec - Published: 2026-10-01 16:38 - Updated: 2026-10-01 17:10Summary
PyJWT.decode()/decode_complete() mutates a caller-supplied options dict in place whenever verify_signature is falsy, adding verify_exp/verify_nbf/verify_iat/verify_aud/verify_iss/verify_sub/verify_jti keys directly onto that object. If application code reuses the same options dict across calls (a config object, a module-level constant, a wrapper's self.options), a later call that explicitly sets verify_signature=True for a full, normal verification silently inherits the leftover verify_exp=False etc. from an earlier verify_signature=False call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.
This is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of options), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.
Details
File: jwt/api_jwt.py, PyJWT._merge_options() (called from decode_complete(), which backs both jwt.decode() and jwt.decode_complete()):
def _merge_options(self, options=None):
if options is None:
return self.options
if not options.get("verify_signature", True):
options["verify_exp"] = options.get("verify_exp", False)
options["verify_nbf"] = options.get("verify_nbf", False)
# ...5 more, same pattern -- all written directly onto the caller's dict
return {**self.options, **options}
No dict(options) copy occurs before these writes, unlike the pre-2025-03-05 code (see #743's fix, options = dict(options or {})). Confirmed via gh api repos/jpadilla/pyjwt/commits/29fbfc36 that #1045's diff removes that line. Confirmed still present on current main via a live fetch of jwt/api_jwt.py.
PoC
Two scripts, each run twice (separate processes), identical output both times (except Python object id()), saved locally and available on request:
import jwt, time
INSECURE_OPTIONS = {"verify_signature": False}
secret = "s3cr3t"
expired_token = jwt.encode(
{"exp": int(time.time()) - 3600, "data": "x"}, secret, algorithm="HS256"
)
jwt.decode(expired_token, options=INSECURE_OPTIONS)
print(INSECURE_OPTIONS)
# The dict has been silently mutated with 7 new keys, including verify_exp: False.
INSECURE_OPTIONS["verify_signature"] = True
result = jwt.decode(expired_token, secret, algorithms=["HS256"], options=INSECURE_OPTIONS)
print(result)
# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True
# was explicitly requested on this call.
Reproduced deterministically across 2 independent process runs.
Impact
Preconditions: the calling application must reuse a mutable options dict object across two or more decode()/decode_complete() calls with different intent (e.g. one "insecure peek" call followed by a "fully verified" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh options dict per call.
When triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.
Scope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.
Suggested fix: restore a shallow copy inside _merge_options() itself (rather than relying on a caller-side copy), so every caller of _merge_options is covered:
def _merge_options(self, options=None):
if options is None:
return self.options
options = dict(options) # never mutate the caller's dict
if not options.get("verify_signature", True):
options.setdefault("verify_exp", False)
options.setdefault("verify_nbf", False)
options.setdefault("verify_iat", False)
options.setdefault("verify_aud", False)
options.setdefault("verify_iss", False)
options.setdefault("verify_sub", False)
options.setdefault("verify_jti", False)
return {**self.options, **options}
Found via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.
Maintainer update — 2026-09-08
We reproduced the reported behavior on PyJWT 2.13.0 at source SHA 7144e4534c34810f4525dc4578a32addd8212cff. A caller-supplied mutable options mapping was modified when verify_signature was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.
A narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes decode(), decode_complete(), constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.
The advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.
| Name | purl | pyjwt | pkg:pypi/pyjwt |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "pyjwt",
"purl": "pkg:pypi/pyjwt"
},
"ranges": [
{
"events": [
{
"introduced": "2.11.0"
},
{
"last_affected": "2.13.0"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"2.11.0",
"2.12.0",
"2.12.1",
"2.13.0"
]
}
],
"aliases": [
"CVE-2026-103001",
"GHSA-gvp8-978c-rx2q"
],
"details": "### Summary\n\n`PyJWT.decode()`/`decode_complete()` mutates a caller-supplied `options` dict in place whenever `verify_signature` is falsy, adding `verify_exp`/`verify_nbf`/`verify_iat`/`verify_aud`/`verify_iss`/`verify_sub`/`verify_jti` keys directly onto that object. If application code reuses the same `options` dict across calls (a config object, a module-level constant, a wrapper\u0027s `self.options`), a later call that explicitly sets `verify_signature=True` for a full, normal verification silently inherits the leftover `verify_exp=False` etc. from an earlier `verify_signature=False` call on the same object -- and accepts an expired, wrong-audience, wrong-issuer, or wrong-subject token as valid with no exception. Signature verification itself is unaffected; this is specifically a claims-check bypass.\n\nThis is a regression of an already-fixed, closed issue: originally reported in #679 (2021), fixed in #743 (2022, via a shallow copy of `options`), and silently reintroduced by #1045 (merged 2025-03-05, a typing/logic refactor that dropped the 2022 shallow-copy line). It has been present since #1045 merged, so it likely shipped in several releases already in the wild.\n\n### Details\n\nFile: `jwt/api_jwt.py`, `PyJWT._merge_options()` (called from `decode_complete()`, which backs both `jwt.decode()` and `jwt.decode_complete()`):\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n if not options.get(\"verify_signature\", True):\n options[\"verify_exp\"] = options.get(\"verify_exp\", False)\n options[\"verify_nbf\"] = options.get(\"verify_nbf\", False)\n # ...5 more, same pattern -- all written directly onto the caller\u0027s dict\n return {**self.options, **options}\n```\n\nNo `dict(options)` copy occurs before these writes, unlike the pre-2025-03-05 code (see #743\u0027s fix, `options = dict(options or {})`). Confirmed via `gh api repos/jpadilla/pyjwt/commits/29fbfc36` that #1045\u0027s diff removes that line. Confirmed still present on current `main` via a live fetch of `jwt/api_jwt.py`.\n\n### PoC\n\nTwo scripts, each run twice (separate processes), identical output both times (except Python object `id()`), saved locally and available on request:\n\n```python\nimport jwt, time\n\nINSECURE_OPTIONS = {\"verify_signature\": False}\n\nsecret = \"s3cr3t\"\nexpired_token = jwt.encode(\n {\"exp\": int(time.time()) - 3600, \"data\": \"x\"}, secret, algorithm=\"HS256\"\n)\n\njwt.decode(expired_token, options=INSECURE_OPTIONS)\nprint(INSECURE_OPTIONS)\n# The dict has been silently mutated with 7 new keys, including verify_exp: False.\n\nINSECURE_OPTIONS[\"verify_signature\"] = True\nresult = jwt.decode(expired_token, secret, algorithms=[\"HS256\"], options=INSECURE_OPTIONS)\nprint(result)\n# No exception raised. Expected: jwt.ExpiredSignatureError, since verify_signature=True\n# was explicitly requested on this call.\n```\n\nReproduced deterministically across 2 independent process runs.\n\n### Impact\n\nPreconditions: the calling application must reuse a mutable `options` dict object across two or more `decode()`/`decode_complete()` calls with different intent (e.g. one \"insecure peek\" call followed by a \"fully verified\" call on the same dict, config object, or instance attribute). This is a real, plausible pattern but not universal; it does not affect code that constructs a fresh `options` dict per call.\n\nWhen triggered: a signed-but-expired (or wrong-aud/iss/sub/jti) token is silently accepted as fully verified, with no warning or exception. This is an authentication/claims-verification bypass, not a signature-verification bypass.\n\nScope: regression present since PR #1045 (merged 2025-03-05), affecting every PyJWT release since then up to and including the current 2.13.0.\n\nSuggested fix: restore a shallow copy inside `_merge_options()` itself (rather than relying on a caller-side copy), so every caller of `_merge_options` is covered:\n\n```python\ndef _merge_options(self, options=None):\n if options is None:\n return self.options\n options = dict(options) # never mutate the caller\u0027s dict\n if not options.get(\"verify_signature\", True):\n options.setdefault(\"verify_exp\", False)\n options.setdefault(\"verify_nbf\", False)\n options.setdefault(\"verify_iat\", False)\n options.setdefault(\"verify_aud\", False)\n options.setdefault(\"verify_iss\", False)\n options.setdefault(\"verify_sub\", False)\n options.setdefault(\"verify_jti\", False)\n return {**self.options, **options}\n```\n\nFound via a property-based audit (copy/clone independence across widely-used Python libraries); both repro scripts were written and independently re-run before this report.\n\n## Maintainer update \u2014 2026-09-08\n\nWe reproduced the reported behavior on PyJWT 2.13.0 at source SHA `7144e4534c34810f4525dc4578a32addd8212cff`. A caller-supplied mutable `options` mapping was modified when `verify_signature` was false; reusing that mapping after re-enabling signature verification could leave registered-claim checks disabled. This is a conditional claim-verification bypass, not a signature-forgery issue.\n\nA narrow fix has been prepared: PyJWT now copies the options mapping before applying defaults. Regression coverage includes `decode()`, `decode_complete()`, constructor input preservation, mutable-mapping compatibility, and reuse of an options mapping across verification modes. The focused tests pass, the full suite passes with 369 tests and 4 intentional cryptography-environment skips, and lint/format/type checks pass.\n\nThe advisory remains in triage while the fix goes through release planning. The independently verified range in this investigation is 2.13.0; reports of earlier affected versions should include the exact version and reproduction details.",
"id": "PYSEC-2026-4146",
"modified": "2026-10-01T17:10:41.224315Z",
"published": "2026-10-01T16:38:41.559119Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-gvp8-978c-rx2q"
},
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/commit/0c87c8c8b1a74cac99ad8115f3050efcb7fbed35"
},
{
"type": "PACKAGE",
"url": "https://github.com/jpadilla/pyjwt"
},
{
"type": "PACKAGE",
"url": "https://pypi.org/project/pyjwt"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-gvp8-978c-rx2q"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-103001"
}
],
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "PyJWT.decode() reintroduces options-dict mutation, enabling silent claim-verification bypass on dict reuse"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.