Skip to content

chore(deps): update dependency graphql-core to v3.3.1 [security] - #510

Merged
cmlplatform-renovate[bot] merged 1 commit into
mainfrom
renovate/pypi-graphql-core-vulnerability
Nov 9, 2026
Merged

cmlplatform-renovate[bot] merged 1 commit into
mainfrom
renovate/pypi-graphql-core-vulnerability

Conversation

@cmlplatform-renovate

@cmlplatform-renovate cmlplatform-renovate Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
graphql-core (changelog) 3.3.0 → 3.3.1 age confidence

GraphQL-core has denial of service via memory leak in aborted validations

CVE-2026-119286 / GHSA-mpqx-q82w-9x46

More information

Details

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
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:

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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


GraphQL-core has denial of service via memory leak in aborted validations

CVE-2026-119286 / GHSA-mpqx-q82w-9x46

More information

Details

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
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:

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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


Release Notes

graphql-python/graphql-core (graphql-core)

v3.3.1

Compare Source

Patch-release GraphQL-core v3.3.1, based on GraphQL.js v17.0.2.

This patch-release supports Python 3.10 to 3.15.

This is a security release. It fixes a denial-of-service vulnerability in the validator. Upgrading is recommended for all users of GraphQL-core 3.3.

Security fix:

  • CVE-2026-119286: Fixed a memory leak that could be used for a denial of service (see the security advisory). When a validation was aborted because the document had more errors than allowed by max_errors (100 by default), the same error object was raised every time. Its traceback grew with each aborted validation and kept the frames of all these validations alive, including their documents. Any client could exhaust the memory of a server by sending documents with more than 100 validation errors. Now a new error is created for every aborted validation (#​287). GraphQL-core 3.2 is not affected.

Bug fixes:

  • The memoization of collected subfields used the ids of objects that could be freed and reused during execution. Therefore, a field could be resolved with the selection set of another field. The cache now uses the identity of the field list like GraphQL.js and keeps the list alive during execution (#​284). This was a regression in 3.3.
  • Closing a subscription did not close the source iterator if the __aiter__ method of the source returned a different object, such as an async generator (#​283). This was a regression in 3.3.
  • Decimal, Fraction and NumPy scalar values, as returned by database drivers, ORMs and NumPy/pandas, can be serialized as Int, Float and Boolean again (#​280). This was a regression in 3.3. Different from 3.2, non-integer values such as Decimal("2.5") are rejected as Int instead of being truncated.
  • If input values of different types shared the same default value, e.g. after extend_schema(), the default was coerced for the type that was used first (#​279). Coerced default values are now memoized on the argument or input field like in GraphQL.js. Schemas that were pickled with 3.3.0 can still be unpickled.

Behavior changes:

  • A GraphQLError created while another exception is handled does not take over the traceback of that exception anymore. If the error is raised, it gets its own traceback, and the handled exception becomes its context. Errors of incremental computations and reasons of abort signals do not accumulate tracebacks anymore when they are raised repeatedly.
  • validation_aborted_error has been removed from graphql.validation.validate. It was not exported.

Other changes:

  • Improved performance when coercing literal arguments, and added a benchmark for executing fields with arguments.

Thanks to @​riodw for reporting the memory leak and to @​Fire162 for submitting a first fix (#​288), to @​abraira85 for reporting and analyzing the issue with the subfields and to @​jadenfix for fixing it, to @​patrick91 for fixing the shared default values and improving performance, to @​AryanRG for fixing the subscription iterator, and to @​gabor-lbl for reporting the serialization issue.

Thanks to @​jkimbo for sponsoring this project.


Configuration

📅 Schedule: (in timezone Europe/Amsterdam)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate CLI.

@codspeed

codspeed Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 7 untouched benchmarks


Comparing renovate/pypi-graphql-core-vulnerability (48748e1) with main (de8c09a)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (fafa0cb) during the generation of this report, so de8c09a was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

Signed-off-by: cmlplatform-renovate[bot] <334049226+cmlplatform-renovate[bot]@users.noreply.github.com>
@cmlplatform-renovate
cmlplatform-renovate Bot force-pushed the renovate/pypi-graphql-core-vulnerability branch from 21a353a to 48748e1 Compare November 9, 2026 21:47
@codecov

codecov Bot commented Nov 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@cmlplatform-renovate
cmlplatform-renovate Bot merged commit bedcaf2 into main Nov 9, 2026
21 checks passed
@cmlplatform-renovate
cmlplatform-renovate Bot deleted the renovate/pypi-graphql-core-vulnerability branch November 9, 2026 22:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants