GHSA-3q6v-r5mr-hxv8: league/commonmark Denial of Service Vulnerability

HIGH
CVSS 7.5
CWE-400 CWE-407
Published
Advisory Summary

GHSA-3q6v-r5mr-hxv8 is a HIGH (CVSS 7.5) denial of service vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting league/commonmark libraries in the Packagist ecosystem. The issue involves quadratic-time denial of service in the github flavored markdown table extension block-start scan. Remediation is available by upgrading affected packages to version 2.10.2.

GHSA-3q6v-r5mr-hxv8 at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-3q6v-r5mr-hxv8
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-400: Uncontrolled Resource Consumption
Affected Product league/commonmark
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 7.5

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

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

How to Fix GHSA-3q6v-r5mr-hxv8

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

league/commonmark fixed: 2.10.2
Upgrade league/commonmark to 2.10.2
Verify Direct and Transitive Dependencies

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

inspect dependencies for league/commonmark

How to Check if You Are Affected

To determine whether your application is affected by GHSA-3q6v-r5mr-hxv8, inspect your project dependencies for league/commonmark:

inspect dependencies for league/commonmark

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

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption, where the product does not properly control the allocation and maintenance of a limited resource. In league/commonmark, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary `league/commonmark`'s GitHub Flavored Markdown Table extension registers `TableStartParser` as a block-start parser. While a paragraph is the active block, the core block parser calls `TableStartParser::tryStart()` on every non-blank line. Its first action fetches the entire growing paragraph buffer via `getParagraphContent()` and runs `strpos($paragraph, '|')` across all of it. For a paragraph of M pipe-free lines, line k rescans about k lines of buffer, so total work is 1+2+...+M, which is O(M^2). An unauthenticated user who submits a single large paragraph of pipe-free lines that do not begin with a letter (see Attack Chain) to any service that converts untrusted Markdown with `GithubFlavoredMarkdownConverter` (or any environment that enables `TableExtension`) drives seconds to tens of seconds of single-core CPU that grows quadratically with body size, enough to exhaust worker processes and deny service. ## Root Cause The GFM table detector performs a per-line "quick check" against the whole accumulated paragraph rather than only the portion that could form a table header. A paragraph never closes while non-blank lines keep arriving, so its buffer grows without bound, and the quick check rescans the entire buffer on each line. Only the paragraph's last line can ever be a table header (it is extracted separately via `strrpos`/`substr`), so scanning the full multi-line buffer for a pipe on every line is unnecessary work and creates quadratic time complexity. There is no input-size cap and the default nesting limit is not reached, so nothing bounds the scan. ```text Affected function: League\CommonMark\Extension\Table\TableStartParser::tryStart Location: src/Extension/Table/TableStartParser.php:35-38 $paragraph = $parserState->getParagraphContent(); // returns the FULL growing buffer if ($paragraph === null || \strpos($paragraph, '|') === false) { return BlockStart::none(); // strpos scans whole buffer each line } Buffer growth: src/Reference/ReferenceParser.php:89-95 ($this->paragraph .= "\n"; $this->paragraph .= $line;) Per-line dispatch: src/Parser/MarkdownParser.php:135-147 and :224-236 ``` ## Impact An unauthenticated, remote attacker with no user interaction can cause denial of service against any application that renders untrusted Markdown with the GitHub Flavored Markdown converter or any configuration that enables the Table extension (the default GFM bundle enables it). A single request carrying a large paragraph of pipe-free, blank-line-free lines that do not begin with a letter consumes CPU proportional to the square of the input size: measured runs show roughly 4x CPU growth per input doubling. A body of a few megabytes ties up a worker for seconds to tens of seconds; repeated or concurrent requests exhaust all available PHP worker processes, denying service to legitimate users. Impact is limited to availability; there is no confidentiality or integrity impact. ## Proof of Concept `Requires PHP with league/commonmark 2.x installed. The harness parses pipe-free paragraphs of increasing size with the real MarkdownParser and the real block-start parsers, comparing a GFM-plus-Table configuration against a core-only configuration to isolate the Table parser's contribution.` 1. Install `league/commonmark` (2.10.1 or any 2.x release). 2. Convert a single large paragraph of pipe-free, blank-line-free lines that do not begin with a letter, with `GithubFlavoredMarkdownConverter`. 3. Measure wall-clock time as the paragraph size doubles. ```php <?php require 'vendor/autoload.php'; use League\CommonMark\GithubFlavoredMarkdownConverter; $converter = new GithubFlavoredMarkdownConverter(); foreach ([25000, 50000, 100000, 200000] as $lines) { $md = str_repeat("12345678\n", $lines); // one paragraph, no blank lines, no '|', lines not starting with a letter $t = microtime(true); $converter->convert($md); printf("%7d lines %6.3fs\n", $lines, microtime(true) - $t); } ``` ```text lines input letter-leading non-letter-leading ("aaaaaaaa") ("12345678") 25000 0.23MB 0.27s 0.75s 50000 0.45MB 0.50s 2.36s 100000 0.90MB 1.07s 7.70s 200000 1.80MB 2.26s 27.81s ``` Letter-leading input scales linearly (doubles per input doubling) because `SkipLinesStartingWithLettersParser` aborts before the Table parser is reached. Non-letter-leading input quadruples per doubling (0.75, 2.36, 7.70, 27.81), confirming O(M^2) behavior attributable to the Table start parser. ## Attack Chain 1. **Exposure:** The attacker submits a Markdown body to any endpoint that converts untrusted Markdown with `GithubFlavoredMarkdownConverter`, or any environment that adds `TableExtension`. The default GFM bundle registers the Table extension: `GithubFlavoredMarkdownExtension` adds `TableExtension` (src/Extension/GithubFlavoredMarkdownExtension.php:30), which registers `TableStartParser` (src/Extension/Table/TableExtension.php:56). 2. **Control:** The attacker fully controls the Markdown body. The exploit input is one paragraph with no blank lines, no `|` character, and lines that do not begin with a letter (for example `str_repeat("12345678\n", 200000)`). 3. **Path:** `MarkdownParser::parseLine()` loops over block-start parsers for every non-blank line while a paragraph is active (src/Parser/MarkdownParser.php:135-147). `ParagraphParser::tryContinue()` keeps the paragraph open for every non-blank line, so the buffer keeps growing (src/Parser/Block/ParagraphParser.php:46-53), appended line by line in `ReferenceParser::parse()` (src/Reference/ReferenceParser.php:89-95). Since 2.0.2, the highest-priority `SkipLinesStartingWithLettersParser` returns `BlockStart::abort()` for any line whose first non-space character is a letter, short-circuiting `findBlockStart()` before `TableStartParser`. When lines begin with a non-letter (e.g. a digit), that parser returns none and every remaining core parser returns null, so control reaches `TableStartParser` on every line (src/Parser/MarkdownParser.php:224-236). 5. **Guard:** There is no input-size cap in `MarkdownInput`, and `max_nesting_level` defaults to `PHP_INT_MAX`, which the depth-1 paragraph never approaches. No guard bounds the whole-buffer scan. 6. **Primitive:** `TableStartParser::tryStart()` runs `strpos($paragraph, '|')` over the entire buffer returned by `getParagraphContent()` (src/Extension/Table/TableStartParser.php:35-38). With no pipe present, `strpos` scans to the end of the buffer on every line. 7. **Result:** Cumulative scanning work is O(M^2). A single small request drives disproportionate, quadratically growing CPU, exhausting worker processes and denying service. ## Bypass Evidence No prior fix exists for this code path, so this is not an incomplete-fix or regression case; it is a distinct, previously undisclosed quadratic path. The strongest disproof attempts were made and all failed: - Attempt: the observed quadratic is core-parser or PHP string overhead, not the Table parser. Refuted: the only difference between the two measured configurations is `TableStartParser`; the core-only baseline is linear while the isolated Table contribution quadruples per input doubling. - Attempt: `strpos` short-circuits or the buffer does not actually grow. Refuted by source trace: `ReferenceParser::parse()` appends every line unconditionally before its state switch, and for pipe-free text `strpos` finds no `|` and scans the full buffer each line. - Attempt: another parser short-circuits before `TableStartParser`. Partially correct: since 2.0.2 `SkipLinesStartingWithLettersParser` aborts block-start scanning for lines beginning with a letter, so letter-leading input (including the original `aaaaaaaa` PoC) never reaches the Table parser and runs in linear time. Input whose lines begin with a non-letter (e.g. a digit) passes every core parser and reaches `TableStartParser` on every line, exhibiting the quadratic behavior. - Attempt: an input-size or nesting guard neutralizes it. Refuted: there is no size cap and the default nesting limit is never reached. - Attempt: this duplicates an existing quadratic-DoS advisory or was already fixed. Refuted: published quadratic advisories address per-line position translation and other distinct mechanisms in other files; `git log` on `src/Extension/Table/TableStartParser.php` shows no security fix ever touched this rescan, and the code is present unchanged in the latest release. ## Affected Versions - `Ecosystem: composer` - `Package: league/commonmark` - `Confirmed affected range: >= 2.0.0, <= 2.10.1` - `Latest release checked: 2.10.1 (Packagist, repo.packagist.org/p2/league/commonmark.json)` - `Fix status: not fixed` Note: `SkipLinesStartingWithLettersParser` was introduced in 2.0.2. On 2.0.0 and 2.0.1 the issue can also be triggered with letter-leading lines. Regardless, the underlying quadratic scan itself is present across the whole >= 2.0.0, <= 2.10.1 range. The vulnerable rescan is artifact-verified present in tags 2.8.0 and 2.10.1 (the latest published release). The lower bound 2.0.0 is inferred from the 2.x block-parser rewrite that introduced the `getParagraphContent()`-based architecture this quick check depends on; the checkout contains only tags 2.8.0 through 2.10.1, so the path is not artifact-verified below 2.8.0. No published release removes or bounds the whole-buffer scan. ## Suggested Fix Enforce that the per-line quick check inspects only the paragraph's last line, which is the only line that can form a table header. Compute the last line break with `strrpos($paragraph, "\n")`, derive the last line, and test `strpos($lastLine, '|')` instead of scanning the whole buffer. This bounds the per-line check to the length of the last line and removes the quadratic behavior without changing table detection semantics. As an interim mitigation without a code change, operators can cap the size of untrusted Markdown accepted for conversion, or disable the Table extension for untrusted input, which reduces exposure but does not remove the underlying quadratic path. Reported by zx (GitHub: @manus-pi).

