CVE-2026-107727
Strawberry legacy graphql-ws retains naturally completed subscription slots
Strawberry GraphQL is a library for creating GraphQL APIs. From 0.312.3 until 0.327.2, the legacy graphql-ws subscription handler in strawberry/subscriptions/protocols/graphql_ws/handlers.py does not remove naturally completed operations from self.tasks and self.subscriptions. When max_subscriptions_per_connection is configured, a client on a persistent WebSocket connection can use distinct operation IDs for one-shot subscriptions to fill the connection's configured slots even after those subscriptions send complete, causing later legitimate operations on that connection to be rejected with Subscription limit reached. The modern graphql-transport-ws protocol and deployments without the configured per-connection cap are not affected. This issue is fixed in version 0.327.2.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
3.7
Strawberry legacy graphql-ws retains naturally completed subscription slots
Strawberry GraphQL is a library for creating GraphQL APIs. From 0.312.3 until 0.327.2, the legacy graphql-ws subscription handler in strawberry/subscriptions/protocols/graphql_ws/handlers.py does not remove naturally completed operations from self.tasks and self.subscriptions. When max_subscriptions_per_connection is configured, a client on a persistent WebSocket connection can use distinct operation IDs for one-shot subscriptions to fill the connection's configured slots even after those subscriptions send complete, causing later legitimate operations on that connection to be rejected with Subscription limit reached. The modern graphql-transport-ws protocol and deployments without the configured per-connection cap are not affected. This issue is fixed in version 0.327.2.
- Record
CVE-2026-107727- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400
- Updated
- 2026-11-02
EUVDEUVD-2026-95391
3.7
Strawberry GraphQL is a library for creating GraphQL APIs. From 0.312.3 until 0.327.2, the legacy graphql-ws subscription handler in strawberry/subscriptions/protocols/graphql_ws/handlers.py does not remove naturally completed operations from self.tasks and self.subscriptions. When max_subscriptions_per_connection is configured, a client on a persistent WebSocket connection can use distinct operation IDs for one-shot subscriptions to fill the connection's configured slots even after those subscriptions send complete, causing later legitimate operations on that connection to be rejected with Subscription limit reached. The modern graphql-transport-ws protocol and deployments without the configured per-connection cap are not affected. This issue is fixed in version 0.327.2.
- Record
EUVD-2026-95391- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-m952-2w3f-6r8h
3.7
Strawberry legacy graphql-ws retains naturally completed subscription slots
Summary
Strawberry's legacy graphql-ws subscription handler can retain task and subscription bookkeeping after a one-shot subscription has naturally sent its complete message. When the application configures max_subscriptions_per_connection, the handler counts those completed operations in len(self.tasks). A client that uses distinct operation IDs can therefore reach the configured subscription limit even though the earlier subscriptions have already completed, causing subsequent legitimate subscriptions on the same persistent WebSocket connection to receive Subscription limit reached.
This is a conditional connection-level availability and resource-accounting issue. It requires explicit use of the legacy graphql-ws protocol, a persistent WebSocket connection, one-shot subscriptions that naturally complete, and a configured max_subscriptions_per_connection limit. It is not an unconditional issue in a default Strawberry installation and is distinct from the previously fixed single-connection infinite-subscription issue.
Details
The audited snapshot is Strawberry 0.324.4, commit c3caabd1188acda62045e7fd9ba9e37ac430cf96. The relevant implementation is in [strawberry/subscriptions/protocols/graphql_ws/handlers.py](https://github.com/strawberry-graphql/strawberry/blob/c3caabd1188acda62045e7fd9ba9e37ac430cf96/strawberry/subscriptions/protocols/graphql_ws/handlers.py), particularly the operation-start, result-handling, and cleanup paths.
When a new operation is started, the handler rejects it once the task count reaches the configured limit:
if (
self.max_subscriptions_per_connection is not None
and len(self.tasks) >= self.max_subscriptions_per_connection
):
await self.send_message(
ErrorMessage(
type="error",
id=operation_id,
payload={"message": "Subscription limit reached"},
)
)
return
The operation task is stored in self.tasks, and its result source is stored in self.subscriptions. On normal exhaustion of the result source, handle_async_results() sends a completion message:
async for result in result_source:
await self.send_data_message(result, operation_id)
await self.send_message(
CompleteMessage(type="complete", id=operation_id)
)
The natural completion path does not call cleanup_operation() before returning. The deletion of the stored operation state is implemented separately:
async def cleanup_operation(self, operation_id: str) -> None:
if operation_id in self.subscriptions:
await self.subscriptions[operation_id].aclose()
del self.subscriptions[operation_id]
self.tasks[operation_id].cancel()
await self.tasks[operation_id]
del self.tasks[operation_id]
Consequently, after a one-shot operation has sent complete, its task entry can remain in self.tasks until the client explicitly stops the operation, reuses the same operation ID, or the connection is cleaned up. New operation IDs are compared against the retained task count and can be rejected.
The connection-slot impact requires both parts of the behavior:
- the natural-completion path leaves the completed operation accounted for; and
- the legacy handler has
max_subscriptions_per_connectionenabled.
PoC
The following reproduction is local-only and bounded. It uses an in-memory Channels WebSocket fixture, one connection, three one-shot subscriptions, and the legacy graphql-ws subprotocol. It does not connect to a public endpoint or start a network service.
1. Environment installation
Create an isolated virtual environment and install the official Channels integration extra together with the local ASGI test dependency:
python -m pip install "strawberry-graphql[channels]==0.324.4" daphne
…
- Record
GHSA-m952-2w3f-6r8h- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400
- Updated
- 2026-11-02
NVD
3.7
Strawberry GraphQL is a library for creating GraphQL APIs. From 0.312.3 until 0.327.2, the legacy graphql-ws subscription handler in strawberry/subscriptions/protocols/graphql_ws/handlers.py does not remove naturally completed operations from self.tasks and self.subscriptions. When max_subscriptions_per_connection is configured, a client on a persistent WebSocket connection can use distinct operation IDs for one-shot subscriptions to fill the connection's configured slots even after those subscriptions send complete, causing later legitimate operations on that connection to be rejected with Subscription limit reached. The modern graphql-transport-ws protocol and deployments without the configured per-connection cap are not affected. This issue is fixed in version 0.327.2.
- Record
CVE-2026-107727- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400
- Updated
- 2026-11-02
OSV
3.7
Strawberry legacy graphql-ws retains naturally completed subscription slots
Strawberry GraphQL is a library for creating GraphQL APIs. From 0.312.3 until 0.327.2, the legacy graphql-ws subscription handler in strawberry/subscriptions/protocols/graphql_ws/handlers.py does not remove naturally completed operations from self.tasks and self.subscriptions. When max_subscriptions_per_connection is configured, a client on a persistent WebSocket connection can use distinct operation IDs for one-shot subscriptions to fill the connection's configured slots even after those subscriptions send complete, causing later legitimate operations on that connection to be rejected with Subscription limit reached. The modern graphql-transport-ws protocol and deployments without the configured per-connection cap are not affected. This issue is fixed in version 0.327.2.
- Record
CVE-2026-107727- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400
- Updated
- 2026-11-02
OSVGHSA-m952-2w3f-6r8h
3.7
Strawberry legacy graphql-ws retains naturally completed subscription slots
Summary
Strawberry's legacy graphql-ws subscription handler can retain task and subscription bookkeeping after a one-shot subscription has naturally sent its complete message. When the application configures max_subscriptions_per_connection, the handler counts those completed operations in len(self.tasks). A client that uses distinct operation IDs can therefore reach the configured subscription limit even though the earlier subscriptions have already completed, causing subsequent legitimate subscriptions on the same persistent WebSocket connection to receive Subscription limit reached.
This is a conditional connection-level availability and resource-accounting issue. It requires explicit use of the legacy graphql-ws protocol, a persistent WebSocket connection, one-shot subscriptions that naturally complete, and a configured max_subscriptions_per_connection limit. It is not an unconditional issue in a default Strawberry installation and is distinct from the previously fixed single-connection infinite-subscription issue.
Details
The audited snapshot is Strawberry 0.324.4, commit c3caabd1188acda62045e7fd9ba9e37ac430cf96. The relevant implementation is in [strawberry/subscriptions/protocols/graphql_ws/handlers.py](https://github.com/strawberry-graphql/strawberry/blob/c3caabd1188acda62045e7fd9ba9e37ac430cf96/strawberry/subscriptions/protocols/graphql_ws/handlers.py), particularly the operation-start, result-handling, and cleanup paths.
When a new operation is started, the handler rejects it once the task count reaches the configured limit:
if (
self.max_subscriptions_per_connection is not None
and len(self.tasks) >= self.max_subscriptions_per_connection
):
await self.send_message(
ErrorMessage(
type="error",
id=operation_id,
payload={"message": "Subscription limit reached"},
)
)
return
The operation task is stored in self.tasks, and its result source is stored in self.subscriptions. On normal exhaustion of the result source, handle_async_results() sends a completion message:
async for result in result_source:
await self.send_data_message(result, operation_id)
await self.send_message(
CompleteMessage(type="complete", id=operation_id)
)
The natural completion path does not call cleanup_operation() before returning. The deletion of the stored operation state is implemented separately:
async def cleanup_operation(self, operation_id: str) -> None:
if operation_id in self.subscriptions:
await self.subscriptions[operation_id].aclose()
del self.subscriptions[operation_id]
self.tasks[operation_id].cancel()
await self.tasks[operation_id]
del self.tasks[operation_id]
Consequently, after a one-shot operation has sent complete, its task entry can remain in self.tasks until the client explicitly stops the operation, reuses the same operation ID, or the connection is cleaned up. New operation IDs are compared against the retained task count and can be rejected.
The connection-slot impact requires both parts of the behavior:
- the natural-completion path leaves the completed operation accounted for; and
- the legacy handler has
max_subscriptions_per_connectionenabled.
PoC
The following reproduction is local-only and bounded. It uses an in-memory Channels WebSocket fixture, one connection, three one-shot subscriptions, and the legacy graphql-ws subprotocol. It does not connect to a public endpoint or start a network service.
1. Environment installation
Create an isolated virtual environment and install the official Channels integration extra together with the local ASGI test dependency:
python -m pip install "strawberry-graphql[channels]==0.324.4" daphne
…
- Record
GHSA-m952-2w3f-6r8h- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - Weakness
- CWE-400
- Updated
- 2026-11-02
OSVPYSEC-2026-4302
3.7
Strawberry legacy graphql-ws retains naturally completed subscription slots
Summary
Strawberry's legacy graphql-ws subscription handler can retain task and subscription bookkeeping after a one-shot subscription has naturally sent its complete message. When the application configures max_subscriptions_per_connection, the handler counts those completed operations in len(self.tasks). A client that uses distinct operation IDs can therefore reach the configured subscription limit even though the earlier subscriptions have already completed, causing subsequent legitimate subscriptions on the same persistent WebSocket connection to receive Subscription limit reached.
This is a conditional connection-level availability and resource-accounting issue. It requires explicit use of the legacy graphql-ws protocol, a persistent WebSocket connection, one-shot subscriptions that naturally complete, and a configured max_subscriptions_per_connection limit. It is not an unconditional issue in a default Strawberry installation and is distinct from the previously fixed single-connection infinite-subscription issue.
Details
The audited snapshot is Strawberry 0.324.4, commit c3caabd1188acda62045e7fd9ba9e37ac430cf96. The relevant implementation is in [strawberry/subscriptions/protocols/graphql_ws/handlers.py](https://github.com/strawberry-graphql/strawberry/blob/c3caabd1188acda62045e7fd9ba9e37ac430cf96/strawberry/subscriptions/protocols/graphql_ws/handlers.py), particularly the operation-start, result-handling, and cleanup paths.
When a new operation is started, the handler rejects it once the task count reaches the configured limit:
if (
self.max_subscriptions_per_connection is not None
and len(self.tasks) >= self.max_subscriptions_per_connection
):
await self.send_message(
ErrorMessage(
type="error",
id=operation_id,
payload={"message": "Subscription limit reached"},
)
)
return
The operation task is stored in self.tasks, and its result source is stored in self.subscriptions. On normal exhaustion of the result source, handle_async_results() sends a completion message:
async for result in result_source:
await self.send_data_message(result, operation_id)
await self.send_message(
CompleteMessage(type="complete", id=operation_id)
)
The natural completion path does not call cleanup_operation() before returning. The deletion of the stored operation state is implemented separately:
async def cleanup_operation(self, operation_id: str) -> None:
if operation_id in self.subscriptions:
await self.subscriptions[operation_id].aclose()
del self.subscriptions[operation_id]
self.tasks[operation_id].cancel()
await self.tasks[operation_id]
del self.tasks[operation_id]
Consequently, after a one-shot operation has sent complete, its task entry can remain in self.tasks until the client explicitly stops the operation, reuses the same operation ID, or the connection is cleaned up. New operation IDs are compared against the retained task count and can be rejected.
The connection-slot impact requires both parts of the behavior:
- the natural-completion path leaves the completed operation accounted for; and
- the legacy handler has
max_subscriptions_per_connectionenabled.
PoC
The following reproduction is local-only and bounded. It uses an in-memory Channels WebSocket fixture, one connection, three one-shot subscriptions, and the legacy graphql-ws subprotocol. It does not connect to a public endpoint or start a network service.
1. Environment installation
Create an isolated virtual environment and install the official Channels integration extra together with the local ASGI test dependency:
python -m pip install "strawberry-graphql[channels]==0.324.4" daphne
…
- Record
PYSEC-2026-4302- CVSS 3.1
- 3.7 Low
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
strawberry-graphqlPyPI |
0.312.3 up to 0.327.2 | 0.327.2 |
strawberry-graphqlPyPI |
0.312.3 up to 0.327.2, plus listed versions 0.312.40.313.00.314.00.314.10.314.2 Show 40 moreShow less0.314.30.315.00.315.10.315.20.315.30.315.40.315.50.315.60.315.70.316.00.317.00.317.10.317.20.318.00.318.10.319.00.320.00.320.10.320.20.320.30.320.40.321.00.321.10.322.00.322.10.322.20.323.00.323.10.323.20.324.00.324.10.324.20.324.30.324.40.324.50.325.00.326.00.326.10.327.00.327.1 |
0.327.2 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-107727 is a low vulnerability (CVSS 3.7) in Strawberry-graphql Strawberry, published October 8, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.43%, the 37th percentile. Fixed in strawberry-graphql 0.327.2.
References
github.com/strawberry-graphql/strawberry/security/advisories/GHSA-m952-2w3f-6r8h
github.com/strawberry-graphql/strawberry/pull/4610
github.com/strawberry-graphql/strawberry/commit/24c5e46d57f7ad4f77e6e01fb8ae482dd1c0ff92
github.com/strawberry-graphql/strawberry/releases/tag/0.327.2
nvd.nist.gov/vuln/detail/CVE-2026-107727
github.com/advisories/GHSA-m952-2w3f-6r8h
github.com/CVEProject/cvelistV5/tree/main/cves/2026/107xxx/CVE-2026-107727.json
github.com/strawberry-graphql/strawberry
pypi.org/project/strawberry-graphql
Left out here: the rest of 3 source descriptions. The JSON record has them.