5 February 2025 · 6 min read
YAML thinks Norway is false
How an unquoted country code became a boolean, and the other coercions waiting in your config files.
YAML was designed to be readable by humans, which means guessing what an unquoted value was meant to be. Most of the time it guesses right. The times it does not have names.
The Norway problem
ISO 3166 gives Norway the two-letter code NO. YAML 1.1 treats no as a boolean. A list of country codes therefore parses like this:
countries: - GB - FR - NO - SE
Three strings and one false. Norway silently leaves your list and a boolean takes its place. The same trap catches ON and OFF, Y and N, and — in YAML 1.1 — YES and NO in any capitalisation.
The fix is a single pair of quotes. "NO" is unambiguously a string. The difficulty is that nothing warns you which values need them.
Versions disagree
YAML 1.2 narrowed booleans to true and false only, which would fix this. But many parsers still implement 1.1 semantics for compatibility, and some implement a hybrid. Whether your config is safe depends on the library your deployment tool happens to use, which is not a comfortable thing to depend on.
The other coercions
- Sexagesimal numbers: 12:30 can parse as 750 in YAML 1.1, because colons once meant base 60. Time values and version-like strings both suffer.
- Leading zeros: 0755 may become octal 493, which matters when the value is a file mode.
- Version strings: 1.20 parses as the number 1.2, so a dependency pin quietly changes meaning.
- Large numbers: an ID beyond 2^53 loses precision when the parser makes it a float.
- The empty value: a key with nothing after the colon is null, not an empty string.
Tags make it worse
YAML supports tags that tell the parser to construct a specific type. Some language bindings implement this by instantiating arbitrary classes, which turns loading a config file into executing code. This is why Python has yaml.safe_load, and why loading untrusted YAML with the default loader has produced real remote code execution.
DevToolery parses with the CORE schema, which excludes every tag capable of constructing an object. That is also why unquoted yes stays a string here rather than becoming true — it is the safe reading, not the friendly one.
Working with it
- Quote any string that could be read as something else: country codes, versions, times, anything with a leading zero.
- Quote values that came from outside your codebase, on principle.
- Convert to JSON to see what a parser actually produced — JSON has no ambiguity to hide behind.
- Prefer safe loaders in every language. There is no situation where the unsafe one is worth it.
None of this makes YAML a bad format. It makes it a format with a large surface of implicit behaviour, which is a fine trade when a human writes the file and a poor one when a machine does.
Tools mentioned