---
id: "CVE-2025-65015"
kind: "vulnerability"
aliases: ["CVE-2025-65015", "GHSA-frfh-8v73-gjg4"]
title: "joserfc has Possible Uncontrolled Resource Consumption Vulnerability Triggered by Logging Arbitrarily Large JWT Token Payloads"
severity: "critical"
cvss: 9.2
epss: 0.0044
exploited: false
status: "open"
category: "package"
products: [{"name": "joserfc", "fixed": ["1.3.5"], "affected": ">= 1.3.3, < 1.3.5", "ecosystem": "pypi"}, {"name": "joserfc", "fixed": ["1.4.2"], "affected": ">= 1.4.0, < 1.4.2", "ecosystem": "pypi"}, {"name": "hsiaoming joserfc", "fixed": ["1.3.5"], "affected": ">= 1.3.3, < 1.3.5", "ecosystem": "vendor"}, {"name": "hsiaoming joserfc", "fixed": ["1.4.2"], "affected": ">= 1.4.0, < 1.4.2", "ecosystem": "vendor"}]
ecosystems: ["pypi", "vendor"]
tags: ["cwe-770", "django", "fastapi", "flask", "gunicorn", "joserfc", "jwt", "package", "python", "starlette", "uvicorn"]
not_affected_if: ["fixed-version", "not-using-joserfc", "reverse-proxy-header-limit"]
published_at: "2025-11-18T18:26:04+00:00"
first_seen_at: "2026-10-27T17:32:35+00:00"
updated_at: "2026-10-27T17:36:18+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-frfh-8v73-gjg4", "first_seen_at": "2026-10-27T17:32:35+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-65015", "first_seen_at": "2026-10-27T17:32:47+00:00"}]
---

# joserfc has Possible Uncontrolled Resource Consumption Vulnerability Triggered by Logging Arbitrarily Large JWT Token Payloads

joserfc's ExceededSizeError messages embed the entire oversized JWT header or payload, so a very large bearer token gets written to logs and monitoring tools. An attacker who can send large Authorization headers can consume memory, CPU, disk and log-ingestion capacity (CWE-770).

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- pypi:joserfc affected >= 1.3.3, < 1.3.5 fixed 1.3.5
- pypi:joserfc affected >= 1.4.0, < 1.4.2 fixed 1.4.2
- vendor:hsiaoming joserfc affected >= 1.3.3, < 1.3.5 fixed 1.3.5
- vendor:hsiaoming joserfc affected >= 1.4.0, < 1.4.2 fixed 1.4.2

## How to check

- Run `pip show joserfc` and compare the version against the fixed releases.
- Check the lock file (`poetry.lock`, `uv.lock`, `requirements.txt`) for `joserfc` pinned below `1.3.5` or in the range `1.4.0` to `1.4.1`.
- Check whether the app is served directly by uvicorn, gunicorn or Werkzeug without a reverse proxy that caps header size.

## Fix

- Upgrade `joserfc` to `1.3.5` if on the 1.3.x line, or to `1.4.2` if on the 1.4.x line.
- Put a reverse proxy such as nginx in front of the app with an explicit maximum header size.
- Consider filtering or truncating ExceededSizeError messages in logging and Sentry before they are stored.

## Timeline

- 2026-10-27T17:32:35+00:00 ghsa https://github.com/advisories/GHSA-frfh-8v73-gjg4
- 2026-10-27T17:32:47+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2025-65015

## From the sources

### Summary
The `ExceededSizeError` exception messages are embedded with non-decoded JWT token parts and may cause Python logging to record an arbitrarily large, forged JWT payload.

### Details
In situations where a misconfigured — or entirely absent — production-grade web server sits in front of a Python web application, an attacker may be able to send arbitrarily large bearer tokens in the HTTP request headers. When this occurs, Python logging or diagnostic tools (e.g., Sentry) may end up processing extremely large log messages containing the full JWT header during the `joserfc.jwt.decode()` operation. The same behavior also appears when validating claims and signature payload sizes, as the library raises `joserfc.errors.ExceededSizeError()` with the full payload embedded in the exception message. Since the payload is already fully loaded into memory at this stage, the library cannot prevent or reject it per se.

