Back to Blog
high SEVERITY6 min read

How Quadratic Complexity Denial of Service happens in JavaScript YAML parsing and how to fix it

A high-severity dependency vulnerability in `js-yaml`, used transitively by `electron-builder`/`electron-updater` inside the DSA Desktop app, allowed attacker-controlled YAML with `!!omap` tags to trigger quadratic-time CPU consumption during parsing. The fix pins `js-yaml` to the patched `4.3.1` (and `3.15.1` for the legacy branch) release via both `package.json` overrides and `package-lock.json`.

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

Answer Summary

This vulnerability is a Denial-of-Service (CWE-407, Inefficient Algorithmic Complexity) in the `js-yaml` npm package, where resolving the `!!omap` YAML type used a quadratic-time algorithm that could be abused with crafted YAML input to exhaust CPU. It affects `apps/dsa-desktop`, which pulls `js-yaml` transitively through `electron-builder`/`electron-updater`. The fix upgrades `js-yaml` from `4.1.1` to the patched `4.3.1` release (and `3.15.1` on the legacy line) and pins the version via an npm `overrides` entry so all transitive resolutions use the safe version.

Vulnerability at a Glance

cweCWE-407 (Inefficient Algorithmic Complexity)
fixUpgrade `js-yaml` to 4.3.1 (and 3.15.1) and pin the version with an npm `overrides` entry
riskMalicious or malformed YAML processed by build/update tooling could cause CPU exhaustion and hang the process
languageJavaScript / Node.js (npm dependency)
root causejs-yaml 4.1.1 (and pre-3.15.1) used an O(n²) algorithm when resolving `!!omap` mappings
vulnerabilityQuadratic CPU Consumption / Algorithmic Complexity Denial of Service in js-yaml `!!omap` resolution

Introduction

The apps/dsa-desktop application ships as an Electron desktop app that relies on electron-builder and electron-updater to manage build configuration and auto-update metadata — both of which parse YAML files under the hood using the js-yaml package. A scan by Orbis AppSec flagged apps/dsa-desktop/package-lock.json for pulling in js-yaml@4.1.1, a version affected by GHSA-5p4m-2wfm-xmqj, a high-severity quadratic CPU consumption issue in how js-yaml resolves the !!omap YAML type.

This isn't a bug in application code — it's a transitive dependency risk. js-yaml is one of the most widely used YAML parsers in the JavaScript ecosystem, and it's pulled in indirectly by build tooling, CI configuration loaders, and update mechanisms like electron-updater. If any part of the build or update pipeline parses YAML that isn't fully trusted (for example, YAML fetched from a remote update feed, a CI artifact, or a config file supplied by a third party), an attacker-crafted document could trigger pathological CPU usage.

The Vulnerability Explained

The root cause lives inside js-yaml's type resolution logic for the !!omap tag — YAML's "ordered map" type. In the affected versions (including 4.1.1, which was pinned in this repo before the fix), the resolver used an algorithm whose runtime scales quadratically, O(n²), with the number of entries in the map being resolved.

Before the fix, package-lock.json looked like this:

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

This version was resolved and installed without any override, meaning any code path in electron-builder/electron-updater that calls into js-yaml to parse configuration or update metadata would run with the vulnerable resolver.

How this could be exploited

Picture an attacker who can influence YAML content consumed anywhere along the build or auto-update pipeline for apps/dsa-desktop — for instance, a malicious or compromised electron-builder config, a spoofed update feed response, or a crafted CI artifact that gets parsed as YAML. If that YAML contains a deeply structured !!omap block, the parser's quadratic-time resolution means that even a moderate document (say, a few thousand ordered-map entries) can force js-yaml — and by extension, the Node.js process running the build or the desktop app's update checker — to burn CPU cycles disproportionately to the input size.

In a build server context, this could stall CI pipelines. In a desktop-app update context, it could hang the update-check process, degrading the user experience or creating a denial-of-service condition against the app's own tooling. The Orbis AppSec assessment notes this was "present in dependency tree, not confirmed reachable" — meaning it wasn't proven to be actively exploitable in this codebase's current call paths, but the exploit primitive existed and could be chained by automated tooling if a reachable code path is introduced later.

The Fix

The fix is a targeted dependency upgrade, touching exactly two files: apps/dsa-desktop/package.json and apps/dsa-desktop/package-lock.json.

1. Upgrading the resolved version in 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==",
+      "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"
       }

This locks the resolved package to js-yaml@4.3.1, which contains the backported fix for the !!omap quadratic-time resolution — restoring linear-time behavior for ordered map parsing.

2. Pinning the version with an npm overrides entry in package.json:

   "overrides": {
-    "builder-util-runtime": "9.7.0"
+    "builder-util-runtime": "9.7.0",
+    "js-yaml": "4.3.1"
   }

