GraphQL-core has denial of service via memory leak in aborted validations
CVE-2026-119286 · GHSA-mpqx-q82w-9x46 · EUVD-2026-113404 · published 29 Oct 2026
How serious is it
- Severity: high, CVSS score 7.5 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-119286?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
graphql-core(PyPI) >= 3.3.0a3, < 3.3.1
How to check
- Run
pip show graphql-corein your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
- Or run
npx shippable-dev scan localhost:3000in your project folder. It reads your lockfile and flags this vulnerability along with every other known one.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
graphql-coreto 3.3.1 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
### Summary
When a validation was aborted because the document contained more errors than allowed by `max_errors` (100 by default), `validate()` raised a single module-level `ValidationAbortedError` instance. Python appends the frames of every `raise` to the existing `__traceback__` of an exception that is raised again, so every aborted validation extended the traceback of this shared error. That traceback kept the frames, and with them the document, the validation context and all errors of every aborted validation, alive for the lifetime of the process.
### Details
Any client can trigger an aborted validation by sending a small document with more than 100 validation errors, e.g. 101 unknown fields. No authentication is required, and since validation runs before execution, depth limiters and complexity analyzers do not prevent it. Each such request permanently retained about 124 KiB on CPython 3.12, without an upper limit, so memory grew with every request until the process was restarted or killed.
GraphQL.js uses a shared error object at the same place, but JavaScript errors capture their stack trace only once when they are created, so it is not affected. GraphQL-core 3.2 creates a new error for each aborted validation and is not affected either.
### Proof of concept
```python
import gc
import tracemalloc
from graphql import build_schema, parse, validate
schema = build_schema("type Query { a: Int }")
document = parse("{ " + " ".join(f"f{i}" for i in range(101)) + " }")
tracemalloc.start()
validate(schema, document)
gc.collect()
base = tracemalloc.get_traced_memory()[0]
for _ in range(200):
validate(schema, document)
gc.collect()
print((tracemalloc.get_traced_memory()[0] - base) // 1024, "KiB")
```
With GraphQL-core 3.3.0 on CPython 3.12, the retained memory grows from 147 KiB after one call to about 25 MB after 200 calls. With the fix, it stays constant at about 180 KiB.
### Impact
Denial of service through memory exhaustion. A few thousand small requests are enough to push a worker past a typical 256 MB or 512 MB container limit and trigger an OOM kill. No data is disclosed or modified.
### Patches
Fixed in **3.3.1**.
`validate()` now creates a new `ValidationAbortedError` for every aborted validation and removes its traceback and exception context before returning it, so that the returned errors do not keep the frames of the validation alive. The module-level `validation_aborted_error` instance, which was not exported, has been removed.
### Workarounds
If you cannot upgrade, clear the traceback of the shared error after each validation:
```python
from graphql.validation import validate as validate_document
from graphql.validation.validate import validation_aborted_error
def validate(*args, **kwargs):
try:
return validate_document(*args, **kwargs)
finally:
validation_aborted_error.__traceback__ = None
```
Pass this function instead of the original `validate` where your framework allows it.
### Credits
Reported by @riodw. @Fire162 submitted a first fix in #288.
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/graphql-python/graphql-core/security/advisories/GHSA-mpqx-q82w-9x46
- github.com/graphql-python/graphql-core/issues/287
- github.com/graphql-python/graphql-core/pull/288
- github.com/graphql-python/graphql-core/commit/3c319fd9f4b8f6c9db4189520561bf8dd0e70be9
- github.com/graphql-python/graphql-core/releases/tag/v3.3.1
- nvd.nist.gov/vuln/detail/CVE-2026-119286
- github.com/advisories/GHSA-mpqx-q82w-9x46
- github.com/graphql-python/graphql-core
Merged from ENISA EUVD, GitHub Advisory Database, OSV. Last updated 31 Oct 2026. All vulnerabilities · RSS · JSON