NS-046
documentedimplicit-type-coercion
An unquoted YAML scalar becomes a boolean before anything reads it
- reads as
- config.yml reads `country: NO` and `version: 1.10`, and it is the file the service loads. Conclusion drawn: those are the values the service has.
- actually
- The YAML 1.1 boolean resolver matches y, yes, n, no, true, false, on and off in any capitalisation, and the loaders in widest use implement it. `NO` loads as False, `off` as False, `on` as True. Numeric resolution is equally implicit: `1.10` becomes the float 1.1 and `0xdeadbeef` becomes 3735928559.
- blind because
- Reading the file confirms the characters, and the characters are right. The conversion happens inside the loader, and the loaded value is never displayed beside the text it came from.
- the check
- Load it and print the types instead of reading it: `python3 -c "import yaml,sys;[print(repr(k),repr(v),type(v).__name__) for k,v in yaml.safe_load(open(sys.argv[1])).items()]" config.yml`. Observed on PyYAML 6.0.1: `NO` -> False, `off` -> False, `on` -> True, `1.10` -> 1.1 (float), `0xdeadbeef` -> 3735928559 (int), while `08` stayed the string '08' because it is not a valid octal literal — so neighbouring keys in one file resolve inconsistently.
- cost of missing
- A country code becomes a boolean and a version becomes a different version. Both are valid values of the wrong type, and both survive any review conducted by reading the file.
- mitigation
- Quote every scalar whose type matters. Loaders following the YAML 1.2 core schema resolve fewer of these, which changes the set of surprises rather than removing it.
- generalises to
- Every format that infers type from spelling: spreadsheet imports turning identifiers into dates, shell word-splitting, environment variables parsed as numbers.
- source
- yaml.org