Back to Blog
high SEVERITY7 min read

How Quadratic CPU Consumption in js-yaml's !!omap Resolution Happens in Node.js and How to Fix It

A high-severity algorithmic complexity vulnerability (GHSA-5p4m-2wfm-xmqj) in js-yaml versions 3.x and 4.x allowed attackers to trigger quadratic CPU consumption through crafted YAML input using the `!!omap` tag. The fix upgrades js-yaml from 4.1.1 to 4.3.1 in the Audex desktop music player, eliminating a denial-of-service vector that could freeze the Electron application when parsing untrusted YAML content.

O
By Orbis AppSec
Published September 1, 2026Reviewed September 1, 2026

Answer Summary

GHSA-5p4m-2wfm-xmqj is a high-severity algorithmic complexity vulnerability (CWE-400) in the js-yaml npm package (versions prior to 4.3.1 and 3.15.1) where parsing YAML documents containing crafted `!!omap` (ordered map) sequences triggers quadratic O(n²) CPU consumption, enabling denial of service. The fix is to upgrade js-yaml to version 4.3.1 (for the 4.x line) or 3.15.1 (for the 3.x line) by updating `package.json` and `package-lock.json` to pin the patched versions.

Vulnerability at a Glance

cweCWE-400 (Uncontrolled Resource Consumption)
fixUpgrade js-yaml from 4.1.1 to 4.3.1 (and 3.x to 3.15.1)
riskDenial of service via quadratic CPU consumption when parsing crafted YAML
languageJavaScript (Node.js)
root causeO(n²) duplicate-key checking algorithm in js-yaml's !!omap type resolver
vulnerabilityAlgorithmic Complexity / Denial of Service (ReDoS-like)

Introduction

In the Audex desktop music player repository (audex-player), Orbis AppSec's Trivy scanner flagged a high-severity vulnerability in package-lock.json: the project's dependency on js-yaml@4.1.1 exposed the application to GHSA-5p4m-2wfm-xmqj, a quadratic CPU consumption attack through crafted !!omap (ordered map) YAML sequences.

This isn't a theoretical concern. Audex is an Electron-based application — meaning it runs a full Node.js runtime on the user's desktop. If any part of the application parses YAML from an untrusted source (configuration files, playlist metadata, plugin manifests), an attacker could craft a payload that locks up the application's main thread, rendering the music player completely unresponsive. The vulnerability existed in js-yaml's !!omap type resolver, where duplicate-key validation used an O(n²) algorithm that could be weaponized with a relatively small input document.

The Vulnerability Explained

What Is !!omap in YAML?

YAML's !!omap tag represents an ordered mapping — essentially an array of key-value pairs where order matters and duplicate keys are forbidden. When js-yaml encounters !!omap during parsing, it must validate that no keys are duplicated. Here's where the problem lies.

The Quadratic Blowup

In js-yaml versions prior to 4.3.1 (and 3.15.1 in the 3.x line), the !!omap type resolver checked for duplicate keys using a nested comparison loop. For each new key encountered, the resolver compared it against every previously seen key. This is classic O(n²) behavior:

  • 100 keys → ~10,000 comparisons
  • 1,000 keys → ~1,000,000 comparisons
  • 10,000 keys → ~100,000,000 comparisons

An attacker doesn't need a massive payload. A YAML document with just a few thousand unique !!omap entries — perhaps 50–100 KB of text — can consume seconds to minutes of CPU time, effectively freezing the Node.js event loop.

Attack Scenario Against Audex

Consider this attack scenario specific to the Audex player:

# Malicious playlist metadata file
!!omap
  - key_0001: "track data"
  - key_0002: "track data"
  - key_0003: "track data"
  # ... thousands more unique keys ...
  - key_9999: "track data"

If Audex loads a playlist file, configuration, or any YAML-formatted data that passes through js-yaml.load(), this payload would cause the Electron main process to hang. The user sees a frozen window, and on some operating systems, the OS may prompt to kill the unresponsive application. This is a local denial of service — and in scenarios where YAML is fetched from a remote source (e.g., shared playlists), it becomes a remote denial of service.

The Vulnerable Dependency in package-lock.json

The pinned version before the fix:

"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=="
}

Version 4.1.1 contains the vulnerable !!omap resolver. The CVE-2026-59870 fix was applied upstream but had not been backported to the versions Audex was using, leaving the application exposed.

The Fix

The fix is a targeted dependency upgrade across two files: package.json and package-lock.json.

Before (Vulnerable)

// 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==",
  "license": "MIT",
  "dependencies": {
    "argparse": "^2.0.1"
  }
}

