The fix is at the source: quote every value, brace every structure.
There is a class of Kubernetes bug that no diff, no review, and no dry-run will ever catch — because the corruption happens below all of them, in the parser. You write a ConfigMap value; YAML 1.1 decides what it means; what reaches the cluster is not what you wrote. And in at least one documented case, the API server doesn’t reject the bad value — it silently drops the key.
This is the famous “Norway problem”: country: NO is not a string to a YAML 1.1 parser. It’s false. Kubernetes has carried this quirk for years. With 1.37, the ecosystem finally has a canonical answer: KYAML, a strict YAML subset that quotes every string and derives structure from braces and brackets, is now stable (KEP-5295) and available as a kubectl output format.
Here is exactly how the bug bites, the two different decode paths it travels, and how to adopt KYAML without breaking anything.
How the bug bites
Kubernetes parses YAML using YAML 1.1 implicit typing — a specification old enough that its boolean list includes words like y, n, yes, no, on, off, and case variants of all of them. It also decodes 010 as octal (the number 8) and treats null and ~ as null. Write any of these unquoted in a manifest and the parser “helpfully” converts them.
A community experiment against a real 1.37 API server fed thirteen plausible ConfigMap values through kubectl apply. The results:
-
NO,no,Yes,OFF,y,n→ booleans ("false","true") -
010→8(octal decode) -
1.0→1(wrong numeric tag resolved) -
nulland~→ the key disappeared from the stored object entirely
Thirteen values in. One survived unchanged.
Thirteen plausible values in, one survivor out. KYAML quotes every string at the source.
Two decode paths, two failure modes
The API server is strict. Everything else in the ecosystem often isn’t.
The subtle part is that kubectl apply and the rest of the ecosystem don’t decode your YAML the same way — and they fail differently.
Path 1: kubectl apply → the API server. The API server decodes strictly, through JSON typing. A boolean or number landing in a map[string]string field (like ConfigMap.data) is a type mismatch, and the server rejects it loudly with a BadRequest. Nothing is written. That path is actually safe — annoying, but safe.
But null and ~ create no type mismatch to reject. Null is a legal value for a string field’s absence — so the key is quietly dropped from the stored object. No error, no warning. And because the corruption happens inside the parser, kubectl apply --dry-run and kubectl diff see the same corrupted input your real apply would — they can’t warn you about it either.
Path 2: controllers and CLIs decode directly. Plenty of ecosystem tooling — operators, GitOps controllers, custom CLIs — unmarshals YAML straight into Go maps with sigs.k8s.io/yaml, no strict API server in the loop. There, the same values are silently mangled: NO becomes the string "false", 010 becomes "8", 1.0 becomes "1", and null/~ become empty strings. In the documented experiment, 11 of 14 values changed without any error.
This is why the bug survives code review. The manifest on screen says NO. The value in the cluster says false. Nothing between your editor and etcd ever flagged the difference.
The manifest on screen says
NO. The value in the cluster saysfalse. Nothing between your editor and etcd ever flagged the difference.
KYAML: the fix at the source
KYAML (KEP-5295) is a strict subset of YAML — think of it as the format halfway between YAML and JSON. The rules are simple and mechanical:
- Every string value is quoted.
country: "NO"can never becomefalse, because quoted scalars are never subject to implicit typing. - Structure comes from braces and brackets —
data: { country: "NO" }— so indentation can never silently change the shape of your document. - Comments and trailing commas are allowed, so it stays human-editable, unlike raw JSON.
It’s still plain YAML underneath: any YAML 1.1/1.2 parser reads it, and it applies on any kubectl version. The implementation lives in sigs.k8s.io/yaml and the Kubernetes core, and KEP-5295 promoted the -o kyaml output format to stable in Kubernetes 1.37.
Converting is one command:
# See your live objects the way KYAML would write them kubectl get configmap app-config -o kyaml # Canonicalize a manifest file kubectl create configmap app-config --from-file=config.yaml \ --dry-run=client -o kyaml > app-config.kyaml.yaml For bulk conversion, Google’s yamlfmt ships a KYAML formatter (yamlfmt --kyaml manifest.yaml) using the official KYAML encoder, plus a pre-commit hook and a Docker image for CI.
Watch out: KYAML only protects what it generates or what you write in its style. Hand-editing a KYAML file and dropping the quotes reintroduces the bug — the safety is in the quoting, not the file extension. Lint for unquoted scalars in CI if your team hand-edits manifests.
Adoption playbook
The four steps form a pipeline: every manifest, whatever its source, gets canonicalized to KYAML, gated in CI, and only then deployed. Quoting happens at generation time — humans never hand-type the risky values.
KYAML adoption pipeline: sources to canonicalization to CI gate to deployment.
- 1. Read: Use
-o kyamlfor inspection and debugging — Output-only; changes nothing in the cluster - 2. Canonicalize: Re-emit stored manifests through
-o kyamlinto your repo — Semantically identical; diff shows only quoting/structure changes - 3. Gate: Add a CI check that flags unquoted
NO/yes/on/off-style scalars and bare~— Catches new occurrences before they reach any decode path - 4. Generate: Emit KYAML from Helm/Kustomize/template tooling — Quoting happens at generation; humans never hand-type risky values
The CI gate from step 3 can be a two-line shell check — quoted values are correctly ignored:
# Fail the build on unquoted risky scalars or a bare ~ grep -rEn ':[[:space:]]*(NO|no|Yes|yes|YES|On|on|ON|Off|off|OFF|y|n|Y|N)[[:space:]]*(#|$)' manifests/ && exit 1 grep -rEn ':[[:space:]]*~[[:space:]]*(#|$)' manifests/ && exit 1 Before you roll it out:
- [ ] Confirm your kubectl is 1.37+ for
-o kyaml(older clients apply KYAML files fine — they just can’t generate them). - [ ] Diff the canonicalized output against what’s in the cluster — expect quote/brace noise only, no semantic changes.
- [ ] Check GitOps tooling (Argo CD, Flux) renders the manifests identically — KYAML is valid YAML, but verify your specific versions.
- [ ] Prioritize ConfigMaps and Secrets:
map[string]stringfields are where coercion does the most damage.
The takeaway
This bug lived in the open for years because every layer assumed another layer had validated the types. The parser assumed the author meant a boolean; the API server assumed the client sent a string; the reviewer assumed the diff showed the truth. KYAML breaks the chain of assumptions by making types explicit at the source — in the text you actually read.
Quote your strings. Brace your maps. Let the tooling do it for you from 1.37 on.
Honey Kiran · DevOps & Platform Engineering
I write about Kubernetes, Terraform, SRE, and the unsexy work that keeps systems alive at 3am.
Go grep your manifests for unquoted NO, yes, on, off, and ~ today — then send this to the teammate who still hand-writes YAML.
Disclaimer
This post describes my personal experience with my own systems and accounts, in my own words. It is a first-person account of what I observed and what I do in my own work — not a statement of any company's intentions, policies, or internal decisions. Where I do not know something, the post says so.
I am not affiliated with, endorsed by, or speaking for any company, product, or vendor named in this post. All product and company names are trademarks of their respective owners and are mentioned only to describe the experience accurately.
Any error messages, documentation excerpts, or figures quoted here are reproduced from my own records or from the public sources linked in the post, believed accurate at the time of writing. Product behavior changes over time; verify against current official documentation before relying on any detail.
Nothing in this post is professional, financial, legal, or security advice. Examples are illustrative — validate in your own environment before running anything in production.
Corrections are welcome: if a company named here publishes an official statement, or a detail above is wrong, I will update this post and note the correction. The goal is an accurate public record of my experience, not a verdict on anyone.
© 2026 Honey Kiran. All rights reserved.






