Back to Blog
high SEVERITY8 min read

How Quadratic CPU Consumption happens in JavaScript YAML parsing and how to fix it

A high-severity denial-of-service vulnerability (GHSA-5p4m-2wfm-xmqj) in js-yaml versions 4.3.0 and 3.x caused quadratic CPU consumption when resolving `!!omap` (ordered map) types in YAML documents. Attackers who could supply crafted YAML input could cause CPU exhaustion proportional to the square of the input size, potentially grinding Node.js services to a halt. The fix upgrades js-yaml to 4.3.1 and pins the version via a `package.json` overrides block to ensure no transitive dependency can r

O
By Orbis AppSec
Published August 9, 2026Reviewed August 9, 2026

Answer Summary

GHSA-5p4m-2wfm-xmqj is a high-severity algorithmic complexity vulnerability (CWE-407) in the JavaScript library js-yaml, affecting versions 4.3.0 and 3.x. When parsing YAML documents containing the `!!omap` (ordered map) tag, the library's resolution logic performed a duplicate-key check using a nested loop, resulting in O(n²) CPU time relative to the number of entries. This means an attacker who can supply crafted YAML input can cause exponential CPU exhaustion in any Node.js application that parses untrusted YAML. The fix upgrades js-yaml to 4.3.1 in `package-lock.json` and adds a `"overrides"` block in `package.json` to pin the safe version across the entire dependency tree.

Vulnerability at a Glance

cweCWE-407 (Inefficient Algorithmic Complexity)
fixUpgrade js-yaml from 4.3.0 to 4.3.1 and add a package.json overrides block to pin the safe version
riskCPU exhaustion leading to denial of service in any service parsing untrusted YAML
languageJavaScript / Node.js
root causejs-yaml's !!omap type resolver performed an O(n²) duplicate-key check during ordered map construction
vulnerabilityAlgorithmic Complexity / Quadratic CPU Consumption (ReDoS-class DoS)

The Hidden Cost of Parsing Ordered Maps: js-yaml's Quadratic CPU Bug

When your Node.js service parses a YAML configuration file or API payload, you probably assume the operation takes time proportional to the size of the document — parse 100 keys, spend roughly 100 units of work. But a subtle flaw in js-yaml's handling of the !!omap (ordered map) type tag broke that assumption entirely. A crafted YAML document with enough ordered-map entries could force the parser to spend quadratic time — 10,000 units of work for 100 entries, 1,000,000 units for 1,000 entries — turning a routine parse call into a CPU-exhausting denial-of-service vector.

This post breaks down exactly what went wrong, how the fix works, and what you can do to protect your own applications.


The Vulnerability Explained

What is !!omap?

YAML supports a rich set of type tags. The !!omap tag represents an ordered mapping — a sequence of key-value pairs where insertion order is preserved and duplicate keys are forbidden. A valid !!omap document looks like this:

!!omap
- alpha: 1
- beta: 2
- gamma: 3

The js-yaml library must validate this structure: it needs to confirm that no key appears more than once. That duplicate-detection logic is where the vulnerability lives.

The O(n²) Duplicate-Key Check

In js-yaml 4.3.0 (and the 3.x line prior to the fix), the !!omap type resolver iterated over every entry in the ordered map and, for each entry, scanned all previously seen entries to check for a duplicate key. In pseudocode:

// Vulnerable pattern (conceptual — pre-fix behaviour)
for (let i = 0; i < pairs.length; i++) {
  for (let j = 0; j < i; j++) {
    if (pairs[j].key === pairs[i].key) {
      throw new Error('duplicate key');
    }
  }
}

This is a classic O(n²) nested loop. For a document with n key-value pairs:

Entries (n) Comparisons (n²)
100 10,000
1,000 1,000,000
10,000 100,000,000
100,000 10,000,000,000

An attacker who can submit YAML input to your application — via a configuration endpoint, a file upload, a webhook payload, or any other surface — can craft a single !!omap document with tens of thousands of unique keys and peg one CPU core at 100% for seconds or minutes per request.

A Concrete Attack Scenario

Imagine a Node.js service that accepts YAML-formatted rule definitions from authenticated users (a CI/CD platform, a network policy editor, an infrastructure-as-code tool). The route handler calls:

const yaml = require('js-yaml');
app.post('/rules', (req, res) => {
  const rules = yaml.load(req.body.yaml); // ← vulnerable in js-yaml 4.3.0
  applyRules(rules);
  res.json({ ok: true });
});

A malicious user submits:

!!omap
- key_0000001: value
- key_0000002: value
# ... 50,000 more unique keys ...
- key_0050000: value

The yaml.load() call triggers the quadratic duplicate-check loop. With 50,000 entries, the resolver performs ~1.25 billion comparisons. On a modern server this can take 30–60 seconds of pure CPU time — per request. Even a handful of concurrent requests can saturate all available CPU cores, denying service to legitimate users.

Because the vulnerability is in the parsing stage, no application-level business logic needs to be reached. The damage is done before applyRules() is ever called.


The Fix

What Changed in package-lock.json

