Back to Blog
high SEVERITY7 min read

How Quadratic CPU Consumption happens in js-yaml and how to fix it

A high-severity denial-of-service flaw in js-yaml's `!!omap` type resolution (GHSA-5p4m-2wfm-xmqj / CVE-2026-59870) let specially crafted YAML documents trigger quadratic CPU consumption. The fix upgrades js-yaml from 4.1.1 to 4.3.1 (and pins 3.x users to 3.15.1) via a `package.json` override, closing off an unpatched backport gap before it could be chained into a larger attack.

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

Answer Summary

This is a quadratic CPU consumption (algorithmic complexity / ReDoS-style CPU exhaustion, CWE-407 / CWE-1333) vulnerability in the js-yaml library's `!!omap` YAML type resolver, present in both the 3.x and 4.x branches. It's fixed by upgrading js-yaml to the patched versions 4.3.1 (or 3.15.1 for the 3.x line), which contain the backported fix for CVE-2026-59870, done here via a `package.json` override plus lockfile update.

Vulnerability at a Glance

cweCWE-407 (Algorithmic Complexity), related to CWE-1333 (Inefficient Regular Expression Complexity)
fixUpgrade js-yaml dependency to 4.3.1 (pinned via `overrides` in `package.json`) and update `package-lock.json` accordingly
riskA malicious YAML payload with a crafted `!!omap` structure can cause CPU usage to scale quadratically with input size, leading to denial of service
languageJavaScript / Node.js
root causejs-yaml's `!!omap` type resolver in versions before 4.3.1 (and before 3.15.1) used an inefficient algorithm that didn't scale linearly with input complexity
vulnerabilityQuadratic CPU Consumption in YAML !!omap resolution

Introduction

The package-lock.json file in localpdf-studio locks the exact version of every dependency the application resolves at install time, including js-yaml — a YAML parser used transitively by the project's dependency tree. Before this fix, that lockfile pinned js-yaml at version 4.1.1, a version that never received the backported fix for GHSA-5p4m-2wfm-xmqj (CVE-2026-59870): a quadratic CPU consumption bug in the !!omap type resolver that affects both the 3.x and 4.x release lines of js-yaml.

This matters even if your application doesn't directly call yaml.load() on untrusted input. js-yaml is one of the most widely used YAML parsers in the JavaScript ecosystem, and it often gets pulled in transitively by build tools, config loaders, and other packages. If any code path in the dependency graph feeds attacker-influenced YAML into js-yaml's parser, the vulnerable resolver becomes reachable — which is exactly why Trivy flagged this as "present in dependency tree, not confirmed reachable." Fixing it proactively, before reachability is proven, is the responsible move: it removes an exploit primitive before automated tooling or a future code change turns it into a concrete attack path.

The Vulnerability Explained

YAML supports a special tag called !!omap — an "ordered map" — which lets a document represent a sequence of single-key mappings that preserve insertion order:

--- !!omap
- key1: value1
- key2: value2
- key3: value3

Internally, js-yaml's resolver for this type has to walk through the sequence and validate the structure of each entry. In the vulnerable versions (js-yaml < 4.3.1 and < 3.15.1), this validation logic scaled poorly: as the number of entries in the !!omap structure grew, the work done by the resolver grew quadratically rather than linearly.

That's the classic signature of an algorithmic complexity vulnerability (CWE-407). A well-behaved parser should process an N-entry structure in roughly O(N) time. A vulnerable one that's O(N²) looks fine on small, benign inputs — but an attacker who controls the YAML being parsed can supply a document with a large, repetitive !!omap structure and force the parser to burn CPU disproportionately to the size of the payload sent.

A concrete attack scenario

Imagine any part of localpdf-studio's build pipeline, CI configuration parsing, or a transitive dependency that ends up calling something like:

const yaml = require('js-yaml');
const doc = yaml.load(untrustedInput);

An attacker who can influence untrustedInput — whether through an uploaded config file, a webhook payload, or a crafted document processed as part of a larger workflow — could submit a YAML document containing a large !!omap block. On the vulnerable js-yaml versions, parsing that single request could pin a CPU core at 100% for a disproportionate amount of time relative to the payload's size, potentially stalling the event loop in a Node.js process and degrading or denying service for every other request being handled by that process.

Because the lockfile showed js-yaml 4.1.1 — a version that predates the CVE-2026-59870 backport — this vulnerability was sitting in the dependency tree regardless of whether it was actively reachable today. That's the nature of a lot of supply-chain risk: the vulnerable code ships whether or not you're using it yet.

The Fix

The fix here is a targeted dependency upgrade, not a code rewrite — because the vulnerability lives inside js-yaml itself, not in localpdf-studio's application logic.

Before:

"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:

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

To make sure this pinned version actually sticks — even if a transitive dependency somewhere else in the tree still asks for an older js-yaml — the PR also adds an explicit override in package.json:

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

This is the important part: without the overrides entry, npm's resolution algorithm could still let a nested dependency pull in an older, vulnerable js-yaml copy elsewhere in node_modules. The override forces every consumer in the tree — direct or transitive — onto the patched 4.3.1 release, which contains the fix for the !!omap resolver's quadratic-time behavior. The project's own version field was also bumped from 4.0.2 to 4.0.3 to mark the patch release.

Because the change is confined to package.json and package-lock.json, and js-yaml 4.3.1 is a patch-level release focused on this security fix, valid YAML documents parse identically to before — only the pathological, attacker-crafted !!omap structures that previously triggered quadratic blowup are now handled efficiently.