This second change is just as important as the version bump itself. Because js-yaml is a transitive dependency — pulled in indirectly through electron-builder and electron-updater — simply bumping the lockfile entry isn't enough to guarantee future installs stay safe. Any nested dependency could still declare a semver range that resolves back down to a vulnerable 4.x release. Adding "js-yaml": "4.3.1" to the overrides block forces npm to resolve every occurrence of js-yaml in the dependency tree to the patched version, regardless of what version range upstream packages request.

Prevention & Best Practices

  • Use npm overrides (or Yarn resolutions) for security-critical transitive dependencies. This repo already had a precedent — builder-util-runtime was pinned the same way — and extending that pattern to js-yaml closes the gap for a package with a known quadratic-complexity flaw.
  • Run dependency scanners like Trivy, npm audit, or Snyk in CI so advisories like GHSA-5p4m-2wfm-xmqj are caught automatically as soon as they're published, rather than discovered manually.
  • Treat any YAML parsing of external input as untrusted, even when it's "just" build configuration or update metadata — auto-update pipelines are a high-value target precisely because they run with elevated trust.
  • Prefer js-yaml's safeLoad/load with schema restrictions where possible, and avoid loading YAML that allows arbitrary custom tags unless strictly necessary.
  • Track algorithmic-complexity advisories, not just RCE/injection CVEs. DoS-class issues like CWE-407 are easy to overlook but can be just as disruptive to CI/CD and production services.

Key Takeaways

  • js-yaml@4.1.1, previously locked in apps/dsa-desktop/package-lock.json, contained a quadratic-time algorithm in its !!omap type resolver (GHSA-5p4m-2wfm-xmqj / CVE-2026-59870).
  • The fix upgrades to js-yaml@4.3.1 (with 3.15.1 for consumers still on the 3.x line) and hard-pins the version via package.json's overrides field.
  • js-yaml was present as a transitive dependency of electron-builder/electron-updater, illustrating how build tooling can silently drag vulnerable parsers into a desktop application's dependency tree.
  • Even when a flaw is "not confirmed reachable" in current code paths, removing the exploit primitive proactively — as was done here — reduces the attack surface available to automated exploit chaining tools.
  • Pinning transitive dependencies with overrides/resolutions is a reusable pattern already established in this codebase (builder-util-runtime) and should be applied consistently to other security-sensitive transitive packages.

How Orbis AppSec Detected This

  • Source: Untrusted YAML documents processed by electron-builder/electron-updater tooling (e.g., build configuration or update-feed metadata) that reach js-yaml's type resolution logic.
  • Sink: js-yaml's internal !!omap type resolver, invoked whenever js-yaml.load()/safeLoad() parses a YAML document containing an ordered-map tag, bundled transitively in apps/dsa-desktop/package-lock.json.
  • Missing control: No pinned/patched version of js-yaml was enforced across the dependency tree — the vulnerable 4.1.1 release was resolved with no overrides entry to force an upgrade.
  • CWE: CWE-407 (Inefficient Algorithmic Complexity), commonly grouped under Denial-of-Service issues.
  • Fix: Upgraded js-yaml to 4.3.1 (3.15.1 on the legacy line) and added an overrides entry in package.json to force the patched version across all transitive resolutions.

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 vulnerability is a good reminder that security risk doesn't only come from code you write — it also comes from the dependency graph beneath your build and update tooling. A quadratic-time flaw in js-yaml's !!omap resolution sat quietly in apps/dsa-desktop's lockfile, reachable only through the transitive chain from electron-builder/electron-updater. The fix was small but precise: bump js-yaml to the patched 4.3.1 release and enforce that version tree-wide with an npm overrides entry, following the same pattern already used for builder-util-runtime. Treat dependency advisories — even DoS-class ones — with the same urgency as injection or RCE bugs, and use version-pinning mechanisms to make sure a patched version actually sticks across your entire dependency tree.

References

Frequently Asked Questions

What is quadratic complexity denial of service?

It's a DoS condition where an algorithm's runtime grows quadratically (O(n²)) relative to input size, so a moderately sized malicious input can consume disproportionate CPU time and stall or crash a process.

How do you prevent algorithmic complexity DoS in Node.js dependencies?

Keep parsing libraries like `js-yaml` up to date, monitor CVE/GHSA advisories for your dependency tree, and use npm `overrides`/`resolutions` to force safe versions even when nested dependencies lag behind.

What CWE is quadratic complexity denial of service?

CWE-407, "Algorithmic Complexity," which covers cases where an attacker can trigger worst-case algorithmic behavior to exhaust resources.

Is version pinning alone enough to prevent this class of vulnerability?

No — pinning only fixes the currently known issue. You also need continuous dependency scanning (e.g., Trivy, npm audit, Dependabot) to catch future advisories in the same package.

Can static analysis detect this vulnerability?

Traditional static analysis on your own code won't catch it since the flaw lives inside a third-party library, but software composition analysis (SCA) tools like Trivy, Snyk, or GitHub Dependabot can flag vulnerable `js-yaml` versions in `package-lock.json`.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2254

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.