Skip to content

Report a block whose aliases form a cycle or expand too far as unreadable [patch] - #191

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/159-yaml-alias-cycles
Oct 6, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/159-yaml-alias-cycles

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #159

Before: an alias inside its own anchor, for example a: &x [1, *x] or a: &x {b: *x}, made YamlSerializer.ConvertValue recurse until the stack overflowed. The test host died with exit code 134 and Stack overflow., which no caller can catch. Nested aliases were also expanded in full at every use. A six-level, roughly 350-byte block grew to a million values, and CombineFrontmatter wrote out a 15-million-character header.

After: ConvertValue keeps track of the containers on the path it is converting, comparing them by reference, and counts how many values it has produced. A cycle, or more than MaxExpandedValues (100,000) values, throws InvalidOperationException. TryParseYamlObject already catches that exception and reports the block as unreadable, so ExtractFrontmatter returns null and CombineFrontmatter returns the document unchanged. An alias that is used more than once without a cycle still expands as before.

ReferenceEqualityComparer isn't available on netstandard2.0/2.1, so a small private reference comparer takes its place. DeepCloneValue needs no guard, because it only ever sees the bounded, acyclic tree that ConvertValue returns.

Tests: added YamlAliasTests, which covers a self-referencing sequence and mapping through both TryParseYamlObject and the public API, the six-level alias bomb, and a non-cyclic alias used twice. With the fix reverted, the self-referencing tests crash the host (exit 134, stack overflow) and both bomb tests fail. With the fix in place, the full suite passes: 260 of 260.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hnw6WDDjwQ8HvxinnWS9CF


Generated by Claude Code

…able [patch]

YamlDotNet resolves an alias to the same object as its anchor, so an alias
inside its own anchor (`a: &x [1, *x]`) produced a cyclic value, and
ConvertValue copied it recursively until the stack overflowed and the host
process died. Nested aliases were also expanded in full at every use, so a
350-byte block grew to a million values.

ConvertValue now tracks the containers on the path it is converting and
counts the values it has produced. A cycle, or more than 100,000 values,
throws InvalidOperationException, which TryParseYamlObject already reports
as an unreadable block, so the document is left unchanged.

Fixes #159

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hnw6WDDjwQ8HvxinnWS9CF
@sonarqubecloud

sonarqubecloud Bot commented Oct 6, 2026

Copy link
Copy Markdown

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 self-referencing YAML anchor in frontmatter crashes the process with StackOverflow; nested aliases expand exponentially

2 participants