Back to Blog
high SEVERITY6 min read

How Quadratic CPU Consumption Happens in js-yaml and How to Fix It

A high-severity denial-of-service vulnerability in js-yaml versions prior to 4.3.1 allowed attackers to craft malicious YAML documents with !!omap tags that triggered quadratic CPU consumption during parsing. This fix upgrades js-yaml from 4.1.1 to 4.3.1 using npm overrides, protecting applications from algorithmic complexity attacks that could freeze or crash Node.js services.

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

Answer Summary

GHSA-5p4m-2wfm-xmqj is a denial-of-service vulnerability in the js-yaml library (versions 3.x before 3.15.1 and 4.x before 4.3.1) affecting Node.js applications. The vulnerability stems from quadratic time complexity in the !!omap (ordered map) type resolution, allowing attackers to craft YAML payloads that cause excessive CPU consumption. The fix involves upgrading js-yaml to version 4.3.1 or 3.15.1 via package.json overrides.

Vulnerability at a Glance

cweCWE-407
fixUpgrade js-yaml to 4.3.1 or 3.15.1
riskService unavailability through CPU exhaustion
languageJavaScript/Node.js
root causeQuadratic time complexity in !!omap YAML type resolution
vulnerabilityAlgorithmic Complexity / Denial of Service

Introduction

The package-lock.json file in this project pinned js-yaml at version 4.1.1, a widely-used YAML parser for Node.js applications. While js-yaml handles configuration files, API responses, and data serialization across countless applications, a flaw in its !!omap (ordered map) type resolution created a severe denial-of-service vulnerability. An attacker who could supply YAML input to the application could craft a document that would cause the parser to consume quadratic CPU time, effectively freezing the Node.js event loop.

This vulnerability, tracked as GHSA-5p4m-2wfm-xmqj and related to CVE-2026-59870, affects both the 3.x and 4.x branches of js-yaml. The fix was not automatically backported to older versions, leaving applications on version 4.1.1 exposed until explicitly upgraded.

The Vulnerability Explained

What is !!omap in YAML?

YAML supports custom type tags that instruct parsers how to interpret data. The !!omap tag represents an ordered map—a sequence of key-value pairs where order matters. Here's what valid !!omap YAML looks like:

!!omap
- first: 1
- second: 2
- third: 3

The Quadratic Complexity Problem

The vulnerable versions of js-yaml (prior to 4.3.1 and 3.15.1) implemented the !!omap resolution with an algorithm that had O(n²) time complexity. When parsing an ordered map, the code would perform nested iterations to validate uniqueness of keys or maintain ordering guarantees. For each of the n entries, it would iterate through up to n other entries.

For small inputs, this is imperceptible. But consider what happens with malicious input:

Input Size Operations (Linear) Operations (Quadratic)
100 items 100 10,000
1,000 items 1,000 1,000,000
10,000 items 10,000 100,000,000

An attacker could craft a YAML document with thousands of !!omap entries. When parsed by the vulnerable js-yaml version, this would:

  1. Block the Node.js event loop during parsing
  2. Consume 100% CPU on the parsing thread
  3. Prevent the application from handling any other requests
  4. Potentially trigger watchdog timeouts or container restarts

Attack Scenario

Imagine this application accepts YAML configuration uploads or processes YAML-formatted webhook payloads. An attacker submits:

!!omap
- key0: value0
- key1: value1
- key2: value2
# ... repeated 10,000 times
- key9999: value9999

The js-yaml 4.1.1 parser begins processing this document. Due to the quadratic resolution algorithm, parsing takes exponentially longer as the document grows. A document that would parse in milliseconds with a linear algorithm now takes minutes or hours, effectively denying service to all users.

The Fix

What Changed

The fix involves two coordinated changes to upgrade js-yaml from 4.1.1 to 4.3.1:

Before (package-lock.json):

