Back to Blog
high SEVERITY6 min read

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.

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

Answer Summary

GHSA-5p4m-2wfm-xmqj is a denial-of-service vulnerability in the js-yaml JavaScript library affecting versions 3.x (before 3.15.1) and 4.x (before 4.3.1). The vulnerability allows quadratic CPU consumption when parsing YAML documents containing the !!omap (ordered map) tag due to inefficient duplicate key detection. The fix requires upgrading js-yaml to version 4.3.1 or 3.15.1, which implements linear-time duplicate checking algorithms.

Vulnerability at a Glance

cweCWE-407
fixUpgrade js-yaml to 4.3.1 (or 3.15.1 for 3.x branch)
riskApplication freeze or crash when processing malicious YAML input
languageJavaScript
root causeQuadratic time complexity in !!omap duplicate key resolution
vulnerabilityAlgorithmic Complexity / Denial of Service

Introduction

In a Node.js project's package-lock.json, we discovered a high-severity denial-of-service vulnerability lurking in the js-yaml dependency. The project was using js-yaml version 4.1.1 as a direct dependency and version 3.14.2 as a transitive dependency through @istanbuljs/load-nyc-config. Both versions contained GHSA-5p4m-2wfm-xmqj, a vulnerability that allows attackers to freeze applications by sending specially crafted YAML documents.

This isn't a theoretical risk—any application that parses YAML from untrusted sources (configuration files, API payloads, user uploads) could be completely locked up by a relatively small malicious input. The vulnerability stems from how js-yaml resolves the !!omap (ordered map) YAML tag, using an algorithm with quadratic time complexity that an attacker can exploit.

The Vulnerability Explained

What is the !!omap Tag?

YAML supports ordered maps through the !!omap tag, which guarantees that keys maintain their insertion order. When js-yaml parses an !!omap, it must verify that all keys are unique—a fundamental requirement for valid ordered maps.

The Quadratic Complexity Problem

The vulnerable versions of js-yaml (4.1.1 and 3.14.2) used an inefficient algorithm for duplicate key detection. Instead of using a hash-based lookup (O(1) per check, O(n) total), the implementation compared each new key against all previously seen keys (O(n) per check, O(n²) total).

Here's what the problematic pattern looks like conceptually:

// Vulnerable approach (simplified)
function checkDuplicates(keys) {
  for (let i = 0; i < keys.length; i++) {
    for (let j = 0; j < i; j++) {
      if (keys[i] === keys[j]) {
        throw new Error('Duplicate key');
      }
    }
  }
}

With 1,000 keys, this performs ~500,000 comparisons. With 10,000 keys, it performs ~50,000,000 comparisons. An attacker can craft a YAML document that triggers this worst-case behavior.

Attack Scenario

Consider this application that processes user-submitted configuration:

const yaml = require('js-yaml'); // version 4.1.1

app.post('/api/config', (req, res) => {
  try {
    const config = yaml.load(req.body.yamlContent);
    // Process configuration...
    res.json({ success: true });
  } catch (e) {
    res.status(400).json({ error: 'Invalid YAML' });
  }
});

An attacker could submit a YAML document like:

--- !!omap
- key_00001: value
- key_00002: value
- key_00003: value
# ... thousands more unique keys ...
- key_50000: value

Even though all keys are unique (no actual duplicates), the quadratic duplicate-checking algorithm would consume massive CPU time verifying this. A document with 50,000 keys could lock up the Node.js event loop for minutes, effectively causing a denial of service for all users.

Real-World Impact

For this specific project, the vulnerability existed in two places:

  1. Direct dependency: js-yaml@4.1.1 used for parsing application configuration
  2. Dev dependency: js-yaml@3.14.2 pulled in by @istanbuljs/load-nyc-config for code coverage configuration

While the dev dependency only affects the build/test environment, the direct dependency poses a production risk if the application processes any YAML from external sources.

The Fix

The fix upgrades both vulnerable js-yaml instances to patched versions that implement efficient O(n) duplicate detection.

Before (Vulnerable)

// package.json
"dependencies": {
  "js-yaml": "^4.1.1",
  // ...
}

// package-lock.json
"node_modules/js-yaml": {
  "version": "4.1.1",
  "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
  // ...
}

"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
  "version": "3.14.2",
  "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
  // ...
}

After (Fixed)

// package.json
"dependencies": {
  "js-yaml": "^4.3.1",
  // ...
}

// 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==",
  // ...
}

"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
  "version": "3.15.1",
  "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz",
  "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==",
  // ...
}

Why Both Files Changed

  1. package.json: Updates the declared dependency range from ^4.1.1 to ^4.3.1, ensuring new installs get the patched version
  2. package-lock.json: Locks the exact resolved versions (4.3.1 and 3.15.1) and updates integrity hashes, ensuring reproducible builds with the secure versions

The patched versions replace the O(n²) comparison loop with a hash-based Set lookup:

// Fixed approach (simplified)
function checkDuplicates(keys) {
  const seen = new Set();
  for (const key of keys) {
    if (seen.has(key)) {
      throw new Error('Duplicate key');
    }
    seen.add(key);
  }
}

This reduces 50,000,000 operations back down to 50,000—a 1000x improvement that eliminates the denial-of-service vector.

Prevention & Best Practices

1. Keep Dependencies Updated

Use automated dependency scanning tools to catch vulnerable packages:

# npm audit
npm audit

# Or use dedicated security scanners
trivy fs --scanners vuln .

2. Implement Parsing Timeouts

