# Safety notes for untrusted input The YAML spec by design allows compact documents that produce objects which are expensive to materialize. Guard against this when the input is untrusted. ### 1. Limit input size Limit the input size to the smallest acceptable value. ### 2. Catch all exceptions `load()` throws on malformed input. Always wrap it in `try/catch` and treat any error as a rejected document. ### 3. Traverse with a node limit before materializing Aliases let a tiny document expand into a huge object graph (the "billion laughs" pattern) and can even create circular references. It stays cheap until you fully walk it (`JSON.stringify`, deep clone, recursive validation), so do a cheap pass first that counts every element and bails out past a limit. The walk is a flat loop over a manual stack — no recursion, so deep documents can't overflow the call stack. Counting happens as each element is discovered, so the stack never grows past the limit: ```js // plain `{}` or `Object.create(null)`, but not Date / Uint8Array / etc. function isContainer(o) { if (Array.isArray(o)) return true if (!o || typeof o !== 'object') return false const proto = Object.getPrototypeOf(o) return proto === Object.prototype || proto === null } function guardNodeCount(root, limit) { let count = 0 const stack = [ root ] while (stack.length) { const node = stack.pop() for (const key in node) { if (++count > limit) throw new Error('Too many nodes') const value = node[key] if (isContainer(value)) stack.push(value) } } } const data = yaml.load(input) guardNodeCount(data, 100000) const json = JSON.stringify(data) ``` Note: aliases that point to the same node are counted every time they appear (matching the real materialization cost), and a cyclic document keeps producing elements until it hits the limit — both are rejected as intended.