Back to Blog
high SEVERITY9 min read

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

A high-severity denial-of-service vulnerability in js-yaml (GHSA-5p4m-2wfm-xmqj) caused quadratic CPU consumption when resolving `!!omap` (ordered map) YAML tags, affecting both the 3.x and 4.x release lines. Upgrading to js-yaml 4.3.1 or 3.15.1 closes the gap by fixing the algorithmic inefficiency in `!!omap` duplicate-key detection. Any application that parses untrusted YAML input is at risk of resource exhaustion leading to service unavailability.

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

Answer Summary

GHSA-5p4m-2wfm-xmqj is a high-severity algorithmic complexity vulnerability (CWE-407) in the js-yaml library (JavaScript/Node.js) where parsing a YAML document containing a `!!omap` tag with many entries triggers O(n²) CPU consumption due to a naive duplicate-key check. An attacker who can supply YAML input to an application can craft a large `!!omap` document to exhaust server CPU and cause a denial of service. The fix is to upgrade js-yaml to version 4.3.1 (for 4.x users) or 3.15.1 (for 3.x users), which replaces the quadratic duplicate detection loop with a constant-time Set-based lookup, as captured in the `bun.lock` and `package.json` changes in this patch.

Vulnerability at a Glance

cweCWE-407 (Inefficient Algorithmic Complexity)
fixUpgrade js-yaml to 4.3.1 / 3.15.1, which uses a Set for O(1) duplicate-key detection in !!omap resolution
riskAttacker-controlled YAML input causes server CPU exhaustion and denial of service
languageJavaScript / Node.js
root causeThe `!!omap` type resolver iterated over previously-seen keys for every new entry, producing O(n²) work for large ordered maps
vulnerabilityAlgorithmic Complexity / Quadratic CPU Consumption (ReDoS-class DoS)

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

Introduction

The bun.lock file in this project pinned js-yaml to version 4.3.0 — a version that harbors a subtle but dangerous algorithmic flaw in how it resolves the YAML !!omap (ordered map) tag. Unlike memory-corruption bugs or injection flaws, this vulnerability does not crash a process or leak data. Instead, it quietly consumes CPU cycles at a quadratic rate, turning a carefully crafted YAML document into a denial-of-service weapon. The issue is tracked as GHSA-5p4m-2wfm-xmqj and affects both the 3.x and 4.x release lines of js-yaml; the CVE-2026-59870 fix was not backported until the releases addressed in this patch.

For developers who use js-yaml to parse configuration files, API payloads, or any other user-supplied YAML, this is a direct path to service unavailability — no authentication required, no memory corruption needed, just a well-formed YAML document with a long !!omap block.


The Vulnerability Explained

What is !!omap?

YAML's !!omap tag represents an ordered mapping — a sequence of key/value pairs where the order of insertion is significant and duplicate keys are explicitly forbidden. js-yaml supports this tag as part of its type system, and when it encounters !!omap during parsing, it runs a resolution function that, among other things, validates that no key appears more than once.

The Quadratic Algorithm

The flaw lives in the duplicate-key detection logic inside the !!omap type resolver. In the vulnerable versions (js-yaml <4.3.1 and <3.15.1), the check was implemented as a linear scan of a growing array:

// Simplified representation of the vulnerable pattern in js-yaml !!omap resolver
function resolveYamlOmap(data) {
  const objectKeys = [];
  for (const pair of data) {
    const key = Object.keys(pair)[0];
    // For each new key, scan the entire previously-seen array
    if (objectKeys.indexOf(key) !== -1) {
      return false; // duplicate found
    }
    objectKeys.push(key);
  }
  return true;
}

The critical line is objectKeys.indexOf(key). For every new key encountered, this call scans the entire objectKeys array from the beginning. With n entries in the !!omap:

  • Entry 1: 0 comparisons
  • Entry 2: 1 comparison
  • Entry 3: 2 comparisons
  • Entry n: n-1 comparisons

Total comparisons: 0 + 1 + 2 + … + (n-1) = n(n-1)/2 → O(n²)

This is a classic algorithmic complexity vulnerability. For a document with 10 entries it is imperceptible. For 100,000 entries, it requires roughly 5 billion comparisons.

Concrete Attack Scenario

An attacker targeting a web application that accepts YAML configuration or data payloads could POST a request body like:

!!omap
- key_00001: value
- key_00002: value
- key_00003: value
# ... 100,000 unique keys ...
- key_99999: value

Because all keys are unique, the !!omap resolver never short-circuits — it must compare every new key against every previously-seen key. A single such request on a commodity server can peg one CPU core for several seconds. Sending a handful of these requests concurrently can exhaust all available CPU, making the service unresponsive to legitimate traffic.

