CVE-2026-53550: JS-YAML Denial of Service Vulnerability

MODERATE
Advisory Identifier: GHSA-h67p-54hq-rp68
CVSS 5.3
CWE-407
Published
Advisory Summary

CVE-2026-53550 is a MODERATE (CVSS 5.3) denial of service vulnerability (CWE-407: Inefficient Algorithmic Complexity) affecting JS-YAML libraries in the npm ecosystem. The issue involves quadratic-complexity dos in merge key handling via repeated aliases. Remediation is available by upgrading affected packages to version 4.2.0, 3.15.0.

CVE-2026-53550 at a Glance

CVE Identifier CVE-2026-53550
GHSA Identifier GHSA-h67p-54hq-rp68
Severity MODERATE
CVSS v3.1 Base Score 5.3
CWE Classification CWE-407: Inefficient Algorithmic Complexity
Affected Product JS-YAML
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 5.3

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability Low (A:L)

How to Fix CVE-2026-53550

To remediate this vulnerability, upgrade all affected JS-YAML dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

js-yaml fixed: 4.2.0
npm install js-yaml@4.2.0
js-yaml fixed: 3.15.0
npm install js-yaml@3.15.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls js-yaml

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-53550, inspect your project dependencies using npm:

npm ls js-yaml

Analyze the terminal output to locate any version of JS-YAML in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.2.0), your application is affected.

Note: The npm ls js-yaml command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-407: Inefficient Algorithmic Complexity, where an algorithm in a product has an inefficient worst-case computational complexity that may be detrimental to system performance and can be triggered by an attacker, typically using crafted manipulations that ensure that the worst case is being reached. In JS-YAML, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary A crafted YAML document can trigger algorithmic CPU exhaustion in `js-yaml` merge-key processing (`<<`) by repeating the same alias many times in a merge sequence. This causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service. ### Details The issue is in merge handling inside `lib/loader.js`: - `storeMappingPair(...)` iterates every element of a merge sequence when key tag is `tag:yaml.org,2002:merge`. - For each element, it calls `mergeMappings(...)`. - `mergeMappings(...)` computes `Object.keys(source)` and performs `_hasOwnProperty.call(destination, key)` checks for each key. When input is of the form: a: &a {k0:0, k1:0, ..., kK:0} b: {<<: [*a, *a, *a, ... repeated M times ...]} all *a entries refer to the same anchored object. After the first merge, subsequent merges are semantically no-ops, but the parser still reprocesses all keys each time. Resulting work is O(K * M), while input size is O(K + M), giving quadratic scaling as payload grows. Relevant code path: lib/loader.js in storeMappingPair(...) merge branch (keyTag === 'tag:yaml.org,2002:merge') lib/loader.js mergeMappings(...) ### Root cause File: lib/loader.js Function: storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, startLine, startLineStart, startPos) Lines: ~359-366 if (keyTag === 'tag:yaml.org,2002:merge') { if (Array.isArray(valueNode)) { for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { mergeMappings(state, _result, valueNode[index], overridableKeys); } } else { mergeMappings(state, _result, valueNode, overridableKeys); } } When the merge value is a sequence (YAML 1.1 <<: [ *a, *a, ... ]), each element is handed to mergeMappings() without deduplication. mergeMappings() then does sourceKeys = Object.keys(source); for (index = 0; index < sourceKeys.length; index += 1) { key = sourceKeys[index]; if (!_hasOwnProperty.call(destination, key)) { setProperty(destination, key, source[key]); overridableKeys[key] = true; } } Every alias reference in the sequence resolves (by design) to the SAME object via state.anchorMap. After the first merge, every subsequent merge of that same reference is a pure no-op semantically, but still performs: * one Object.keys(source) call (O(K)) * K _hasOwnProperty.call checks on the destination Total: M * K hasOwnProperty checks + M Object.keys allocations, while the final object and all observable side effects are identical to a single merge. YAML semantics for `<<:` are idempotent and commutative over duplicate sources, so collapsing duplicates preserves behavior exactly; this isn't a spec trade-off. ### PoC Environment: js-yaml version: 4.1.1 Node.js: v24.5.0 Platform: arm64 macOS (reproduced consistently) Reproduction script: Create many keys in one anchored map (&a). Merge that same alias repeatedly via <<: [*a, *a, ...]. Measure parse time and compare with control payload using single merge (<<: *a). Observed repeated runs (same machine): K=M=1000, input 9,909 bytes: ~33–36 ms K=M=2000, input 20,909 bytes: ~121–123 ms K=M=4000, input 42,909 bytes: ~524–537 ms K=M=6000, input 64,909 bytes: ~1,608–1,829 ms K=M=8000, input 86,909 bytes: ~3,395–3,565 ms Control (single merge, similar key counts): K=2000: ~1–2 ms K=4000: ~3 ms K=8000: ~5 ms Also verified: repeated-merge output equals single-merge output (same key count and same JSON), confirming excess time is redundant computation. ### Impact This is a denial-of-service vulnerability (CPU exhaustion / algorithmic complexity). Any service parsing untrusted YAML with js-yaml can be impacted, including API backends, CI tools, config processors, and automation services. An attacker can submit crafted YAML to significantly increase CPU time and reduce availability. ### Suggested fix: Dedupe the merge source list by reference before invoking mergeMappings. Any of the following are minimal and preserve YAML 1.1 merge semantics: dedupe in storeMappingPair: if (keyTag === 'tag:yaml.org,2002:merge') { if (Array.isArray(valueNode)) { var seen = new Set(); for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { var src = valueNode[index]; if (seen.has(src)) continue; // idempotent; skip redundant alias seen.add(src); mergeMappings(state, _result, src, overridableKeys); } } else { mergeMappings(state, _result, valueNode, overridableKeys); } }