It is therefore the responsibility of the underlying web server (`uvicorn`/`h11`, `gunicorn`, `Starlette`, `Werkzeug`, `nginx`...etc) to enforce limits on header sizes. For example, a `FastAPI`/`Starlette` application running _without_ `uvicorn` and/or `gunicorn` cannot enforce header size limits on its own. With `uvicorn`/`h11`, the [--h11-max-incomplete-event-size <int>](https://uvicorn.dev/settings/#implementation:~:text=%2D%2Dh11%2Dmax%2Dincomplete%2Devent%2Dsize%20%3Cint%3E) option can restrict the total size of the header _plus_ body, but not the header alone. Similarly, [vLLM serve](https://docs.vllm.ai/en/latest/cli/serve/#-h11-max-incomplete-event-size) —due to its reliance on `uvicorn`/`h11` and the need for heavy data transfer in ML inference workloads, sets a default limit of 4 MB for header _plus_ body and is frequently increased. In practice, a robust reverse proxy (such as `nginx`) is typically required because it can explicitly cap maximum header size. Unfortunately, many web applications do not run behind a proper reverse proxy.

Given these constraints, the `joserfc` library cannot safely log or embed payloads of arbitrary size. This issue is particularly subtle, as it occurs only when a maliciously crafted JWT finally reaches the Python application, a scenario that most developers will never encounter during routine development and testing.

### PoC
**Environment**
Ubuntu 24.04 LTS
Python 3.12
Tested on `joserfc` version `1.4.1`

```python

import logging
from datetime import UTC, datetime, timedelta

from joserfc import jwt
from joserfc.errors import ExceededSizeError, UnsupportedAlgorithmError
from joserfc.jwk import OctKey


logger = logging.getLogger(__name__)


SECRET_KEY = "8c13bd66babc241b29f8553429bdab7deb6f5b74ddfda7765471e57ecd55641e"
LONG_JWT_TOKEN = (
    "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQifQ"
    "."
    "eyJpc3MiOiJhdXRoX3NlcnZlciIsImlhdCI6MTc2MzI0OTEwMSwiZXhwIjoxNzY5MjQ5MTAxfQ"
    "."
    "6-k2jmkGXD6wXOgYgjPS8E5lS_GjWpgIuY54gokjAn8"
)

HEADER = {
    "alg": (
        "RS256dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
        "RS256dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
        "RS256dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
        "RS256dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
        "RS256dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
        "RS256dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
    ),
}
CLAIMS = {
    "iss": "auth_server",
    "iat": datetime.now(UTC),
    "exp": datetime.now(UTC) + timedelta(minutes=15),
}


def main():
    # Create OctKey from SECRET_KEY
    key = OctKey.import_key(SECRET_KEY)

    # Simulate creating a very large JWT
    # (this will fail with joserfc.errors.UnsupportedAlgorithmError
    # due to an invalid 'alg' header content
    try:
        token = jwt.encode(HEADER, CLAIMS, key)
    except UnsupportedAlgorithmError:
        # Use a forged token that has the same header and claims instead
        # but an invalid signature
        token = LONG_JWT_TOKEN
    logger.warning(f"Created JWT: {token}")

    # Now try to decode the large JWT
    try:
        decoded_token = jwt.decode(token, key)
        logger.warning("This line will never be reached.")
        logger.warning(decoded_token.claims)
    except ExceededSizeError:
        logger.exception(
            "The JWT size is too large and may be a security attack attempt."
        )
        # this is logging the whole header content in the exception message!

```
```
Created JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQifQ.eyJpc3MiOiJhdXRoX3NlcnZlciIsImlhdCI6MTc2MzI0OTEwMSwiZXhwIjoxNzY5MjQ5MTAxfQ.6-k2jmkGXD6wXOgYgjPS8E5lS_GjWpgIuY54gokjAn8
The JWT size is too large and may be a security attack attempt.
Traceback (most recent call last):
  File "security_issue.py", line 55, in main
    claims = jwt.decode(token, key)
             ^^^^^^^^^^^^^^^^^^^^^^
  File ".venv/lib/python3.12/site-packages/joserfc/jwt.py", line 106, in decode
    header, payload = _decode_jws(_value, key, algorithms, registry)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".venv/lib/python3.12/site-packages/joserfc/jwt.py", line 127, in _decode_jws
    jws_obj = deserialize_compact(value, key, algorithms, registry)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".venv/lib/python3.12/site-packages/joserfc/jws.py", line 183, in deserialize_compact
    obj = extract_compact(to_bytes(value), payload, registry)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".venv/lib/python3.12/site-packages/joserfc/_rfc7797/compact.py", line 50, in extract_rfc7515_compact
    registry.validate_header_size(header_segment)
  File ".venv/lib/python3.12/site-packages/joserfc/_rfc7515/registry.py", line 104, in validate_header_size
    raise ExceededSizeError(f"Header size of '{header!r}' exceeds {self.max_header_length} bytes.")
joserfc.errors.ExceededSizeError: exceeded_size: Header size of 'b'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRSUzI1NmRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQifQ'' exceeds 512 bytes.

```

## Code location
This behavior occurs in:

**`joserfc/_rfc7515/registry.py`**
**L102-112**
```python
    def validate_header_size(self, header: bytes) -> None:
        if header and len(header) > self.max_header_length:
            raise ExceededSizeError(f"Header size of '{header!r}' exceeds {self

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