This is particularly dangerous in this project because:

  1. It is a web application (noted in the PR threat model), meaning YAML input paths may be reachable from the internet.
  2. The vulnerability requires no authentication or special privileges — only the ability to send a request containing YAML.
  3. The attack is deterministic and repeatable: the attacker does not need to guess memory addresses or race conditions.

Why Both 3.x and 4.x Are Affected

The !!omap resolver code was present in both major release lines with the same algorithmic pattern. The CVE-2026-59870 fix corrected the issue in the upstream codebase but was not backported to the 3.x branch until 3.15.1, leaving users of either line exposed if they had not upgraded.


The Fix

What Changed

The fix upgrades js-yaml in two places within this repository:

File Change
package.json Version constraint updated to require js-yaml ≥4.3.1
bun.lock Resolved version pinned to 4.3.1 (and 3.15.1 for any 3.x transitive dependency)

The patch in bun.lock ensures that Bun's deterministic installer will pull the patched release rather than the cached 4.3.0 artifact.

The Algorithmic Fix Inside js-yaml

In js-yaml 4.3.1 / 3.15.1, the !!omap resolver replaces the array-scan pattern with a Set-based lookup:

// BEFORE (vulnerable — O(n²))
function resolveYamlOmap(data) {
  const objectKeys = [];
  for (const pair of data) {
    const key = Object.keys(pair)[0];
    if (objectKeys.indexOf(key) !== -1) {  // ← linear scan every iteration
      return false;
    }
    objectKeys.push(key);
  }
  return true;
}

// AFTER (patched — O(n))
function resolveYamlOmap(data) {
  const seenKeys = new Set();              // ← O(1) lookup
  for (const pair of data) {
    const key = Object.keys(pair)[0];
    if (seenKeys.has(key)) {               // ← constant-time membership test
      return false;
    }
    seenKeys.add(key);
  }
  return true;
}

A JavaScript Set uses a hash table internally, so has() and add() are both O(1) amortized. The total work for resolving an !!omap with n entries drops from O(n²) to O(n) — a fundamental improvement that makes the attack economically unviable regardless of document size.

Why the bun.lock Change Matters

Locking files like bun.lock (and package-lock.json, yarn.lock) record the exact resolved version of every dependency. Even if package.json is updated to allow 4.3.1, the lock file continues to install 4.3.0 until it is regenerated. This PR correctly updates both files, ensuring the patched version is installed in all environments — local development, CI, and production — without ambiguity.


Prevention & Best Practices

1. Audit Algorithmic Complexity in Parser Code

Any code that processes unbounded user input and uses nested loops or linear-scan membership tests is a candidate for this class of vulnerability. Review parsers, deserializers, and validators for patterns like:

// Red flag: array.indexOf() or array.includes() inside a loop over user data
for (const item of userSuppliedData) {
  if (seenItems.indexOf(item) !== -1) { ... }  // O(n) inside O(n) loop = O(n²)
}

Replace with Set or Map for O(1) lookups.

2. Enforce Input Size Limits Before Parsing

Even with the fix applied, it is good practice to cap YAML document size before handing it to any parser:

const MAX_YAML_BYTES = 1_000_000; // 1 MB
if (Buffer.byteLength(rawInput) > MAX_YAML_BYTES) {
  throw new Error('YAML input exceeds maximum allowed size');
}
const parsed = yaml.load(rawInput);

This provides defense-in-depth against future unknown complexity vulnerabilities.

3. Keep Lock Files in Version Control and Update Them

A lock file that is not committed, or is committed but never updated, creates a false sense of security. Automate dependency updates with tools like Dependabot or Renovate, and ensure your CI pipeline verifies that the lock file matches package.json.

4. Use Vulnerability Scanners on Lock Files

Tools like Trivy (which detected this issue), Snyk, and npm audit can scan lock files for known-vulnerable versions. Integrate these into your CI pipeline as a required check:

# Example: fail the build if any high/critical vulnerabilities are found
trivy fs --exit-code 1 --severity HIGH,CRITICAL .

5. Reference Security Standards

  • CWE-407: Inefficient Algorithmic Complexity — the canonical classification for this class of bug.
  • OWASP A05:2021 – Security Misconfiguration: Includes using components with known vulnerabilities.
  • OWASP Dependency-Check: A tool specifically designed to identify known-vulnerable dependencies.

Key Takeaways

  • !!omap is a non-obvious attack surface: Most developers think of YAML parsing risks as code execution (via unsafe yaml.load), but the !!omap tag creates a CPU exhaustion path even when using the safe yaml.safeLoad / yaml.load (safe mode) API.
  • Array .indexOf() inside a parsing loop is an O(n²) smell: In the vulnerable js-yaml code, replacing objectKeys.indexOf(key) with seenKeys.has(key) (using a Set) was the entire fix — a one-line change with massive security impact.
  • Lock file updates are as important as package.json updates: Pinning package.json to ^4.3.1 without regenerating bun.lock would have left the vulnerable version installed in production.
  • Both 3.x and 4.x users are affected: Do not assume a major version upgrade automatically resolves all security issues in a library; always check the specific advisory for affected version ranges.
  • Trivy's lock file scanning caught what code review would miss: The vulnerability is not visible in application source code — it lives inside the library itself. Automated SCA (Software Composition Analysis) scanning of bun.lock was the detection mechanism here.

