Skip to content

Bound what a document expands into, not only what it weighs - #121

Merged
rasuvaeff merged 1 commit into
masterfrom
fix/bound-the-nodes-a-document-expands-into
Sep 6, 2026
Merged

Bound what a document expands into, not only what it weighs#121
rasuvaeff merged 1 commit into
masterfrom
fix/bound-the-nodes-a-document-expands-into

Conversation

@rasuvaeff

Copy link
Copy Markdown
Owner

Fixes #120

Found by the first full-cycle review (docs/reviews/review-openapi-contract-2026-09-06-full-cycle.md, findings A-1 and A-2).

A-1. A YAML document of 772 bytes exhausted memory and died with a fatal error — not an InvalidContract, so a caller could not catch it.

file=676 bytes, 9^7 nodes   accepted in  93.5 ms, peak  54 MiB
file=724 bytes, 9^8 nodes   accepted in 774.7 ms, peak 390 MiB
file=772 bytes, 9^9 nodes   Fatal error: Allowed memory size exhausted

The mechanism sits between two earlier waves, which is why no single-axis pass could see it: the third wave correctly stopped the reference resolver from descending into data (enum, example, default, const), which also removed the node budget from those keywords; the first wave added assertEncodable(), which json_encodes a schema whole to detect NAN/INF. The bomb lives in an enum, so nothing counted it, and the encodability check turned 387 million nodes into an ~800 MB string.

Documents are now measured by what they expand into — Limits::$documentNodes, default 5 000 000, counted iteratively (the structure being measured must not exhaust the stack either), applied in fromArray() (which fromJson() delegates to) and per file in a DocumentGraph, sharing one budget across the graph as the byte budget already does. The default sits above what any document within documentBytes can hold, so it refuses amplification without refusing size. After the fix every level of the bomb is refused as InvalidContract in ~100 ms at a 10 MiB peak.

A-2. MessageReading appeared in no #[Covers], so Infection generated no mutants for it at all — the body reading loop, including the byte-budget comparison, was outside the gate. The package's own AGENTS.md calls that the symptom and claims the trait was fixed in the first wave; it never was. Adding it raised the mutant count 2297 → 2373, of which 66 were already killed. The ten that escape are the chunking arithmetic and the media-type helpers, classified in AGENTS.md with the reasoning rather than left as noise.

Verification: composer build, composer rector, bin/package-audit green; mutation 2206 killed / 158 escaped, MSI 93% against a gate of 92; probes in docs/reviews/probe-openapi-contract-2026-09-06-full/.

A YAML file of 772 bytes took the process down with a fatal error. Anchors and
aliases expand inside the parser, before any budget measures anything, and the
byte budget measures the file; the reference resolver's own budget counts the
nodes it descends into, and since the third wave it rightly does not descend
into data — `enum`, `example`, `default`, `const` — which is exactly where an
alias is as welcome as anywhere else. Both behaviours are right on their own;
together they left an unbounded path, and the fail-closed encodability check
was the amplifier that turned it into 800 MB of JSON.

Documents are now measured by what they expand into: a node budget on `Limits`,
counted iteratively at every entry point and shared across a multi-file graph
the way the byte budget already is. Counting stops at the budget, so refusing
an oversized document costs the budget rather than its size.

The message-reading trait was in no `#[Covers]`, so it produced no mutants at
all — the body reading loop was outside the gate, byte-budget comparison
included. Adding it raised the mutant count by 76; the ten that escape are
classified in AGENTS.md rather than left unexplained.
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: f6028502-1a5e-4dce-9c68-841c9790a2dd

Warning

Your free Security trial is over. An organization admin can upgrade to Advanced for continuous pull request security review or dismiss this notice.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rasuvaeff
rasuvaeff merged commit ba1fd02 into master Sep 6, 2026
10 checks passed
@rasuvaeff
rasuvaeff deleted the fix/bound-the-nodes-a-document-expands-into branch September 6, 2026 16:48
@rasuvaeff rasuvaeff mentioned this pull request Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A sub-kilobyte YAML document exhausts memory and dies with a fatal error

1 participant