After (Patched)

// 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==",
  "funding": [
    {
      "type": "github",
      "url": "https://github.com/sponsors/puzrin"
    },
    {
      "type": "github",
      "url": "https://github.com/sponsors/nodeca"
    }
  ],
  "license": "MIT",
  "dependencies": {
    "argparse": "^2.0.1"
  }
}

What Changed Internally in js-yaml 4.3.1

The patched version replaces the O(n²) nested-loop duplicate detection in the !!omap resolver with a hash-set-based approach (O(n) amortized). Instead of comparing each new key against all previous keys in a linear scan, the resolver now inserts keys into a Set or object-based lookup, making duplicate detection constant-time per key.

Additional Changes in the PR

The PR also includes minor formatting normalizations in package.json:

  • Unicode character encoding: The em dash () in the description field was encoded as \u2014, and the copyright symbol (©) as \u00a9. These are cosmetic normalizations that occur during npm install regeneration.
  • Array formatting: The linux.target array was reformatted from inline ["AppImage", "deb"] to a multi-line format. This is standard npm lockfile regeneration behavior.

These changes are side effects of running npm install with the updated dependency and do not affect application behavior.

Prevention & Best Practices

1. Pin and Audit Dependencies Regularly

# Run npm audit regularly
npm audit

# Use Trivy for comprehensive scanning
trivy fs --scanners vuln .

The js-yaml vulnerability sat in package-lock.json as a transitive or direct dependency. Regular auditing catches these before they're exploited.

2. Set Input Size Limits on YAML Parsing

Even with patched libraries, defense-in-depth matters:

const yaml = require('js-yaml');
const MAX_YAML_SIZE = 1024 * 1024; // 1 MB limit

function safeParseYaml(input) {
  if (input.length > MAX_YAML_SIZE) {
    throw new Error('YAML input exceeds maximum allowed size');
  }
  return yaml.load(input);
}

3. Use Schema Restrictions

If you don't need !!omap or other advanced YAML types, restrict the schema:

// Only allow core YAML types — no !!omap, !!pairs, etc.
const result = yaml.load(input, { schema: yaml.CORE_SCHEMA });

4. Implement Parsing Timeouts for Electron Apps

In Electron applications, parse untrusted data in a worker thread with a timeout:

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

function parseYamlWithTimeout(input, timeoutMs = 5000) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./yaml-worker.js', { workerData: input });
    const timer = setTimeout(() => {
      worker.terminate();
      reject(new Error('YAML parsing timed out'));
    }, timeoutMs);
    worker.on('message', (result) => { clearTimeout(timer); resolve(result); });
    worker.on('error', (err) => { clearTimeout(timer); reject(err); });
  });
}

5. Automate Dependency Updates

Use tools like Dependabot, Renovate, or Orbis AppSec to automatically detect and patch vulnerable dependencies before they reach production.

Key Takeaways

  • js-yaml's !!omap resolver prior to 4.3.1 used O(n²) duplicate-key checking, making it trivial to craft a small YAML document that consumes disproportionate CPU time — a classic algorithmic complexity attack.
  • Electron applications are especially vulnerable to event-loop-blocking attacks because a frozen main thread means a frozen UI, turning a parsing bug into a full application denial of service.
  • The package-lock.json pinned js-yaml at 4.1.1, which did not include the CVE-2026-59870 backport — demonstrating why lockfile auditing is essential, not just package.json review.
  • Schema restriction (yaml.CORE_SCHEMA) would have mitigated this vulnerability even without the upgrade, since !!omap is not part of the core YAML schema.
  • A 50 KB YAML payload could freeze an application for minutes — input size alone is not a reliable defense against algorithmic complexity attacks; the algorithm itself must be efficient.

How Orbis AppSec Detected This

  • Source: YAML content parsed by the js-yaml library (version 4.1.1) as declared in package-lock.json of the Audex player project
  • Sink: The !!omap type resolver internal to js-yaml@4.1.1, which performs O(n²) duplicate-key validation during yaml.load() calls
  • Missing control: No upgrade to the patched js-yaml version (4.3.1/3.15.1) that replaces the quadratic algorithm with linear-time duplicate detection; no input size limits or schema restrictions on YAML parsing
  • CWE: CWE-400 (Uncontrolled Resource Consumption)
  • Fix: Upgraded js-yaml from 4.1.1 to 4.3.1 in both package.json and package-lock.json, replacing the vulnerable !!omap resolver with the patched version that uses O(n) duplicate-key detection

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 !!omap quadratic CPU consumption vulnerability (GHSA-5p4m-2wfm-xmqj) is a textbook example of how algorithmic complexity bugs in widely-used parsing libraries can create real denial-of-service risks — especially in Electron applications where the main thread is sacred. The fix was straightforward: upgrade js-yaml from 4.1.1 to 4.3.1. But the lesson runs deeper. Dependency lockfiles deserve the same security scrutiny as application code, schema restrictions should be applied to all parsers handling untrusted input, and automated scanning tools are essential for catching vulnerabilities that hide in transitive dependency trees.

