CVE-2026-35523
Description
Strawberry GraphQL is a library for creating GraphQL APIs. Strawberry up until version 0.312.3 is vulnerable to an authentication bypass on WebSocket subscription endpoints. The legacy graphql-ws subprotocol handler does not verify that a connection_init handshake has been completed before processing start (subscription) messages. This allows a remote attacker to skip the on_ws_connect authentication hook entirely by connecting with the graphql-ws subprotocol and sending a start message directly, without ever sending connection_init. This vulnerability is fixed in 0.312.3.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
strawberry-graphqlPyPI | < 0.312.3 | 0.312.3 |
Affected products
1- cpe:2.3:a:strawberry:strawberry_graphql:*:*:*:*:*:python:*:*Range: <0.312.3
Patches
10977a4e6b41bFix WebSocket connection_init bypass and add subscription limit per connection (#4344)
19 files changed · +397 −0
RELEASE.md+28 −0 added@@ -0,0 +1,28 @@ +Release type: patch + +This release fixes two security vulnerabilities in the WebSocket subscription +handlers (CVE-2026-35526, CVE-2026-35523). + +**CVE-2026-35526 - Authentication bypass in `graphql-ws`**: The legacy +`graphql-ws` protocol handler didn't verify that the `connection_init` +handshake was completed before accepting `start` messages, allowing clients +to bypass any authentication logic in `on_ws_connect`. The connection is now +closed with `4401 Unauthorized` if the handshake hasn't been completed. + +**CVE-2026-35523 - Unbounded subscriptions per connection**: Both WebSocket +protocol handlers allowed unlimited concurrent subscriptions on a single +connection, making it possible for a malicious client to exhaust server +resources. A new `max_subscriptions_per_connection` parameter has been added +to all views (default: `100`). Set it to `None` to disable the limit. + +Example: + +```python +import strawberry +from strawberry.fastapi import GraphQLRouter + +schema = strawberry.Schema(query=Query, subscription=Subscription) + +# default is 100, set to None to disable the limit +graphql_app = GraphQLRouter(schema, max_subscriptions_per_connection=50) +```
strawberry/aiohttp/views.py+2 −0 modified@@ -102,6 +102,7 @@ def __init__( ), connection_init_wait_timeout: timedelta = timedelta(minutes=1), multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ) -> None: self.schema = schema self.allow_queries_via_get = allow_queries_via_get @@ -110,6 +111,7 @@ def __init__( self.subscription_protocols = subscription_protocols self.connection_init_wait_timeout = connection_init_wait_timeout self.multipart_uploads_enabled = multipart_uploads_enabled + self.max_subscriptions_per_connection = max_subscriptions_per_connection self.graphql_ide = graphql_ide async def render_graphql_ide(self, request: web.Request) -> web.Response:
strawberry/asgi/__init__.py+2 −0 modified@@ -114,6 +114,7 @@ def __init__( ), connection_init_wait_timeout: timedelta = timedelta(minutes=1), multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ) -> None: self.schema = schema self.allow_queries_via_get = allow_queries_via_get @@ -122,6 +123,7 @@ def __init__( self.protocols = subscription_protocols self.connection_init_wait_timeout = connection_init_wait_timeout self.multipart_uploads_enabled = multipart_uploads_enabled + self.max_subscriptions_per_connection = max_subscriptions_per_connection self.graphql_ide = graphql_ide async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
strawberry/channels/handlers/ws_handler.py+2 −0 modified@@ -112,13 +112,15 @@ def __init__( GRAPHQL_WS_PROTOCOL, ), connection_init_wait_timeout: datetime.timedelta | None = None, + max_subscriptions_per_connection: int | None = 100, ) -> None: if connection_init_wait_timeout is None: connection_init_wait_timeout = datetime.timedelta(minutes=1) self.connection_init_wait_timeout = connection_init_wait_timeout self.schema = schema self.keep_alive = keep_alive self.keep_alive_interval = keep_alive_interval + self.max_subscriptions_per_connection = max_subscriptions_per_connection self.protocols = subscription_protocols self.message_queue: asyncio.Queue[MessageQueueData] = asyncio.Queue() self.run_task: asyncio.Task | None = None
strawberry/fastapi/router.py+2 −0 modified@@ -131,6 +131,7 @@ def __init__( GRAPHQL_WS_PROTOCOL, ), connection_init_wait_timeout: timedelta = timedelta(minutes=1), + max_subscriptions_per_connection: int | None = 100, prefix: str = "", tags: list[str | Enum] | None = None, dependencies: Sequence[params.Depends] | None = None, @@ -184,6 +185,7 @@ def __init__( ) self.protocols = subscription_protocols self.connection_init_wait_timeout = connection_init_wait_timeout + self.max_subscriptions_per_connection = max_subscriptions_per_connection self.multipart_uploads_enabled = multipart_uploads_enabled self.graphql_ide = graphql_ide
strawberry/http/async_base_view.py+3 −0 modified@@ -88,6 +88,7 @@ class AsyncBaseHTTPView( keep_alive = False keep_alive_interval: float | None = None connection_init_wait_timeout: timedelta = timedelta(minutes=1) + max_subscriptions_per_connection: int | None = 100 request_adapter_class: Callable[[Request], AsyncHTTPRequestAdapter] websocket_adapter_class: Callable[ [ @@ -322,6 +323,7 @@ async def run( root_value=root_value, schema=self.schema, connection_init_wait_timeout=self.connection_init_wait_timeout, + max_subscriptions_per_connection=self.max_subscriptions_per_connection, ).handle() elif websocket_subprotocol == GRAPHQL_WS_PROTOCOL: await self.graphql_ws_handler_class( @@ -332,6 +334,7 @@ async def run( schema=self.schema, keep_alive=self.keep_alive, keep_alive_interval=self.keep_alive_interval, + max_subscriptions_per_connection=self.max_subscriptions_per_connection, ).handle() else: await websocket.close(4406, "Subprotocol not acceptable")
strawberry/litestar/controller.py+5 −0 modified@@ -224,6 +224,7 @@ class GraphQLController( ) keep_alive: bool = False keep_alive_interval: float = 1 + max_subscriptions_per_connection: int | None = 100 def is_websocket_request( self, request: Request | WebSocket @@ -385,6 +386,7 @@ def make_graphql_controller( ), connection_init_wait_timeout: timedelta = timedelta(minutes=1), multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ) -> type[GraphQLController]: # sourcery skip: move-assign if context_getter is None: custom_context_getter_ = _none_custom_context_getter @@ -421,6 +423,9 @@ class _GraphQLController(GraphQLController): _GraphQLController.allow_queries_via_get = allow_queries_via_get_ _GraphQLController.graphql_ide = graphql_ide_ _GraphQLController.multipart_uploads_enabled = multipart_uploads_enabled + _GraphQLController.max_subscriptions_per_connection = ( + max_subscriptions_per_connection + ) return _GraphQLController
strawberry/quart/views.py+2 −0 modified@@ -93,6 +93,7 @@ def __init__( ), connection_init_wait_timeout: timedelta = timedelta(minutes=1), multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ) -> None: self.schema = schema self.allow_queries_via_get = allow_queries_via_get @@ -101,6 +102,7 @@ def __init__( self.subscription_protocols = subscription_protocols self.connection_init_wait_timeout = connection_init_wait_timeout self.multipart_uploads_enabled = multipart_uploads_enabled + self.max_subscriptions_per_connection = max_subscriptions_per_connection self.graphql_ide = graphql_ide async def render_graphql_ide(self, request: Request) -> Response:
strawberry/subscriptions/protocols/graphql_transport_ws/handlers.py+18 −0 modified@@ -54,13 +54,15 @@ def __init__( root_value: RootValue | None, schema: BaseSchema, connection_init_wait_timeout: timedelta, + max_subscriptions_per_connection: int | None = None, ) -> None: self.view = view self.websocket = websocket self.context = context self.root_value = root_value self.schema = schema self.connection_init_wait_timeout = connection_init_wait_timeout + self.max_subscriptions_per_connection = max_subscriptions_per_connection self.connection_init_timeout_task: asyncio.Task | None = None self.connection_init_received = False self.connection_acknowledged = False @@ -233,6 +235,22 @@ async def handle_subscribe(self, message: SubscribeMessage) -> None: await self.websocket.close(code=4409, reason=reason) return + # NOTE: this applies to all in-flight operations (queries and mutations + # executed over WebSocket included), not only subscriptions. + if ( + self.max_subscriptions_per_connection is not None + and len(self.operations) >= self.max_subscriptions_per_connection + ): + error = GraphQLError("Subscription limit reached") + await self.send_message( + { + "id": message["id"], + "type": "error", + "payload": [error.formatted], + } + ) + return + operation = Operation( self, message["id"],
strawberry/subscriptions/protocols/graphql_ws/handlers.py+27 −0 modified@@ -44,6 +44,7 @@ def __init__( schema: BaseSchema, keep_alive: bool, keep_alive_interval: float | None, + max_subscriptions_per_connection: int | None = None, ) -> None: self.view = view self.websocket = websocket @@ -52,9 +53,11 @@ def __init__( self.schema = schema self.keep_alive = keep_alive self.keep_alive_interval = keep_alive_interval + self.max_subscriptions_per_connection = max_subscriptions_per_connection self.keep_alive_task: asyncio.Task | None = None self.subscriptions: dict[str, AsyncGenerator] = {} self.tasks: dict[str, asyncio.Task] = {} + self.connection_acknowledged: bool = False async def handle(self) -> None: try: @@ -119,6 +122,8 @@ async def handle_connection_init(self, message: ConnectionInitMessage) -> None: {"type": "connection_ack", "payload": connection_ack_payload} ) + self.connection_acknowledged = True + if self.keep_alive: keep_alive_handler = self.handle_keep_alive() self.keep_alive_task = asyncio.create_task(keep_alive_handler) @@ -129,7 +134,29 @@ async def handle_connection_terminate( await self.websocket.close(code=1000, reason="") async def handle_start(self, message: StartMessage) -> None: + if not self.connection_acknowledged: + await self.websocket.close(code=4401, reason="Unauthorized") + return + operation_id = message["id"] + + # Clean up existing operation with same ID to prevent task leaks + if operation_id in self.tasks: + await self.cleanup_operation(operation_id) + + 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 + payload = message["payload"] query = payload["query"] operation_name = payload.get("operationName")
tests/http/clients/aiohttp.py+2 −0 modified@@ -74,6 +74,7 @@ def __init__( connection_init_wait_timeout: timedelta = timedelta(minutes=1), result_override: ResultOverrideFunction = None, multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ): view = GraphQLView( schema=schema, @@ -84,6 +85,7 @@ def __init__( subscription_protocols=subscription_protocols, connection_init_wait_timeout=connection_init_wait_timeout, multipart_uploads_enabled=multipart_uploads_enabled, + max_subscriptions_per_connection=max_subscriptions_per_connection, ) view.result_override = result_override
tests/http/clients/asgi.py+2 −0 modified@@ -78,6 +78,7 @@ def __init__( connection_init_wait_timeout: timedelta = timedelta(minutes=1), result_override: ResultOverrideFunction = None, multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ): view = GraphQLView( schema, @@ -88,6 +89,7 @@ def __init__( subscription_protocols=subscription_protocols, connection_init_wait_timeout=connection_init_wait_timeout, multipart_uploads_enabled=multipart_uploads_enabled, + max_subscriptions_per_connection=max_subscriptions_per_connection, ) view.result_override = result_override
tests/http/clients/base.py+1 −0 modified@@ -108,6 +108,7 @@ def __init__( connection_init_wait_timeout: timedelta = timedelta(minutes=1), result_override: ResultOverrideFunction = None, multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ): ... @abc.abstractmethod
tests/http/clients/channels.py+2 −0 modified@@ -159,13 +159,15 @@ def __init__( connection_init_wait_timeout: timedelta = timedelta(minutes=1), result_override: ResultOverrideFunction = None, multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ): self.ws_app = DebuggableGraphQLWSConsumer.as_asgi( schema=schema, keep_alive=keep_alive, keep_alive_interval=keep_alive_interval, subscription_protocols=subscription_protocols, connection_init_wait_timeout=connection_init_wait_timeout, + max_subscriptions_per_connection=max_subscriptions_per_connection, ) self.http_app = DebuggableGraphQLHTTPConsumer.as_asgi(
tests/http/clients/fastapi.py+2 −0 modified@@ -89,6 +89,7 @@ def __init__( connection_init_wait_timeout: timedelta = timedelta(minutes=1), result_override: ResultOverrideFunction = None, multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ): self.app = FastAPI() @@ -101,6 +102,7 @@ def __init__( subscription_protocols=subscription_protocols, connection_init_wait_timeout=connection_init_wait_timeout, multipart_uploads_enabled=multipart_uploads_enabled, + max_subscriptions_per_connection=max_subscriptions_per_connection, context_getter=fastapi_get_context, root_value_getter=get_root_value, )
tests/http/clients/litestar.py+2 −0 modified@@ -65,6 +65,7 @@ def __init__( connection_init_wait_timeout: timedelta = timedelta(minutes=1), result_override: ResultOverrideFunction = None, multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ): BaseGraphQLController = make_graphql_controller( schema=schema, @@ -75,6 +76,7 @@ def __init__( subscription_protocols=subscription_protocols, connection_init_wait_timeout=connection_init_wait_timeout, multipart_uploads_enabled=multipart_uploads_enabled, + max_subscriptions_per_connection=max_subscriptions_per_connection, path="/graphql", context_getter=litestar_get_context, root_value_getter=get_root_value,
tests/http/clients/quart.py+2 −0 modified@@ -99,6 +99,7 @@ def __init__( connection_init_wait_timeout: timedelta = timedelta(minutes=1), result_override: ResultOverrideFunction = None, multipart_uploads_enabled: bool = False, + max_subscriptions_per_connection: int | None = 100, ): self.app = Quart(__name__) self.app.debug = True @@ -114,6 +115,7 @@ def __init__( subscription_protocols=subscription_protocols, connection_init_wait_timeout=connection_init_wait_timeout, multipart_uploads_enabled=multipart_uploads_enabled, + max_subscriptions_per_connection=max_subscriptions_per_connection, ) self.app.add_url_rule(
tests/websockets/test_graphql_transport_ws.py+97 −0 modified@@ -1216,3 +1216,100 @@ async def test_unexpected_client_disconnects_are_gracefully_handled( assert not process_errors.called assert Subscription.active_infinity_subscriptions == 0 + + +async def test_max_subscriptions_per_connection(http_client_class: type[HttpClient]): + """Test that subscriptions beyond the limit are rejected with an error.""" + test_client = http_client_class(schema, max_subscriptions_per_connection=2) + + async with test_client.ws_connect( + "/graphql", protocols=[GRAPHQL_TRANSPORT_WS_PROTOCOL] + ) as ws: + await ws.send_message({"type": "connection_init"}) + connection_ack_message: ConnectionAckMessage = await ws.receive_json() + assert connection_ack_message == {"type": "connection_ack"} + + # First two subscriptions should succeed + await ws.send_message( + { + "id": "sub1", + "type": "subscribe", + "payload": {"query": 'subscription { infinity(message: "Hi") }'}, + } + ) + next_message: NextMessage = await ws.receive_json() + assert next_message["type"] == "next" + assert next_message["id"] == "sub1" + + await ws.send_message( + { + "id": "sub2", + "type": "subscribe", + "payload": {"query": 'subscription { infinity(message: "Hi") }'}, + } + ) + next_message = await ws.receive_json() + assert next_message["type"] == "next" + assert next_message["id"] == "sub2" + + # Third subscription should be rejected + await ws.send_message( + { + "id": "sub3", + "type": "subscribe", + "payload": {"query": 'subscription { infinity(message: "Hi") }'}, + } + ) + error_message: ErrorMessage = await ws.receive_json() + assert error_message["type"] == "error" + assert error_message["id"] == "sub3" + assert error_message["payload"] == [{"message": "Subscription limit reached"}] + + # Completing one should free a slot + await ws.send_message({"id": "sub1", "type": "complete"}) + + await ws.send_message( + { + "id": "sub4", + "type": "subscribe", + "payload": {"query": 'subscription { infinity(message: "Hi") }'}, + } + ) + next_message = await ws.receive_json() + assert next_message["type"] == "next" + assert next_message["id"] == "sub4" + + await ws.send_message({"id": "sub2", "type": "complete"}) + await ws.send_message({"id": "sub4", "type": "complete"}) + await ws.close() + + +async def test_max_subscriptions_per_connection_disabled( + http_client_class: type[HttpClient], +): + """Test that setting max_subscriptions_per_connection to None disables the limit.""" + test_client = http_client_class(schema, max_subscriptions_per_connection=None) + + async with test_client.ws_connect( + "/graphql", protocols=[GRAPHQL_TRANSPORT_WS_PROTOCOL] + ) as ws: + await ws.send_message({"type": "connection_init"}) + connection_ack_message: ConnectionAckMessage = await ws.receive_json() + assert connection_ack_message == {"type": "connection_ack"} + + # Should be able to create many subscriptions without limit + for i in range(5): + await ws.send_message( + { + "id": f"sub{i}", + "type": "subscribe", + "payload": {"query": 'subscription { infinity(message: "Hi") }'}, + } + ) + next_message: NextMessage = await ws.receive_json() + assert next_message["type"] == "next" + assert next_message["id"] == f"sub{i}" + + for i in range(5): + await ws.send_message({"id": f"sub{i}", "type": "complete"}) + await ws.close()
tests/websockets/test_graphql_ws.py+196 −0 modified@@ -255,6 +255,30 @@ async def test_rejecting_connection_with_custom_connection_error_payload( assert not ws_raw.close_reason +async def test_start_without_connection_init_is_rejected( + ws_raw: WebSocketClient, +): + """Sending a 'start' message without a prior 'connection_init' must be rejected. + + This ensures that the on_ws_connect authentication hook cannot be bypassed + by skipping the connection_init handshake. + """ + await ws_raw.send_legacy_message( + { + "type": "start", + "id": "1", + "payload": { + "query": "subscription { listener }", + }, + } + ) + + await ws_raw.receive(timeout=2) + assert ws_raw.closed + assert ws_raw.close_code == 4401 + assert ws_raw.close_reason == "Unauthorized" + + async def test_context_can_be_modified_from_within_on_ws_connect( ws_raw: WebSocketClient, ): @@ -865,3 +889,175 @@ async def test_unexpected_client_disconnects_are_gracefully_handled( assert not process_errors.called assert Subscription.active_infinity_subscriptions == 0 + + +async def test_max_subscriptions_per_connection(http_client_class: type[HttpClient]): + """Test that subscriptions beyond the limit are rejected with an error.""" + test_client = http_client_class(schema, max_subscriptions_per_connection=2) + + async with test_client.ws_connect( + "/graphql", protocols=[GRAPHQL_WS_PROTOCOL] + ) as ws: + await ws.send_legacy_message({"type": "connection_init"}) + response: ConnectionAckMessage = await ws.receive_json() + assert response["type"] == "connection_ack" + + # First two subscriptions should succeed + await ws.send_legacy_message( + { + "type": "start", + "id": "sub1", + "payload": { + "query": 'subscription { infinity(message: "Hi") }', + }, + } + ) + data_message: DataMessage = await ws.receive_json() + assert data_message["type"] == "data" + assert data_message["id"] == "sub1" + + await ws.send_legacy_message( + { + "type": "start", + "id": "sub2", + "payload": { + "query": 'subscription { infinity(message: "Hi") }', + }, + } + ) + data_message = await ws.receive_json() + assert data_message["type"] == "data" + assert data_message["id"] == "sub2" + + # Third subscription should be rejected + await ws.send_legacy_message( + { + "type": "start", + "id": "sub3", + "payload": { + "query": 'subscription { infinity(message: "Hi") }', + }, + } + ) + error_message: ErrorMessage = await ws.receive_json() + assert error_message["type"] == "error" + assert error_message["id"] == "sub3" + assert error_message["payload"] == {"message": "Subscription limit reached"} + + # Completing one should free a slot + await ws.send_legacy_message({"type": "stop", "id": "sub1"}) + + await ws.send_legacy_message( + { + "type": "start", + "id": "sub4", + "payload": { + "query": 'subscription { infinity(message: "Hi") }', + }, + } + ) + # The 'complete' for sub1 is always sent before sub4's first data message + # (cleanup_operation awaits the cancelled task, which sends the complete). + msg = await ws.receive_json() + while msg.get("id") == "sub1": + msg = await ws.receive_json() + assert msg["type"] == "data" + assert msg["id"] == "sub4" + + await ws.close() + + +async def test_reusing_operation_id_does_not_count_against_limit( + http_client_class: type[HttpClient], +): + """Reusing an existing operation ID should clean up the old subscription + and not count against the subscription limit.""" + test_client = http_client_class(schema, max_subscriptions_per_connection=2) + + async with test_client.ws_connect( + "/graphql", protocols=[GRAPHQL_WS_PROTOCOL] + ) as ws: + await ws.send_legacy_message({"type": "connection_init"}) + response: ConnectionAckMessage = await ws.receive_json() + assert response["type"] == "connection_ack" + + # Fill up to the limit + await ws.send_legacy_message( + { + "type": "start", + "id": "sub1", + "payload": { + "query": 'subscription { infinity(message: "Hi") }', + }, + } + ) + data_message: DataMessage = await ws.receive_json() + assert data_message["type"] == "data" + assert data_message["id"] == "sub1" + + await ws.send_legacy_message( + { + "type": "start", + "id": "sub2", + "payload": { + "query": 'subscription { infinity(message: "Hi") }', + }, + } + ) + data_message = await ws.receive_json() + assert data_message["type"] == "data" + assert data_message["id"] == "sub2" + + # Reuse "sub1" — should clean up the old one, not hit the limit + await ws.send_legacy_message( + { + "type": "start", + "id": "sub1", + "payload": { + "query": 'subscription { infinity(message: "Hello") }', + }, + } + ) + + msg = await ws.receive_json() + # skip any leftover messages from the old sub1 + while msg.get("id") == "sub1" and msg.get("type") == "complete": + msg = await ws.receive_json() + + assert msg["type"] == "data" + assert msg["id"] == "sub1" + + await ws.close() + + +async def test_max_subscriptions_per_connection_disabled( + http_client_class: type[HttpClient], +): + """Test that setting max_subscriptions_per_connection to None disables the limit.""" + test_client = http_client_class(schema, max_subscriptions_per_connection=None) + + async with test_client.ws_connect( + "/graphql", protocols=[GRAPHQL_WS_PROTOCOL] + ) as ws: + await ws.send_legacy_message({"type": "connection_init"}) + response: ConnectionAckMessage = await ws.receive_json() + assert response["type"] == "connection_ack" + + # Should be able to create many subscriptions without limit + for i in range(5): + await ws.send_legacy_message( + { + "type": "start", + "id": f"sub{i}", + "payload": { + "query": 'subscription { infinity(message: "Hi") }', + }, + } + ) + data_message: DataMessage = await ws.receive_json() + assert data_message["type"] == "data" + assert data_message["id"] == f"sub{i}" + + for i in range(5): + await ws.send_legacy_message({"type": "stop", "id": f"sub{i}"}) + await ws.close()
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-vpwc-v33q-mq89ghsaADVISORY
- github.com/strawberry-graphql/strawberry/security/advisories/GHSA-vpwc-v33q-mq89nvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-35523ghsaADVISORY
- github.com/strawberry-graphql/strawberry/commit/0977a4e6b41b7cfe3e9d8ba84a43458a2b0c54c2ghsaWEB
- github.com/strawberry-graphql/strawberry/releases/tag/0.312.3ghsaWEB
News mentions
0No linked articles in our index yet.