CVE-2026-53550 Affected JS-YAML Versions

2 component(s)
Fixed Versions:
4.2.03.15.0
Product / Package Ecosystem Affected Versions Fixed Version
js-yaml
npm
≥ 4.0.0
< 4.2.0
4.2.0
js-yaml
npm
< 3.15.0
3.15.0

References

Frequently Asked Questions

What is CVE-2026-53550?
CVE-2026-53550 is a security vulnerability in JS-YAML involving Quadratic-complexity DoS in merge key handling via repeated aliases under CWE-407: Inefficient Algorithmic Complexity.
Which JS-YAML versions are affected?
Affected versions include releases prior to 4.2.0, 3.15.0 across affected packages: js-yaml.
What version fixes CVE-2026-53550?
Upgrading to 4.2.0, 3.15.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is js-yaml affected?
Yes, js-yaml is affected in releases prior to 4.2.0, 3.15.0. Upgrade your dependency to 4.2.0, 3.15.0 or later.
What is the CVSS score of CVE-2026-53550?
CVE-2026-53550 has a CVSS v3.1 base score of 5.3 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L.
What CWE is associated with CVE-2026-53550?
CVE-2026-53550 is classified under CWE-407: Inefficient Algorithmic Complexity (An algorithm in a product has an inefficient worst-case computational complexity that may be detrimental to system performance and can be triggered by an attacker, typically using crafted manipulations that ensure that the worst case is being reached.).
How can I check npm dependencies for CVE-2026-53550?
Run the dependency tree command: 'npm ls js-yaml'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-h67p-54hq-rp68",
  "modified" : "2026-06-29T15:05:57Z",
  "published" : "2026-06-15T17:15:07Z",
  "aliases" : [ "CVE-2026-53550" ],
  "summary" : "JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases",
  "details" : "### Summary\nA crafted YAML document can trigger algorithmic CPU exhaustion in `js-yaml` merge-key processing (`<<`) by repeating the same alias many times in a merge sequence.  \nThis causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service.\n\n### Details\nThe issue is in merge handling inside `lib/loader.js`:\n\n- `storeMappingPair(...)` iterates every element of a merge sequence when key tag is `tag:yaml.org,2002:merge`.\n- For each element, it calls `mergeMappings(...)`.\n- `mergeMappings(...)` computes `Object.keys(source)` and performs `_hasOwnProperty.call(destination, key)` checks for each key.\n\nWhen input is of the form:\n\na: &a {k0:0, k1:0, ..., kK:0}\nb: {<<: [*a, *a, *a, ... repeated M times ...]}\nall *a entries refer to the same anchored object. After the first merge, subsequent merges are semantically no-ops, but the parser still reprocesses all keys each time.\nResulting work is O(K * M), while input size is O(K + M), giving quadratic scaling as payload grows.\nRelevant code path:\nlib/loader.js in storeMappingPair(...) merge branch (keyTag === 'tag:yaml.org,2002:merge')\nlib/loader.js mergeMappings(...)\n\n\n### Root cause\nFile:       lib/loader.js\nFunction:   storeMappingPair(state, _result, overridableKeys, keyTag, keyNode,\n                             valueNode, startLine, startLineStart, startPos)\nLines:      ~359-366\n\n    if (keyTag === 'tag:yaml.org,2002:merge') {\n      if (Array.isArray(valueNode)) {\n        for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {\n          mergeMappings(state, _result, valueNode[index], overridableKeys);\n        }\n      } else {\n        mergeMappings(state, _result, valueNode, overridableKeys);\n      }\n    }\n\nWhen the merge value is a sequence (YAML 1.1 <<: [ *a, *a, ... ]), each element\nis handed to mergeMappings() without deduplication. mergeMappings() then does\n\n    sourceKeys = Object.keys(source);\n    for (index = 0; index < sourceKeys.length; index += 1) {\n      key = sourceKeys[index];\n      if (!_hasOwnProperty.call(destination, key)) {\n        setProperty(destination, key, source[key]);\n        overridableKeys[key] = true;\n      }\n    }\n\nEvery alias reference in the sequence resolves (by design) to the SAME object\nvia state.anchorMap. After the first merge, every subsequent merge of that same\nreference is a pure no-op semantically, but still performs:\n\n  * one Object.keys(source) call (O(K))\n  * K _hasOwnProperty.call checks on the destination\n\nTotal: M * K hasOwnProperty checks + M Object.keys allocations, while the final\nobject and all observable side effects are identical to a single merge.\n\nYAML semantics for `<<:` are idempotent and commutative over duplicate sources,\nso collapsing duplicates preserves behavior exactly; this isn't a spec trade-off.\n\n\n### PoC\nEnvironment:\njs-yaml version: 4.1.1\nNode.js: v24.5.0\nPlatform: arm64 macOS (reproduced consistently)\nReproduction script:\nCreate many keys in one anchored map (&a).\nMerge that same alias repeatedly via <<: [*a, *a, ...].\nMeasure parse time and compare with control payload using single merge (<<: *a).\nObserved repeated runs (same machine):\nK=M=1000, input 9,909 bytes: ~33–36 ms\nK=M=2000, input 20,909 bytes: ~121–123 ms\nK=M=4000, input 42,909 bytes: ~524–537 ms\nK=M=6000, input 64,909 bytes: ~1,608–1,829 ms\nK=M=8000, input 86,909 bytes: ~3,395–3,565 ms\nControl (single merge, similar key counts):\nK=2000: ~1–2 ms\nK=4000: ~3 ms\nK=8000: ~5 ms\nAlso verified: repeated-merge output equals single-merge output (same key count and same JSON), confirming excess time is redundant computation.\n\n\n### Impact\nThis is a denial-of-service vulnerability (CPU exhaustion / algorithmic complexity).\nAny service parsing untrusted YAML with js-yaml can be impacted, including API backends, CI tools, config processors, and automation services. An attacker can submit crafted YAML to significantly increase CPU time and reduce availability.\n\n### Suggested fix:\nDedupe the merge source list by reference before invoking mergeMappings. Any of\nthe following are minimal and preserve YAML 1.1 merge semantics:\n\ndedupe in storeMappingPair:\n\n    if (keyTag === 'tag:yaml.org,2002:merge') {\n      if (Array.isArray(valueNode)) {\n        var seen = new Set();\n        for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {\n          var src = valueNode[index];\n          if (seen.has(src)) continue;   // idempotent; skip redundant alias\n          seen.add(src);\n          mergeMappings(state, _result, src, overridableKeys);\n        }\n      } else {\n        mergeMappings(state, _result, valueNode, overridableKeys);\n      }\n    }",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "js-yaml"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "4.0.0"
      }, {
        "fixed" : "4.2.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.1.1"
    }
  }, {
    "package" : {
      "ecosystem" : "npm",
      "name" : "js-yaml"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "3.15.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/nodeca/js-yaml/security/advisories/GHSA-h67p-54hq-rp68"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-53550"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/nodeca/js-yaml"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-407" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-15T17:15:07Z",
    "nvd_published_at" : "2026-06-22T16:16:38Z"
  }
}