The pull request makes a targeted, two-file change. In package-lock.json, the pinned version of node_modules/js-yaml moves from the vulnerable 4.3.0 to the patched 4.3.1:

 "node_modules/js-yaml": {
-  "version": "4.3.0",
-  "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
-  "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
+  "version": "4.3.1",
+  "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
+  "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",

The integrity hash change confirms this is a genuinely different package artifact, not just a metadata update.

Why the package.json Overrides Block Matters

Updating package-lock.json alone protects the direct dependency, but js-yaml is a popular library that frequently appears as a transitive dependency — pulled in by tools like webpack, jest, eslint, or dozens of other packages. Without an explicit override, npm install could silently resolve a transitive path back to 4.3.0.

The fix adds an overrides block to package.json to prevent this:

+  "overrides": {
+    "js-yaml": "4.3.1"
+  }

This npm v8+ feature tells the package manager: regardless of what any dependency requests, always resolve js-yaml to 4.3.1. It is a belt-and-suspenders measure that makes the fix durable across future npm install runs and dependency tree changes.

How 4.3.1 Fixes the Algorithm

js-yaml 4.3.1 replaces the nested-loop duplicate check with a Set-based lookup, which has O(1) average-case insertion and membership testing:

// Fixed pattern (post-4.3.1 behaviour)
const seenKeys = new Set();
for (const pair of pairs) {
  if (seenKeys.has(pair.key)) {
    throw new Error('duplicate key');
  }
  seenKeys.add(pair.key);
}

The total work is now O(n) — linear in the number of entries. The same 50,000-entry !!omap document that previously triggered billions of comparisons now requires exactly 50,000 Set operations. The attack surface collapses entirely.


Prevention & Best Practices

1. Pin and Override Transitive Dependencies

Direct dependencies are only part of the story. Use npm's overrides (npm ≥ 8), Yarn's resolutions, or pnpm's overrides to force safe versions of security-sensitive libraries across your entire dependency tree.

// package.json
{
  "overrides": {
    "js-yaml": "4.3.1"
  }
}

2. Enforce Input Size Limits Before Parsing

Even with a patched library, applying a size cap before handing untrusted data to any parser is good defence-in-depth:

const MAX_YAML_BYTES = 1_000_000; // 1 MB
if (Buffer.byteLength(rawInput) > MAX_YAML_BYTES) {
  return res.status(413).json({ error: 'Payload too large' });
}
const parsed = yaml.load(rawInput);

3. Use Automated Dependency Scanning

This vulnerability was detected by Trivy, a container and filesystem vulnerability scanner. Integrate similar tools into your CI pipeline:

  • Trivy — scans package-lock.json, container images, and IaC files
  • npm audit — built-in, catches advisories in the npm registry
  • Dependabot / Renovate — automated PRs when new patched versions are released
  • Semgrep — rule-based static analysis; see the js-yaml Semgrep rules

4. Understand Algorithmic Complexity Vulnerabilities

Quadratic-complexity bugs are often invisible in testing because test inputs are small. Consider:

  • Fuzz testing with large, structured inputs to surface O(n²) behaviour
  • Profiling YAML/JSON/XML parse paths under load
  • Treating any parser that touches untrusted input as a potential DoS vector

5. OWASP and CWE Alignment

This vulnerability maps to:

  • CWE-407: Inefficient Algorithmic Complexity
  • OWASP A05:2021 — Security Misconfiguration (using outdated, vulnerable library versions)
  • OWASP A06:2021 — Vulnerable and Outdated Components

Key Takeaways

  • !!omap is a legitimate YAML attack surface: Any application that parses YAML from untrusted sources and uses js-yaml 4.3.0 or 3.x is exposed to CPU exhaustion via crafted ordered-map documents.
  • The package-lock.json version alone is not enough: Without the "overrides" block in package.json, future npm install runs can silently reintroduce the vulnerable 4.3.0 through transitive dependencies.
  • O(n²) bugs are invisible at test scale: The duplicate-key loop in js-yaml's !!omap resolver looked correct and passed all unit tests — the problem only manifests with large inputs designed to trigger worst-case behaviour.
  • Set-based lookups are the right fix for uniqueness checks: Replacing the nested array scan with a Set drops the complexity from O(n²) to O(n) and eliminates the attack entirely.
  • Integrity hashes in package-lock.json are your tamper-evidence: The SHA-512 change from sha512-1td788... to sha512-CY6crG... confirms you are running genuinely different (patched) code, not just a metadata change.

How Orbis AppSec Detected This

  • Source: Untrusted YAML content supplied to yaml.load() calls anywhere in the application or its dependency chain that processes external input.
  • Sink: The !!omap type resolver inside node_modules/js-yaml (version 4.3.0 as recorded in package-lock.json), which performed a quadratic duplicate-key scan during ordered-map construction.
  • Missing control: No algorithmic complexity guard existed in the !!omap resolver; the library used a nested array iteration instead of a constant-time Set lookup, and no input-size limit was enforced upstream.
  • CWE: CWE-407 — Inefficient Algorithmic Complexity
  • Fix: package-lock.json was updated to resolve node_modules/js-yaml to version 4.3.1, and a "overrides": { "js-yaml": "4.3.1" } block was added to package.json to pin the patched version across all transitive dependency paths.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

GHSA-5p4m-2wfm-xmqj is a reminder that denial-of-service vulnerabilities don't always look dramatic. There is no memory corruption, no remote code execution, no leaked secrets — just a nested loop that grows quadratically and a YAML tag that most developers have never typed. Yet the impact is real: a single HTTP request carrying a crafted !!omap document can saturate a CPU core for tens of seconds, and a handful of concurrent requests can take down a service entirely.

The fix is surgical: two files changed, one version bumped, one overrides entry added. The patched algorithm is O(n) instead of O(n²), and the overrides block ensures the fix survives future dependency tree changes. If your project depends on js-yaml — directly or transitively — verify you are on 4.3.1 (or 3.15.1 for the 3.x line) today.


References

Frequently Asked Questions

What is quadratic CPU consumption in YAML parsing?

It is a class of algorithmic complexity vulnerability where the time to process input grows as the square of the input size (O(n²)). In js-yaml's !!omap resolver, checking for duplicate keys in an ordered map was done with a nested loop, so doubling the number of entries quadrupled the processing time.

How do you prevent algorithmic complexity vulnerabilities in JavaScript?

Use data structures with O(1) or O(log n) lookup (such as a Set or Map) instead of linear array scans inside loops, enforce input size limits before parsing, and keep parsing libraries up to date so patched algorithms are in use.

What CWE is quadratic CPU consumption?

CWE-407 — Inefficient Algorithmic Complexity. This CWE covers cases where an algorithm's resource usage grows faster than linearly with input size, enabling denial-of-service attacks through crafted inputs.

Is rate-limiting enough to prevent this vulnerability?

Rate-limiting reduces the attack surface but does not eliminate it. A single large crafted YAML document can still consume excessive CPU before a rate limit triggers. The correct fix is patching the library so the algorithm itself is efficient.

Can static analysis detect this vulnerability?

Yes. Tools like Trivy (which detected this issue) and Semgrep can identify known-vulnerable versions of js-yaml in package-lock.json and package.json. Semgrep rules targeting !!omap parsing patterns can also flag custom YAML handling code with similar algorithmic issues.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #22

Related Articles

high

How Quadratic CPU Consumption Vulnerabilities Happen in JavaScript YAML Parsers and How to Fix Them

A high-severity denial-of-service vulnerability in js-yaml versions 3.x and 4.x allowed attackers to trigger quadratic CPU consumption through specially crafted YAML documents using the !!omap tag. This fix upgrades js-yaml from 4.1.1 to 4.3.1 and from 3.14.2 to 3.15.1, eliminating the algorithmic complexity attack vector that could freeze Node.js applications processing untrusted YAML input.

high

How javascript.lang.security.detect-child-process.detect-child-process happens in Node.js and how to fix it

A high-severity command injection vulnerability was discovered in `scripts/build.js` where `execSync` was called with string-interpolated arguments (`sourceDir` and `outputPath`) inside a shell command. By replacing `execSync` with `spawnSync` using an argument array (no shell), the fix eliminates the possibility of shell metacharacter injection while preserving identical build behavior.

high

How Command Injection happens in Node.js child_process and how to fix it

A command injection vulnerability in nix.js's Release class allowed potentially malicious input through the `arch` parameter to be executed via shell commands. The fix replaced `execSync()` with `execFileSync()`, eliminating shell interpretation and preventing command injection by passing arguments as an array instead of a concatenated string.

critical

How Cross-Site Scripting (XSS) happens in JavaScript innerHTML and how to fix it

A critical Cross-Site Scripting (XSS) vulnerability was discovered in `js/main.js` where commit messages fetched from the GitHub API were directly interpolated into `innerHTML` without any sanitization. An attacker with repository write access could push a commit with a malicious message like `<img src=x onerror=alert(document.cookie)>`, causing arbitrary JavaScript execution in every visitor's browser. The fix applies HTML entity encoding to all five dangerous characters before rendering.

high

How Quadratic CPU Consumption in YAML Parsing happens in JavaScript and how to fix it

A high-severity vulnerability in js-yaml versions 3.x and 4.x allowed attackers to cause quadratic CPU consumption through specially crafted YAML documents using the `!!omap` type. This denial-of-service vulnerability (GHSA-5p4m-2wfm-xmqj) was fixed by upgrading from js-yaml 4.3.0 to 4.3.1, protecting applications from algorithmic complexity attacks during YAML parsing.

high

How Arbitrary HTTP Header Injection via Prototype Pollution happens in JavaScript and how to fix it

A high-severity vulnerability (CVE-2026-42035) in axios version 1.13.5 allowed attackers to inject arbitrary HTTP headers through prototype pollution. The fix upgrades axios to version 1.18.0 in the frontend's dependency tree, which includes proper prototype chain validation when constructing HTTP request headers. This prevents attackers from manipulating outgoing requests to perform SSRF, session hijacking, or cache poisoning attacks.