BREW-SCHEMATHESIS-CVE-20… (GHSA-MPQX-Q82W-9X46)

Vulnerability from osv_homebrew – Published: 2026-10-31 11:32 – Updated: 2026-10-31 11:32 – Source website
VLAI
Summary
GraphQL-core has denial of service via memory leak in aborted validations
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.


{
  "affected": [
    {
      "ecosystem_specific": {
        "fix": "bump",
        "range_state": "fixed",
        "resource": "graphql-core",
        "resource_purl": "pkg:pypi/graphql-core@3.3.1",
        "upstream_fixed_in": "3.3.1"
      },
      "package": {
        "ecosystem": "Homebrew",
        "name": "schemathesis",
        "purl": "pkg:brew/schemathesis"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.32.1"
            },
            {
              "fixed": "4.32.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "database_specific": {
    "confidence": "high",
    "source": "matched",
    "strategy": "registry",
    "upstream_evidence": [
      {
        "ecosystem": "PyPI",
        "key": "pkg:pypi/graphql-core@3.3.1",
        "name": "graphql-core",
        "resource": "graphql-core",
        "strategy": "registry",
        "subject_version": "3.3.1"
      }
    ]
  },
  "details": "### Summary\n\nWhen 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.\n\n### Details\n\nAny 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.\n\nGraphQL.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.\n\n### Proof of concept\n\n```python\nimport gc\nimport tracemalloc\nfrom graphql import build_schema, parse, validate\n\nschema = build_schema(\"type Query { a: Int }\")\ndocument = parse(\"{ \" + \" \".join(f\"f{i}\" for i in range(101)) + \" }\")\n\ntracemalloc.start()\nvalidate(schema, document)\ngc.collect()\nbase = tracemalloc.get_traced_memory()[0]\nfor _ in range(200):\n    validate(schema, document)\ngc.collect()\nprint((tracemalloc.get_traced_memory()[0] - base) // 1024, \"KiB\")\n```\n\nWith 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.\n\n### Impact\n\nDenial 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.\n\n### Patches\n\nFixed in **3.3.1**.\n\n`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.\n\n### Workarounds\n\nIf you cannot upgrade, clear the traceback of the shared error after each validation:\n\n```python\nfrom graphql.validation import validate as validate_document\nfrom graphql.validation.validate import validation_aborted_error\n\ndef validate(*args, **kwargs):\n    try:\n        return validate_document(*args, **kwargs)\n    finally:\n        validation_aborted_error.__traceback__ = None\n```\n\nPass this function instead of the original `validate` where your framework allows it.\n\n### Credits\n\nReported by @riodw. @Fire162 submitted a first fix in #288.",
  "id": "BREW-schemathesis-CVE-2026-119286",
  "modified": "2026-10-31T11:32:43Z",
  "published": "2026-10-31T11:32:43Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/graphql-python/graphql-core/security/advisories/GHSA-mpqx-q82w-9x46"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-119286"
    },
    {
      "type": "WEB",
      "url": "https://github.com/graphql-python/graphql-core/issues/287"
    },
    {
      "type": "WEB",
      "url": "https://github.com/graphql-python/graphql-core/pull/288"
    },
    {
      "type": "WEB",
      "url": "https://github.com/graphql-python/graphql-core/commit/3c319fd9f4b8f6c9db4189520561bf8dd0e70be9"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/graphql-python/graphql-core"
    },
    {
      "type": "WEB",
      "url": "https://github.com/graphql-python/graphql-core/releases/tag/v3.3.1"
    }
  ],
  "schema_version": "1.7.3",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "GraphQL-core has denial of service via memory leak in aborted validations",
  "upstream": [
    "GHSA-mpqx-q82w-9x46",
    "CVE-2026-119286"
  ]
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    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.