How Orbis AppSec Detected This

  • Source: Any code path that calls yaml.load() or yaml.safeLoad() with input containing a !!omap tag — in a web application, this originates from HTTP request bodies, file uploads, or configuration endpoints that accept YAML.
  • Sink: The resolveYamlOmap function inside js-yaml's type resolver, invoked during YAML document parsing whenever the !!omap tag is encountered.
  • Missing control: No bound on the number of !!omap entries processed, combined with an O(n²) duplicate-key detection algorithm — no per-request CPU budget or input size cap was enforced before the fix.
  • CWE: CWE-407 — Inefficient Algorithmic Complexity.
  • Fix: Upgraded js-yaml from 4.3.0 to 4.3.1 (and 3.x to 3.15.1) in package.json and bun.lock, replacing the quadratic duplicate-key detection with a constant-time Set-based lookup.

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 textbook example of why algorithmic complexity matters in security contexts. The !!omap duplicate-key check in js-yaml looked completely reasonable — scan a list for duplicates before accepting a value — but at scale it becomes a denial-of-service primitive that requires no special knowledge to exploit. A single well-formed YAML document is all an attacker needs.

The fix is straightforward: upgrade to js-yaml 4.3.1 or 3.15.1, update your lock file, and let a Set do what arrays were never meant to do. Pair that with input size limits and automated SCA scanning in CI, and you have a robust defense against this entire class of vulnerability.

Algorithmic complexity bugs are easy to overlook in code review because the code is correct — it just isn't efficient. That is precisely why automated tooling that tracks known-vulnerable dependency versions is an essential layer of a modern security program.


References

Frequently Asked Questions

What is quadratic CPU consumption in js-yaml?

It is an algorithmic complexity flaw in js-yaml's `!!omap` YAML tag handler where duplicate-key checking uses a nested loop, causing processing time to grow as O(n²) relative to the number of entries — small inputs are fine, but large crafted inputs can pin a CPU core.

How do you prevent algorithmic complexity attacks in JavaScript YAML parsing?

Use up-to-date versions of js-yaml (≥4.3.1 or ≥3.15.1), enforce input size limits before parsing, and prefer Set/Map data structures over array-scan loops for membership testing in parsers.

What CWE is quadratic CPU consumption?

CWE-407 — Inefficient Algorithmic Complexity. It covers cases where an algorithm's resource consumption grows super-linearly with input size, enabling denial-of-service attacks.

Is input validation alone enough to prevent this vulnerability?

Not reliably. While enforcing a maximum YAML document size helps reduce blast radius, it does not eliminate the root cause. The proper fix is upgrading to the patched js-yaml release so the underlying algorithm is efficient regardless of input structure.

Can static analysis detect this vulnerability?

Yes — tools like Trivy (which flagged this issue in `bun.lock`) and Dependabot can detect known-vulnerable package versions. Semgrep rules targeting unsafe js-yaml version ranges can also surface this in CI pipelines.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #575

Related Articles

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.

high

How Infinite Loop Denial of Service happens in nanoid custom alphabet generation and how to fix it

A high-severity infinite loop vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.17, affecting the custom alphabet generation feature. When processing certain malformed alphabet configurations, nanoid would enter an infinite loop, causing a complete denial of service. This vulnerability was fixed by upgrading from nanoid 3.3.16 to 3.3.17 and implementing dependency overrides to ensure the patched version is used throughout the dependency tree.

critical

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

A cross-site scripting (XSS) vulnerability in `renderer/views/library.js` allowed attackers who could control mod metadata—such as category icons rendered in pack thumbnail grids—to inject arbitrary JavaScript through unescaped output in `innerHTML` assignments. The fix wraps the `catIcon()` return value in the existing `esc()` helper, ensuring all dynamically generated HTML content is properly encoded before insertion into the DOM.

high

How Unauthenticated Denial of Service happens in React Router and how to fix it

CVE-2026-55685 is a high-severity Denial of Service vulnerability in React Router's `@remix-run/server-runtime` that allows unauthenticated attackers to exhaust server resources by sending crafted requests to the manifest endpoint. The fix upgrades `react-router` from version 7.17.0 to 7.18.0, which tightens handling of untrusted input in route matching logic. Developers using any React Router 7.x application with server-side rendering should apply this patch immediately.