Don't wait for an attacker to freeze your application with a 50 KB YAML file. Audit your dependencies today.

References

Frequently Asked Questions

What is quadratic CPU consumption in YAML parsing?

It occurs when a YAML parser's internal algorithm for resolving certain tags (like `!!omap`) uses a nested loop to check for duplicate keys, causing processing time to grow quadratically with input size — meaning doubling the input quadruples the processing time, eventually freezing the application.

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

Keep parsing libraries like js-yaml updated, set input size limits on YAML payloads, use `safeLoad` or schema-restricted parsing modes, and avoid parsing untrusted YAML input without timeouts or resource constraints.

What CWE is quadratic CPU consumption?

CWE-400: Uncontrolled Resource Consumption. This covers scenarios where an attacker can cause disproportionate resource usage (CPU, memory) relative to the size of their input.

Is upgrading js-yaml enough to prevent this vulnerability?

Upgrading to js-yaml 4.3.1 or 3.15.1 fixes this specific `!!omap` resolution issue, but you should also implement defense-in-depth measures like input size limits and parsing timeouts to protect against future algorithmic complexity issues in any parser.

Can static analysis detect algorithmic complexity vulnerabilities?

Static analysis tools like Trivy (which detected this issue) can identify known vulnerable dependency versions. However, detecting novel algorithmic complexity bugs typically requires specialized tools or manual code review of loop structures and data structure operations.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #4

Related Articles

critical

How Prototype Pollution Happens in JavaScript Carousel Libraries and How to Fix It

A critical prototype pollution vulnerability (CVE-2026-27212) was discovered in Swiper versions up to 11.2.10, a popular JavaScript carousel library used in production web applications. This vulnerability could allow attackers to manipulate application behavior through the prototype chain. The fix involved upgrading Swiper from 11.2.10 to 12.1.2, which patches the underlying prototype pollution flaw.

high

How Quadratic CPU Consumption in YAML Parsing Happens in Node.js and How to Fix It

A critical vulnerability in js-yaml's `!!omap` tag resolution allowed attackers to craft malicious YAML files that consumed CPU resources quadratically, leading to denial of service. The Orbis AppSec team identified this unpatched vulnerability in the docs-site project and automatically upgraded js-yaml to versions 4.3.1 and 3.15.1, which include CVE-2026-59870 backports that fix the algorithmic complexity issue.

critical

How Information Disclosure and Denial of Service Vulnerabilities Happen in PostCSS and How to Fix Them

PostCSS 8.5.6 contained a critical vulnerability that could enable attackers to cause denial of service and information disclosure through specially crafted CSS input. This blog post explores how the vulnerability manifested in the dependency tree and how upgrading to PostCSS 8.5.23 eliminates the attack surface.

high

How Quadratic CPU Consumption in js-yaml's !!omap Resolution Happens in Node.js and How to Fix It

A high-severity algorithmic complexity vulnerability (GHSA-5p4m-2wfm-xmqj) in js-yaml versions 3.x through 4.3.0 allows attackers to trigger quadratic CPU consumption through specially crafted `!!omap` YAML sequences. The fix upgrades js-yaml to 4.3.1 using a pnpm override in the `e2e/adapter/claude-code` package, ensuring all transitive dependencies also receive the patched version. This proactive patch eliminates an exploit primitive before it can be chained with other weaknesses.

critical

How Command Injection happens in Python PopClip Extensions and how to fix it

A critical command injection vulnerability was discovered in `contrib/Klipz.popclipext/Klipz.py`, where user-controlled clipboard content was concatenated directly into shell commands executed via `osascript`. The fix replaces unsafe string concatenation with `subprocess` and proper argument lists, and replaces the unsafe `pickle` serialization with `json` to eliminate a secondary deserialization risk. Together, these changes close two distinct attack surfaces in a single file.

high

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

A command injection vulnerability was discovered in `scripts/install.js` where user-controllable input was passed to `child_process.execSync()` through string interpolation. This high-severity issue could allow attackers to execute arbitrary shell commands by crafting malicious package file paths. The fix replaces `execSync()` with `execFileSync()`, which bypasses the shell entirely and treats arguments as literal values.