GHSA-gfr2-rv44-r23c: High severity npm/markdown-it vulnerability

Published Oct 20, 2026
·
Updated

Summary

A link reference definition whose label or title is never closed is O(n²) in markdown-it 15.0.2 with default options. Continuation lines are pulled lazily with str += lineContent in src/rulesblock/reference.ts, and the subsequent charCodeAt and slice calls flatten the V8 cons-string, copying everything collected so far; this runs before the rule decides the definition is invalid. It is a regression introduced by PR #1004 in 14.0.0: 13.0.2 processed a 313 KB input in 125 ms, while every version since takes about 11 seconds on the same input. At the sizes below, 200 KB takes 3.6 seconds, 400 KB takes 18.1 seconds, and 800 KB takes 74.0 seconds, which is roughly 4–5× per doubling. No options are required to trigger it.

Details

src/rulesblock/reference.ts appends continuation lines with str += lineContent at lines 74, 83, 100, 133, and 150, then keeps indexing the accumulated string via str.charCodeAt(pos) in the label and whitespace loops and str.slice(...) in parseLinkTitle. V8 represents += as a cons-string, and the next charCodeAt or slice flattens it, copying everything collected so far, so one definition spanning n lines costs O(n²). Isolation confirms the rule is responsible: block-only parsing with inline disabled is still quadratic, md.disable('reference') makes the same input linear, and the same text without the leading [ is linear. The fix is to track positions in state.src instead of building str, or to collect line slices in an array and parse across them. At minimum, stop scanning the label after 999 characters (CommonMark §6.3) before pulling more lines, and cap title continuation. Add a pathological test with a 100k-line unclosed title and label. Open PR #1215 edits the same file but its label check runs after the label loop, so it does not bound either variant and the fix can land together with that PR.

The observed output from PoC was: unclosed title n=100000 200009 B 3585.5 ms unclosed title n=200000 400009 B 18107.1 ms (5.05x) unclosed title n=400000 800009 B 73954.4 ms (4.08x)

Regression comparison at 313 KB: 13.0.2 → 125 ms, 14.1.0 → 11.4 s, 15.0.0 → 10.6 s, 15.0.2 → 10.7 s.

Proof of concept

How to execute the PoC: 1. mkdir -p markdown-it-poc && cd markdown-it-poc 2. npm init -y 3. npm i markdown-it@15.0.2 4. node pocmarkdownit.js

PoC below is: javascript // pocmarkdownit.js // Requires: npm install markdown-it@15.0.2 const MarkdownIt = require('markdown-it'); const md = new MarkdownIt();

function bench(label, src) { const t0 = process.hrtime.bigint(); md.render(src); const t1 = process.hrtime.bigint(); console.log( ${label.padEnd(30)} ${String(src.length).padStart(9)} B ${(Number(t1 - t0) / 1e6).toFixed(1).padStart(9)} ms ); }

// Unclosed title: one reference definition spread over many lines for (const n of [100000, 200000, 400000]) { bench(unclosed title n=${n}, '[a]: /u "' + 'x\n'.repeat(n)); }

// Unclosed label for (const n of [5000, 10000, 20000]) { bench(unclosed label n=${n}, '[' + 'a\n'.repeat(n)); }

Impact

This is an algorithmic-complexity denial of service (CWE-407). Any server that renders user Markdown with markdown-it is affected with default options, including comments, issues, chat, wikis, docs previews, and LLM chat UIs. A ~400 KB request body blocks the Node.js event loop for about 18 seconds and an ~800 KB body for about 74 seconds. This is the same class and attacker model as GHSA-253c, but it is reachable without any opt-in option, since the affected path is a core block rule.

Affected Software

2 affected componentsFixes available
npm/markdown-it>=15.0.0<15.0.3
15.0.3
npm/markdown-it>=14.1.0<14.3.3
14.3.3

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/markdown-it to a version that resolves this vulnerability.

    Fixed in 15.0.3
  2. Upgrade

    Upgrade npm/markdown-it to a version that resolves this vulnerability.

    Fixed in 14.3.3
  3. Compensating control

    Modify the markdown-it reference-definition parser to stop scanning an unclosed label after 999 characters, cap title continuation, and avoid quadratic string construction by tracking positions in state.src or collecting line slices in an array before parsing.

Event History

Oct 20, 2026
Advisory Published
via GitHub·08:28 PM
Data Sourced
via GitHub·08:28 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Who is exposed to this issue?

Applications that parse attacker-controlled or otherwise untrusted Markdown with markdown-it are exposed to a denial-of-service risk. The issue is reachable with markdown-it's default options.

2

What does an attacker need to submit to trigger the slowdown?

An attacker needs to provide Markdown containing a link reference definition with an unclosed label or title that spans continuation lines. No authentication, user interaction, or special parser options are required.

3

Which versions are affected?

The regression was introduced in version 14.0.0. Version 13.0.2 did not exhibit the reported behavior, while the advisory states that every version since the regression has the quadratic processing behavior.

4

How can I recognize the issue in a deployment?

Test parsing a Markdown input containing an unclosed reference definition across many continuation lines and observe parsing time as input size increases. Reported timings grew from 3.6 seconds at 200 KB to 18.1 seconds at 400 KB and 74.0 seconds at 800 KB, consistent with quadratic growth.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203