Even with patched libraries, defense in depth matters:

const { Worker } = require('worker_threads');

function parseYamlWithTimeout(content, timeoutMs = 5000) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(`
      const yaml = require('js-yaml');
      const { parentPort } = require('worker_threads');
      parentPort.on('message', (content) => {
        parentPort.postMessage(yaml.load(content));
      });
    `, { eval: true });

    const timeout = setTimeout(() => {
      worker.terminate();
      reject(new Error('YAML parsing timeout'));
    }, timeoutMs);

    worker.on('message', (result) => {
      clearTimeout(timeout);
      resolve(result);
    });

    worker.postMessage(content);
  });
}

3. Limit Input Size

Reject oversized YAML documents before parsing:

const MAX_YAML_SIZE = 1024 * 1024; // 1MB

app.post('/api/config', (req, res) => {
  if (req.body.yamlContent.length > MAX_YAML_SIZE) {
    return res.status(413).json({ error: 'YAML document too large' });
  }
  // Parse YAML...
});

4. Use Dependabot or Renovate

Configure automated dependency updates to catch security patches quickly:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 10

Key Takeaways

  • js-yaml versions before 4.3.1 and 3.15.1 contain a DoS vulnerability in !!omap resolution that can freeze your application
  • Transitive dependencies matter: The vulnerability existed in both direct and dev dependencies through @istanbuljs/load-nyc-config
  • Algorithmic complexity attacks bypass input validation: The malicious YAML is syntactically valid, so schema validation won't help
  • Both package.json AND package-lock.json must be updated to ensure the fix is applied consistently across environments
  • Defense in depth is essential: Even with patched libraries, implement timeouts and size limits for parsing untrusted input

How Orbis AppSec Detected This

  • Source: YAML content from external input (configuration files, API payloads)
  • Sink: yaml.load() call in application code using vulnerable js-yaml versions
  • Missing control: No patched version of js-yaml that implements efficient duplicate key detection
  • CWE: CWE-407 (Inefficient Algorithmic Complexity)
  • Fix: Upgraded js-yaml from 4.1.1 to 4.3.1 and from 3.14.2 to 3.15.1 in package.json and package-lock.json

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

The js-yaml quadratic CPU consumption vulnerability (GHSA-5p4m-2wfm-xmqj) demonstrates how algorithmic complexity issues can turn seemingly innocent parsing operations into denial-of-service vectors. By upgrading to js-yaml 4.3.1 (or 3.15.1 for the 3.x branch), you eliminate this attack surface while maintaining full backward compatibility for valid YAML documents.

Remember that dependency security is an ongoing process. Automated scanning tools like Trivy, combined with proactive dependency management, help ensure vulnerabilities like this are caught and fixed before they can be exploited. Always treat YAML parsing of untrusted input as a potential attack vector and implement appropriate safeguards.

References

Frequently Asked Questions

What is algorithmic complexity vulnerability?

An algorithmic complexity vulnerability occurs when an algorithm's worst-case performance is significantly worse than expected, allowing attackers to craft inputs that consume excessive CPU time or memory, causing denial of service.

How do you prevent algorithmic complexity attacks in JavaScript?

Use libraries with proven O(n) or O(n log n) algorithms for parsing, implement input size limits, set timeouts for parsing operations, and keep dependencies updated to patched versions.

What CWE is algorithmic complexity vulnerability?

CWE-407 (Inefficient Algorithmic Complexity) covers vulnerabilities where algorithms have worst-case complexity that can be exploited to cause denial of service.

Is input validation enough to prevent YAML DoS attacks?

No, input validation alone cannot prevent algorithmic complexity attacks because the malicious payload may appear syntactically valid. You must use patched library versions with efficient algorithms.

Can static analysis detect algorithmic complexity vulnerabilities?

Static analysis tools can detect known vulnerable library versions through dependency scanning (like Trivy), but detecting novel algorithmic complexity issues in custom code requires specialized analysis.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #753

Related Articles

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 Sensitive Data Exposure in Error Logging happens in TypeScript/Deno and how to fix it

A critical vulnerability in Supabase Edge Functions allowed sensitive authentication errors and API credentials to leak through verbose error logging. The `cancel-subscription/index.ts` function logged full error objects to the console, potentially exposing Paddle API keys and auth tokens in deployment logs. The fix sanitizes all error messages to log only safe error text while preserving debugging capability.

critical

How HTTP Header Injection Happens in Go and How to Fix It

A critical vulnerability in the file upload handler allowed attackers to inject CRLF sequences into HTTP response headers through crafted filenames. The fix sanitizes user-supplied filenames before using them in Content-Disposition headers, preventing header injection attacks that could lead to cache poisoning, session fixation, or XSS.

high

How Path Traversal and Security Policy Bypass Happens in Node.js Dependencies and How to Fix It

A high-severity vulnerability in the fast-uri package (CVE-2026-6321) allowed attackers to bypass security policies through improper Unicode hostname canonicalization and path traversal. This issue affected the @apralabs/apra-fleet project through its dependency tree, and was resolved by upgrading fast-uri from version 3.1.0 to 4.1.2 using npm overrides.

high

How Email Exhaustion Denial of Service Happens in Node.js OTP Endpoints and How to Fix It

A Node.js authentication service exposed unauthenticated OTP endpoints without adequate rate limiting, allowing attackers to exhaust email service quotas through repeated requests. The fix implements per-session resend caps and cooldown enforcement to prevent email-based denial of service attacks while preserving legitimate user workflows.