GHSA-PFVF-FWFP-25MP
Vulnerability from github – Published: 2026-10-09 14:07 – Updated: 2026-10-09 14:07Summary
PermissionExtension.resolve() evaluates the return value of has_permission() for truthiness on the synchronous path. supports_sync only classifies a permission as asynchronous when has_permission is declared with async def (via inspect.iscoroutinefunction), so a plain def that returns an awaitable is treated as synchronous. An awaitable is always truthy, so the check passes even when it resolves to False and the protected resolver runs.
The resolve path is chosen by the field resolver, not by the execution method, so any field with a synchronous resolver is affected under both execute_sync() and execute(). Permissions declared with async def has_permission(), or a plain def returning a boolean, are not affected.
Details
The affected code is PermissionExtension.resolve() in strawberry/permission.py. A permission attached to a field whose has_permission is a normal def returning an awaitable reaches this path; the awaitable is never awaited and its truthiness grants access. resolve_async() is not affected because it uses await_maybe().
PoC
import strawberry
from strawberry.permission import BasePermission
class DenyViaAwaitable(BasePermission):
message = "denied"
def has_permission(self, source, info, **kwargs):
async def result():
return False
return result()
@strawberry.type
class Query:
@strawberry.field(permission_classes=[DenyViaAwaitable])
def secret(self) -> str:
return "secret"
schema = strawberry.Schema(Query)
print(schema.execute_sync("{ secret }").data) # {'secret': 'secret'} instead of a permission error
Impact
An application using a custom permission whose has_permission is a normal def returning an awaitable can unintentionally grant access to the protected field. Standard permissions (a def returning a boolean, or an async def) are not affected, so exploitability depends on the application using this specific permission shape.
Fix
The synchronous path now fails closed: if has_permission() returns an awaitable, an error is raised instead of granting access.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.326.0"
},
"package": {
"ecosystem": "PyPI",
"name": "strawberry-graphql"
},
"ranges": [
{
"events": [
{
"introduced": "0.217.0"
},
{
"fixed": "0.326.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-107728"
],
"database_specific": {
"cwe_ids": [
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-09T14:07:16Z",
"nvd_published_at": "2026-10-08T23:16:58Z",
"severity": "HIGH"
},
"details": "### Summary\n\n`PermissionExtension.resolve()` evaluates the return value of `has_permission()` for truthiness on the synchronous path. `supports_sync` only classifies a permission as asynchronous when `has_permission` is declared with `async def` (via `inspect.iscoroutinefunction`), so a plain `def` that returns an awaitable is treated as synchronous. An awaitable is always truthy, so the check passes even when it resolves to `False` and the protected resolver runs.\n\nThe resolve path is chosen by the field resolver, not by the execution method, so any field with a synchronous resolver is affected under both `execute_sync()` and `execute()`. Permissions declared with `async def has_permission()`, or a plain `def` returning a boolean, are not affected.\n\n### Details\n\nThe affected code is `PermissionExtension.resolve()` in `strawberry/permission.py`. A permission attached to a field whose `has_permission` is a normal `def` returning an awaitable reaches this path; the awaitable is never awaited and its truthiness grants access. `resolve_async()` is not affected because it uses `await_maybe()`.\n\n### PoC\n\n```python\nimport strawberry\nfrom strawberry.permission import BasePermission\n\n\nclass DenyViaAwaitable(BasePermission):\n message = \"denied\"\n\n def has_permission(self, source, info, **kwargs):\n async def result():\n return False\n\n return result()\n\n\n@strawberry.type\nclass Query:\n @strawberry.field(permission_classes=[DenyViaAwaitable])\n def secret(self) -\u003e str:\n return \"secret\"\n\n\nschema = strawberry.Schema(Query)\nprint(schema.execute_sync(\"{ secret }\").data) # {\u0027secret\u0027: \u0027secret\u0027} instead of a permission error\n```\n\n### Impact\n\nAn application using a custom permission whose `has_permission` is a normal `def` returning an awaitable can unintentionally grant access to the protected field. Standard permissions (a `def` returning a boolean, or an `async def`) are not affected, so exploitability depends on the application using this specific permission shape.\n\n### Fix\n\nThe synchronous path now fails closed: if `has_permission()` returns an awaitable, an error is raised instead of granting access.",
"id": "GHSA-pfvf-fwfp-25mp",
"modified": "2026-10-09T14:07:17Z",
"published": "2026-10-09T14:07:16Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/strawberry-graphql/strawberry/security/advisories/GHSA-pfvf-fwfp-25mp"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-107728"
},
{
"type": "WEB",
"url": "https://github.com/strawberry-graphql/strawberry/pull/4605"
},
{
"type": "WEB",
"url": "https://github.com/strawberry-graphql/strawberry/commit/2ebb79796c0e5ebb43cae1abd2b5a21363b1c00e"
},
{
"type": "PACKAGE",
"url": "https://github.com/strawberry-graphql/strawberry"
},
{
"type": "WEB",
"url": "https://github.com/strawberry-graphql/strawberry/releases/tag/0.326.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Strawberry GraphQL: Synchronous permission checks can treat an awaitable authorization result as truthy"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.