---
id: "GHSA-253c-mchw-3w2r"
kind: "vulnerability"
aliases: ["GHSA-253c-mchw-3w2r"]
title: "markdown-it linkify: true has two quadratic paths, so a few hundred KB of markdown blocks the event loop for tens of seconds"
severity: "medium"
cvss: null
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "markdown-it", "fixed": ["15.0.1"], "affected": "= 15.0.0", "ecosystem": "npm"}, {"name": "markdown-it", "fixed": ["14.3.1"], "affected": "< 14.3.1", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["cwe-400", "cwe-407", "markdown-it", "node.js", "package", "user-supplied-markdown", "web-apps"]
not_affected_if: ["default-config", "linkify-disabled", "no-untrusted-markdown"]
published_at: "2026-09-29T17:57:56+00:00"
first_seen_at: "2026-10-23T19:11:31+00:00"
updated_at: "2026-10-23T20:20:16+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-253c-mchw-3w2r", "first_seen_at": "2026-10-23T19:11:31+00:00"}]
---

# markdown-it linkify: true has two quadratic paths, so a few hundred KB of markdown blocks the event loop for tens of seconds

markdown-it with linkify enabled has two quadratic-time code paths, so a few hundred KB of crafted markdown can block the Node.js event loop for tens of seconds. The attacker gets denial of service only; nothing is read, written or executed.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:markdown-it affected = 15.0.0 fixed 15.0.1
- npm:markdown-it affected < 14.3.1 fixed 14.3.1

## How to check

Run `npm ls markdown-it` and look for 15.0.0 or any version below 14.3.1 in the tree.
Check whether the app sets `linkify: true` in its MarkdownIt constructor; with the default `linkify: false` the advisory does not apply.

## Fix

Upgrade markdown-it to 15.0.1, or to 14.3.1 if you are on the 14.x line.
If you cannot upgrade, keep linkify off or cap the size of user-supplied markdown before rendering.

## Timeline

- 2026-10-23T19:11:31+00:00 ghsa https://github.com/advisories/GHSA-253c-mchw-3w2r

## From the sources

## Summary

Two independent quadratic paths in the `linkify: true` handling. Both are in markdown-it's own code rather than in linkify-it, which stays linear on both payloads.

`src/rules_core/linkify.ts` calls `arrayReplaceAt` once per linkified text token, and that rebuilds the whole `children` array each time. A paragraph of N soft-broken lines is one inline token with about 2N children, so you get N rebuilds over a 2N array. Schema-less emails are what reach it. A `http://` link gets consumed by the inline rule first and never arrives as a text token, so those stay linear.

`src/rules_inline/linkify.ts` runs `state.pending.match(SCHEME_RE)` at every `://` in the source. `state.pending` only gets truncated once a link is actually produced, so an unregistered scheme leaves it growing and every `://` rescans the lot.

## Proof of concept

Clean install of 15.0.0 from npm, `new MarkdownIt({ linkify: true }).render(payload)`, Node 24.

| N | 5,000 | 10,000 | 20,000 | 40,000 |
|---|---|---|---|---|
| `'a@b.co\n'.repeat(N)`, 34KB to 273KB | 1.1s | 4.7s | 19.6s | 89s |
| `'a://'.repeat(N)`, 20KB to 156KB | 0.25s | 0.76s | 3.1s | 15.0s |

Doubling the input roughly quadruples the time in both. With `linkify: false` the same inputs run in 25 to 131ms and stay flat.

Controls for the first one, all at N=20000: putting every email in a single text token (`'a@b.co '`) takes 0.59s, one email per paragraph (`'a@b.co\n\n'`) takes 0.64s, and soft-broken lines with nothing linkifiable take 42ms. So it needs many children AND many of them linkifying. For the second, replaying just the `SCHEME_RE` calls against the same growing prefixes with no markdown-it involved accounts for 11.7s of the 15s.

Ordinary prose does it too. `'ping a@b.co ok\n'.repeat(20000)` is 293KB and takes 31s.

## Caveat

`linkify` is off by default, so this only reaches apps that turn it on.

## Impact

Availability only. A few hundred KB of fairly ordinary markdown pins one core for tens of seconds, and because it's quadratic it degrades quickly with size. Nothing is read, written or executed.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