GHSA-3q6v-r5mr-hxv8 Affected league/commonmark Versions

1 component(s)
Fixed Versions:
2.10.2
Product / Package Ecosystem Affected Versions Fixed Version
league/commonmark
Packagist
≥ 2.0.0
< 2.10.2
2.10.2

References

Frequently Asked Questions

What is GHSA-3q6v-r5mr-hxv8?
GHSA-3q6v-r5mr-hxv8 is a security vulnerability in league/commonmark involving Quadratic-time denial of service in the GitHub Flavored Markdown Table extension block-start scan under CWE-400: Uncontrolled Resource Consumption.
Which league/commonmark versions are affected?
Affected versions include releases prior to 2.10.2 across affected packages: league/commonmark.
What version fixes GHSA-3q6v-r5mr-hxv8?
Upgrading to 2.10.2 (or the latest patched release for your respective branch) resolves the vulnerability.
Is league/commonmark affected?
Yes, league/commonmark is affected in releases prior to 2.10.2. Upgrade your dependency to 2.10.2 or later.
What is the CVSS score of GHSA-3q6v-r5mr-hxv8?
GHSA-3q6v-r5mr-hxv8 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with GHSA-3q6v-r5mr-hxv8?
GHSA-3q6v-r5mr-hxv8 is classified under CWE-400: Uncontrolled Resource Consumption (The product does not properly control the allocation and maintenance of a limited resource.).
How can I check Packagist dependencies for GHSA-3q6v-r5mr-hxv8?
Run the dependency tree command: 'inspect dependencies for league/commonmark'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-3q6v-r5mr-hxv8",
  "modified" : "2026-09-30T15:36:14Z",
  "published" : "2026-09-30T15:36:13Z",
  "aliases" : [ ],
  "summary" : "league/commonmark: Quadratic-time denial of service in the GitHub Flavored Markdown Table extension block-start scan",
  "details" : "## Summary\n\n`league/commonmark`'s GitHub Flavored Markdown Table extension registers `TableStartParser` as a block-start parser. While a paragraph is the active block, the core block parser calls `TableStartParser::tryStart()` on every non-blank line. Its first action fetches the entire growing paragraph buffer via `getParagraphContent()` and runs `strpos($paragraph, '|')` across all of it. For a paragraph of M pipe-free lines, line k rescans about k lines of buffer, so total work is 1+2+...+M, which is O(M^2). An unauthenticated user who submits a single large paragraph of pipe-free lines that do not begin with a letter (see Attack Chain) to any service that converts untrusted Markdown with `GithubFlavoredMarkdownConverter` (or any environment that enables `TableExtension`) drives seconds to tens of seconds of single-core CPU that grows quadratically with body size, enough to exhaust worker processes and deny service.\n\n## Root Cause\n\nThe GFM table detector performs a per-line \"quick check\" against the whole accumulated paragraph rather than only the portion that could form a table header. A paragraph never closes while non-blank lines keep arriving, so its buffer grows without bound, and the quick check rescans the entire buffer on each line. Only the paragraph's last line can ever be a table header (it is extracted separately via `strrpos`/`substr`), so scanning the full multi-line buffer for a pipe on every line is unnecessary work and creates quadratic time complexity. There is no input-size cap and the default nesting limit is not reached, so nothing bounds the scan.\n\n```text\nAffected function: League\\CommonMark\\Extension\\Table\\TableStartParser::tryStart\nLocation: src/Extension/Table/TableStartParser.php:35-38\n  $paragraph = $parserState->getParagraphContent();       // returns the FULL growing buffer\n  if ($paragraph === null || \\strpos($paragraph, '|') === false) {\n      return BlockStart::none();                           // strpos scans whole buffer each line\n  }\nBuffer growth: src/Reference/ReferenceParser.php:89-95 ($this->paragraph .= \"\\n\"; $this->paragraph .= $line;)\nPer-line dispatch: src/Parser/MarkdownParser.php:135-147 and :224-236\n```\n\n## Impact\n\nAn unauthenticated, remote attacker with no user interaction can cause denial of service against any application that renders untrusted Markdown with the GitHub Flavored Markdown converter or any configuration that enables the Table extension (the default GFM bundle enables it). A single request carrying a large paragraph of pipe-free, blank-line-free lines that do not begin with a letter consumes CPU proportional to the square of the input size: measured runs show roughly 4x CPU growth per input doubling. A body of a few megabytes ties up a worker for seconds to tens of seconds; repeated or concurrent requests exhaust all available PHP worker processes, denying service to legitimate users. Impact is limited to availability; there is no confidentiality or integrity impact.\n\n## Proof of Concept\n\n`Requires PHP with league/commonmark 2.x installed. The harness parses pipe-free paragraphs of increasing size with the real MarkdownParser and the real block-start parsers, comparing a GFM-plus-Table configuration against a core-only configuration to isolate the Table parser's contribution.`\n\n1. Install `league/commonmark` (2.10.1 or any 2.x release).\n2. Convert a single large paragraph of pipe-free, blank-line-free lines that do not begin with a letter, with `GithubFlavoredMarkdownConverter`.\n3. Measure wall-clock time as the paragraph size doubles.\n\n```php\n<?php\nrequire 'vendor/autoload.php';\nuse League\\CommonMark\\GithubFlavoredMarkdownConverter;\n\n$converter = new GithubFlavoredMarkdownConverter();\nforeach ([25000, 50000, 100000, 200000] as $lines) {\n    $md = str_repeat(\"12345678\\n\", $lines); // one paragraph, no blank lines, no '|', lines not starting with a letter\n    $t = microtime(true);\n    $converter->convert($md);\n    printf(\"%7d lines  %6.3fs\\n\", $lines, microtime(true) - $t);\n}\n```\n\n```text\n   lines    input    letter-leading    non-letter-leading\n                     (\"aaaaaaaa\")          (\"12345678\")\n   25000   0.23MB        0.27s               0.75s\n   50000   0.45MB        0.50s               2.36s\n  100000   0.90MB        1.07s               7.70s\n  200000   1.80MB        2.26s              27.81s\n```\n\nLetter-leading input scales linearly (doubles per input doubling) because `SkipLinesStartingWithLettersParser` aborts before the Table parser is reached. Non-letter-leading input quadruples per doubling (0.75, 2.36, 7.70, 27.81), confirming O(M^2) behavior attributable to the Table start parser.\n\n## Attack Chain\n\n1. **Exposure:** The attacker submits a Markdown body to any endpoint that converts untrusted Markdown with `GithubFlavoredMarkdownConverter`, or any environment that adds `TableExtension`. The default GFM bundle registers the Table extension: `GithubFlavoredMarkdownExtension` adds `TableExtension` (src/Extension/GithubFlavoredMarkdownExtension.php:30), which registers `TableStartParser` (src/Extension/Table/TableExtension.php:56).\n2. **Control:** The attacker fully controls the Markdown body. The exploit input is one paragraph with no blank lines, no `|` character, and lines that do not begin with a letter (for example `str_repeat(\"12345678\\n\", 200000)`).\n3. **Path:** `MarkdownParser::parseLine()` loops over block-start parsers for every non-blank line while a paragraph is active (src/Parser/MarkdownParser.php:135-147). `ParagraphParser::tryContinue()` keeps the paragraph open for every non-blank line, so the buffer keeps growing (src/Parser/Block/ParagraphParser.php:46-53), appended line by line in `ReferenceParser::parse()` (src/Reference/ReferenceParser.php:89-95). Since 2.0.2, the highest-priority `SkipLinesStartingWithLettersParser` returns `BlockStart::abort()` for any line whose first non-space character is a letter, short-circuiting `findBlockStart()` before `TableStartParser`. When lines begin with a non-letter (e.g. a digit), that parser returns none and every remaining core parser returns null, so control reaches `TableStartParser` on every line (src/Parser/MarkdownParser.php:224-236).\n5. **Guard:** There is no input-size cap in `MarkdownInput`, and `max_nesting_level` defaults to `PHP_INT_MAX`, which the depth-1 paragraph never approaches. No guard bounds the whole-buffer scan.\n6. **Primitive:** `TableStartParser::tryStart()` runs `strpos($paragraph, '|')` over the entire buffer returned by `getParagraphContent()` (src/Extension/Table/TableStartParser.php:35-38). With no pipe present, `strpos` scans to the end of the buffer on every line.\n7. **Result:** Cumulative scanning work is O(M^2). A single small request drives disproportionate, quadratically growing CPU, exhausting worker processes and denying service.\n\n## Bypass Evidence\n\nNo prior fix exists for this code path, so this is not an incomplete-fix or regression case; it is a distinct, previously undisclosed quadratic path. The strongest disproof attempts were made and all failed:\n\n- Attempt: the observed quadratic is core-parser or PHP string overhead, not the Table parser. Refuted: the only difference between the two measured configurations is `TableStartParser`; the core-only baseline is linear while the isolated Table contribution quadruples per input doubling.\n- Attempt: `strpos` short-circuits or the buffer does not actually grow. Refuted by source trace: `ReferenceParser::parse()` appends every line unconditionally before its state switch, and for pipe-free text `strpos` finds no `|` and scans the full buffer each line.\n- Attempt: another parser short-circuits before `TableStartParser`. Partially correct: since 2.0.2 `SkipLinesStartingWithLettersParser` aborts block-start scanning for lines beginning with a letter, so letter-leading input (including the original `aaaaaaaa` PoC) never reaches the Table parser and runs in linear time. Input whose lines begin with a non-letter (e.g. a digit) passes every core parser and reaches `TableStartParser` on every line, exhibiting the quadratic behavior.\n- Attempt: an input-size or nesting guard neutralizes it. Refuted: there is no size cap and the default nesting limit is never reached.\n- Attempt: this duplicates an existing quadratic-DoS advisory or was already fixed. Refuted: published quadratic advisories address per-line position translation and other distinct mechanisms in other files; `git log` on `src/Extension/Table/TableStartParser.php` shows no security fix ever touched this rescan, and the code is present unchanged in the latest release.\n\n## Affected Versions\n\n- `Ecosystem: composer`\n- `Package: league/commonmark`\n- `Confirmed affected range: >= 2.0.0, <= 2.10.1`\n- `Latest release checked: 2.10.1 (Packagist, repo.packagist.org/p2/league/commonmark.json)`\n- `Fix status: not fixed`\n\nNote: `SkipLinesStartingWithLettersParser` was introduced in 2.0.2. On 2.0.0 and 2.0.1 the issue can also be triggered with letter-leading lines. Regardless, the underlying quadratic scan itself is present across the whole >= 2.0.0, <= 2.10.1 range.\n\nThe vulnerable rescan is artifact-verified present in tags 2.8.0 and 2.10.1 (the latest published release). The lower bound 2.0.0 is inferred from the 2.x block-parser rewrite that introduced the `getParagraphContent()`-based architecture this quick check depends on; the checkout contains only tags 2.8.0 through 2.10.1, so the path is not artifact-verified below 2.8.0. No published release removes or bounds the whole-buffer scan.\n\n## Suggested Fix\n\nEnforce that the per-line quick check inspects only the paragraph's last line, which is the only line that can form a table header. Compute the last line break with `strrpos($paragraph, \"\\n\")`, derive the last line, and test `strpos($lastLine, '|')` instead of scanning the whole buffer. This bounds the per-line check to the length of the last line and removes the quadratic behavior without changing table detection semantics. As an interim mitigation without a code change, operators can cap the size of untrusted Markdown accepted for conversion, or disable the Table extension for untrusted input, which reduces exposure but does not remove the underlying quadratic path.\n\nReported by zx (GitHub: @manus-pi).",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "league/commonmark"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.0.0"
      }, {
        "fixed" : "2.10.2"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 2.10.1"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/thephpleague/commonmark/security/advisories/GHSA-3q6v-r5mr-hxv8"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/thephpleague/commonmark/commit/5f63680a5e29dd57f9c6be9743b0e90493d3c2d0"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/thephpleague/commonmark"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/thephpleague/commonmark/releases/tag/2.10.2"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-400", "CWE-407" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-09-30T15:36:13Z",
    "nvd_published_at" : null
  }
}