"node_modules/js-yaml": {
  "version": "4.1.1",
  "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
  "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",

After (package-lock.json):

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

Before (package.json overrides):

"overrides": {
  "basic-ftp": "5.3.1"
}

After (package.json overrides):

"overrides": {
  "basic-ftp": "5.3.1",
  "js-yaml": "4.3.1"
}

Why the Override?

The overrides field in package.json is crucial here. js-yaml might be a transitive dependency—pulled in by other packages in the dependency tree. Simply updating a direct dependency wouldn't force nested dependencies to use the patched version. The override ensures that every instance of js-yaml in the entire dependency tree uses version 4.3.1, regardless of what version other packages request.

How 4.3.1 Fixes the Issue

Version 4.3.1 of js-yaml includes a rewritten !!omap resolution algorithm with O(n) time complexity. The fix likely uses a hash-based data structure (like a JavaScript Set or Map) for key uniqueness checks instead of nested array iterations, reducing the algorithmic complexity from quadratic to linear.

Prevention & Best Practices

Dependency Management

  1. Use lockfile scanning: Tools like Trivy, Snyk, or npm audit can detect known vulnerable versions in your dependency tree
  2. Implement npm overrides: When transitive dependencies are vulnerable, use overrides to force patched versions
  3. Regular updates: Schedule regular dependency updates rather than waiting for security alerts

Input Handling

  1. Size limits: Implement maximum document size limits before parsing YAML
  2. Timeouts: Wrap parsing operations in timeouts to prevent indefinite blocking
  3. Sandboxing: Consider parsing untrusted YAML in worker threads or separate processes

Code Example: Safe YAML Parsing

const yaml = require('js-yaml'); // Must be 4.3.1+
const { setTimeout } = require('timers/promises');

async function safeYamlParse(input, maxSize = 1024 * 1024) {
  // Limit input size
  if (input.length > maxSize) {
    throw new Error('YAML document exceeds maximum size');
  }

  // Parse with timeout protection
  const parsePromise = new Promise((resolve, reject) => {
    try {
      resolve(yaml.load(input));
    } catch (e) {
      reject(e);
    }
  });

  const timeoutPromise = setTimeout(5000).then(() => {
    throw new Error('YAML parsing timeout');
  });

  return Promise.race([parsePromise, timeoutPromise]);
}

Key Takeaways

  • Transitive dependencies matter: The vulnerable js-yaml 4.1.1 was in the dependency tree, requiring npm overrides to ensure all instances were upgraded
  • Algorithmic complexity is a security concern: O(n²) algorithms on untrusted input create denial-of-service attack surfaces
  • YAML's type system expands attack surface: Custom type tags like !!omap introduce parsing complexity that can be exploited
  • Version pinning requires active maintenance: The lockfile pinned a specific version, but security patches require explicit upgrades
  • Defense in depth: Even with patched libraries, implement input size limits and parsing timeouts for untrusted data

How Orbis AppSec Detected This

  • Source: YAML document input parsed by the application (potentially from user uploads, API requests, or configuration files)
  • Sink: yaml.load() call using js-yaml 4.1.1 in the dependency tree
  • Missing control: Patched library version with linear-time !!omap resolution
  • CWE: CWE-407 (Inefficient Algorithmic Complexity)
  • Fix: Upgraded js-yaml to 4.3.1 via package.json overrides, ensuring all instances in the dependency tree use the patched version with O(n) resolution

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

This js-yaml vulnerability demonstrates how algorithmic complexity in trusted libraries can create severe security risks. The quadratic CPU consumption in !!omap resolution might seem like an obscure edge case, but it represents a real denial-of-service vector for any application processing untrusted YAML.

The fix was straightforward—a version upgrade with npm overrides—but required awareness that the vulnerability existed and affected transitive dependencies. This underscores the importance of automated dependency scanning and proactive security patching in modern JavaScript applications.

When working with YAML parsing or any data serialization format, always consider: What happens when an attacker controls the input? Libraries like js-yaml are battle-tested, but even well-maintained projects occasionally ship algorithmic vulnerabilities. Keep your dependencies updated, implement defense in depth, and never assume that parsing untrusted input is safe without additional protections.

References

Frequently Asked Questions

What is algorithmic complexity vulnerability?

An algorithmic complexity vulnerability occurs when an algorithm's time or space requirements grow disproportionately with input size, allowing attackers to craft inputs that consume excessive resources and cause denial of service.

How do you prevent algorithmic complexity attacks in Node.js?

Keep dependencies updated, implement input size limits, use timeouts for parsing operations, and prefer libraries with linear-time algorithms for handling untrusted input.

What CWE is algorithmic complexity vulnerability?

CWE-407 (Inefficient Algorithmic Complexity) covers vulnerabilities where algorithmic complexity allows resource consumption attacks.

Is input validation enough to prevent YAML DoS attacks?

Input validation helps but isn't sufficient alone—you must also use patched library versions since the vulnerability exists in the parser itself, not just in specific input patterns.

Can static analysis detect algorithmic complexity vulnerabilities?

Static analysis tools like Trivy and Snyk can detect known vulnerable dependency versions, but detecting novel algorithmic complexity issues typically requires dynamic analysis or manual code review.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #19

Related Articles

high

How Denial of Service via Memory Exhaustion happens in Socket.IO Parser and how to fix it

CVE-2026-69185 is a high-severity Denial of Service vulnerability in the `socket.io-parser` package that allows attackers to exhaust server memory by sending specially crafted packets. The fix upgrades `socket.io-parser` from version 4.2.4 to 4.2.7 (and parallel branches to 3.4.5 and 3.3.6) in `client/package-lock.json`, closing the attack surface against malicious clients. This kind of memory-exhaustion flaw is particularly dangerous in real-time applications where the parser handles a continuo

high

How express-check-csurf-middleware-usage happens in JavaScript/Express and how to fix it

A high-severity CSRF vulnerability was identified in `tower_game/index.js` where the Express application lacked any Cross-Site Request Forgery protection middleware. Without CSRF validation, an attacker could craft malicious pages that trick authenticated users into submitting unwanted requests to the game server. The fix adds `csurf` middleware with cookie-based token storage in just four lines of code.

critical

How Denial of Service via Gzip Bomb happens in Node.js and how to fix it

CVE-2026-59873 is a critical Denial of Service vulnerability in node-tar versions prior to 7.5.19, where a maliciously crafted gzip bomb can exhaust server resources when extracting archives. The fix upgrades the `tar` dependency from version 7.5.15 to 7.5.21 in `package-lock.json` and pins the version via an `overrides` block in `package.json`. Any application that processes user-supplied tar archives is at risk of resource exhaustion, making this an urgent upgrade.

high

How javascript.express.security.audit.express-check-csurf-middleware-usage.express-check-csurf-middleware-usage happens in Express.js and how to fix it

An Express.js application in `src/server.js` was missing CSRF (Cross-Site Request Forgery) protection middleware, leaving all state-changing endpoints vulnerable to forged requests from malicious sites. The fix introduces the `csrf` package to generate and validate tokens on non-GET requests, while exempting API-key-authenticated clients. This defensive hardening raises the bar against automated exploit chaining.

high

How Client-Side Denial of Service happens in Node.js FTP clients and how to fix it

CVE-2026-44240 is a client-side Denial of Service vulnerability in the `basic-ftp` Node.js package (versions prior to 5.3.1) caused by improper handling of unterminated multiline FTP server responses. An attacker controlling an FTP server—or capable of intercepting FTP traffic—could send a malformed response that causes the client to hang indefinitely. Upgrading `basic-ftp` to 5.3.1 and adding a package override in `package.json` closes the attack surface entirely.

high

How Denial of Service via Unbounded Brace Expansion Happens in Node.js Dependencies and How to Fix It

A critical vulnerability in adm-zip (CVE-2026-39244) allowed attackers to craft malicious ZIP files that trigger unbounded brace expansion, causing excessive memory allocation and process crashes. The CortexKit project fixed this by upgrading adm-zip from 0.5.17 to 0.6.0, which implements bounds checking on expansion operations. This vulnerability demonstrates why dependency management and timely security updates are essential for production Node.js applications.