Recursive AI

Read untrusted YAML safely

All oneyaml C# recipes

YAML from a request body, an upload or another tenant can be built to exhaust memory or the stack. OneYaml bounds depth, alias expansion and integer size by default; for input you don’t control, use YamlLimits.Untrusted, which also bounds input size, scalar size and document count.

C#
using System.Text;
using OneYaml;
 
var options = new YamlSerializerOptions { Limits = YamlLimits.Untrusted };
 
// A normal request body reads as usual.
var ok = YamlSerializer.Deserialize<Dictionary<string, object?>>("name: web\nports: [80]\n", options)!;
Console.WriteLine($"ok: {ok.Count} keys");
 
// "Billion laughs": nine levels of aliases, each repeated ten times.
var bomb = new StringBuilder("l0: &l0 [x, x, x, x, x, x, x, x, x, x]\n");
for (int i = 1; i < 9; i++)
{
    bomb.Append($"l{i}: &l{i} [{string.Join(", ", Enumerable.Repeat($"*l{i - 1}", 10))}]\n");
}
Report(() => YamlSerializer.Deserialize<object>(bomb.ToString(), options));
 
// Deep nesting is refused too, without overflowing the stack.
Report(() => YamlSerializer.Deserialize<object>(new string('[', 100_000), options));
 
// Size the limits yourself: start from the preset and change what you need.
var small = new YamlSerializerOptions { Limits = YamlLimits.Untrusted with { MaxInputBytes = 1024 } };
Report(() => YamlSerializer.Deserialize<object>(string.Concat(Enumerable.Repeat("a: b\n", 1000)), small));
 
static void Report(Action read)
{
    try
    {
        read();
    }
    catch (YamlException e) when (e.Category == YamlErrorCategory.Limit)
    {
        Console.WriteLine($"refused: {e.Message}");
    }
}
Text
ok: 2 keys
refused: Expanding the aliases exceeds the budget set by YamlLimits (AliasNodeFactor 2, AliasNodeFloor 100000, AliasByteFactor 2, AliasByteFloor 8388608). (line 5, column 45)
refused: The nesting depth is larger than the limit MaxDepth (128). (line 1, column 129)
refused: The input is larger than the limit MaxInputBytes (1024).

Good to know

  • YamlLimits is a record: start from YamlLimits.Untrusted and change only what your input needs with with { ... }.
  • The same limits apply to YamlDocument.Parse through YamlDocumentOptions.Limits.
  • An alias whose expansion would exceed the budget is refused, and the message names the settings that set it.
  • A limit error has Category == YamlErrorCategory.Limit, so you can tell it apart from bad YAML.
  • No unsafe code and no dependencies outside .NET, and the parser is fuzzed.

Every limit and its default is in Limits.