Prevention & Best Practices

  • Run software composition analysis (SCA) regularly. Tools like Trivy, npm audit, Snyk, or Orbis AppSec scan your lockfile against advisory databases and catch exactly this class of issue — a vulnerable transitive dependency that your own code never directly touches.
  • Don't assume "not directly reachable" means "safe." Trivy's own note here was "present in dependency tree, not confirmed reachable" — but dependency graphs change, and code that calls yaml.load() on untrusted input can be added later without anyone realizing the underlying parser is vulnerable.
  • Use overrides (npm) or resolutions (Yarn) to enforce minimum safe versions across your entire dependency tree, not just your direct dependencies.
  • Prefer yaml.load() with json: true schema restrictions or yaml.safeLoad()-equivalent strict parsing when consuming untrusted YAML, and consider input size limits or timeouts around any untrusted document parsing to bound worst-case CPU usage regardless of parser bugs.
  • Track CVEs for your core parsing libraries. js-yaml is foundational infrastructure for countless JS/Node projects; subscribe to its release notes or GitHub Security Advisories.

Key Takeaways

  • The vulnerable code lived entirely inside the js-yaml package's !!omap resolver, not in localpdf-studio's own source — a reminder that dependency risk is application risk.
  • js-yaml 4.1.1 predated the backported fix for CVE-2026-59870; the patched line is 4.3.1 (4.x) or 3.15.1 (3.x).
  • Simply bumping the lockfile version wasn't fully sufficient — the fix also adds an overrides entry in package.json to guarantee no transitive dependency can reintroduce the vulnerable js-yaml version.
  • Quadratic-time parsers can turn a small, attacker-supplied YAML payload into a disproportionate CPU/denial-of-service cost — always worth patching even without confirmed reachability.
  • This is scoped to exactly two files (package.json, package-lock.json), preserving all valid-input behavior while eliminating the exploit primitive.

How Orbis AppSec Detected This

  • Source: Any untrusted YAML document reaching the application through a transitive dependency's call into js-yaml's parsing API (e.g., config loaders, CI tooling, or future code paths calling yaml.load()/yaml.loadAll())
  • Sink: js-yaml's internal !!omap type resolver (versions < 4.3.1 / < 3.15.1), reached whenever a YAML document containing an !!omap tag is parsed
  • Missing control: No enforced minimum-safe-version constraint on the js-yaml dependency — the lockfile pinned an unpatched 4.1.1, and no overrides/resolutions entry existed to prevent transitive resolution of vulnerable versions
  • CWE: CWE-407 (Algorithmic Complexity) / related to CWE-1333 and CWE-400 (Uncontrolled Resource Consumption)
  • Fix: Upgraded js-yaml to 4.3.1 in package-lock.json and added a package.json overrides entry pinning js-yaml to 4.3.1 across the entire dependency tree

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 fix is a great example of why dependency hygiene matters just as much as reviewing your own application code. localpdf-studio's package-lock.json was quietly pinning a version of js-yaml that missed a critical backported security fix for a quadratic CPU consumption bug in !!omap resolution — a flaw that could turn a small, crafted YAML payload into an outsized CPU cost anywhere the parser is invoked on untrusted input. By upgrading to js-yaml 4.3.1 and locking in that version with a package.json override, the project closes off this exploit primitive tree-wide, protecting against both current and future code paths that might parse attacker-influenced YAML.

References

Frequently Asked Questions

What is quadratic CPU consumption in js-yaml's !!omap resolution?

It's a denial-of-service vulnerability where parsing a maliciously crafted YAML document containing an `!!omap` (ordered map) structure causes js-yaml's resolver to perform work that grows quadratically relative to input size, tying up the CPU disproportionately compared to the size of the payload.

How do you prevent quadratic CPU consumption vulnerabilities in Node.js?

Keep parsing libraries like js-yaml patched and up to date, avoid parsing untrusted YAML/JSON with libraries that have known complexity issues, and enforce input size limits and timeouts on any untrusted document parsing.

What CWE is quadratic CPU consumption?

It falls under CWE-407 (Algorithmic Complexity), and is closely related to CWE-1333 (Inefficient Regular Expression Complexity) and CWE-400 (Uncontrolled Resource Consumption).

Is simply limiting YAML file size enough to prevent quadratic CPU consumption?

Size limits reduce the blast radius but don't fix the underlying inefficient algorithm — you can still exhaust CPU with a moderately sized crafted payload; the actual library fix (upgrading js-yaml) is required to eliminate the root cause.

Can static analysis detect quadratic CPU consumption vulnerabilities like this?

Traditional static analysis rarely catches algorithmic complexity issues in third-party dependencies; the real detection mechanism is dependency/SCA scanning (like Trivy, npm audit, or Orbis AppSec) that flags known-vulnerable package versions against advisories like GHSA-5p4m-2wfm-xmqj.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #20

Related Articles

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How dependabot-missing-cooldown happens in GitHub Actions/Node.js and how to fix it

The repository's `.github/dependabot.yml` had no cooldown period configured, meaning Dependabot could immediately propose updates to newly published package versions with zero time for the community to flag malware or instability. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, forcing a 7-day waiting period before new releases are surfaced as update PRs.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.

critical

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

CVE-2026-33937 is a critical remote code execution vulnerability in Handlebars.js that allows attackers to execute arbitrary code by passing maliciously crafted Abstract Syntax Tree (AST) objects to the compile() function. The vulnerability was patched in version 4.7.9, and we've upgraded to protect against this threat vector.

critical

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

A critical Denial of Service vulnerability (CVE-2026-59873) in the `tar` npm package allowed attackers to craft malicious gzip archives that could exhaust memory or CPU during decompression. The fix upgrades `tar` from 7.5.11 to 7.5.21 across `package.json` and `package-lock.json`, closing the resource-exhaustion path without changing any application code.