GitHub Security Advisory: GHSA-r7cg-qjjm-xhqq
Package: webonyx/graphql-php (COMPOSER)
Severity: HIGH (CVSS 8.2)
CWE: CWE-674: Uncontrolled Recursion
Vulnerable versions: <= 15.32.2
Patched in: 15.32.3

Summary: webonyx/graphql-php has unbounded recursion in parser that causes stack overflow on crafted nested input

Details:
## Summary

`GraphQL\Language\Parser` is a recursive descent parser with no recursion depth limit and no `zend.max_allowed_stack_size` interaction. Crafted nested queries trigger a SIGSEGV in the PHP runtime, killing the FPM/CLI worker process. Smallest crashing payload is approximately 74 KB.

## Affected Component

- `src/Language/Parser.php` -- the `Parser` class (no recursion depth tracking)
- `src/Language/Lexer.php` -- the `Lexer` class

## Severity

**HIGH (8.2)** -- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H

Integrity is Low because the entire PHP process (FPM worker, CLI process, Swoole worker, RoadRunner worker, etc.) is terminated by SIGSEGV. Every concurrent request handled by the same process is dropped along with the attacker's request, with no error message, no log entry, and no recovery path beyond restart. The 74 KB minimum crashing payload sits well below any common HTTP body size limit, and the failure mode is **the worst possible**: not catchable, not observable, no diagnostics.

## Description

`GraphQL\Language\Parser` parses GraphQL documents using mutually recursive PHP methods (`parseValueLiteral`, `parseObject`, `parseObjectField`, `parseList`, `parseSelectionSet`, `parseSelection`, `parseField`, `parseTypeReference`, `parseInlineFragment`). The constructor (`Parser.php:325`) accepts only three options:

```php
// src/Language/Parser.php:64
/**
 * @phpstan-type ParserOptions array{
 *   noLocation?: bool,
 *   allowLegacySDLEmptyFields?: bool,
 *   allowLegacySDLImplementsInterfaces?: bool,
 * }
 */
```

There is **no `maxTokens`, no `maxDepth`, no `maxRecursionDepth`, no token counter, and no recursion depth counter** anywhere in the parser or lexer. PHP recursion is bounded only by the C stack size (typically 8 MB via `ulimit -s 8192`).

When the C stack is exhausted by graphql-php's recursive parser, **PHP segfaults**. The PHP 8.3 runtime ships with `zend.max_allowed_stack_size` (default 0 = auto-detect), which is supposed to convert userland recursion overflow into a catchable `Stack overflow detected` error. **In practice, this protection does not catch the graphql-php parser overflow**: testing with PHP 8.3.30 in default Docker configuration, every crashing depth produces SIGSEGV (exit code 139), not a catchable error.

This finding has been tested against the **latest stable release `webonyx/graphql-php@v15.31.4`** running on PHP 8.3.30.

## Root Cause

```php
// src/Language/Parser.php:168
class Parser
{
    // ... no recursionDepth field ...
    private Lexer $lexer;

    // src/Language/Parser.php:325
    public function __construct($source, array $options = [])
    {
        $sourceObj = $source instanceof Source
            ? $source
            : new Source($source);
        $this->lexer = new Lexer($sourceObj, $options);
    }
```

There is no field tracking recursion depth. Every recursive parse method calls itself without bound. PHP function call frames are small (~256 bytes each), but the cumulative d