markdown-it CVE-2026-48988
MEDIUMSeverity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Network-reachable with zero complexity and no auth; A:H because a single 160KB request blocks a server thread for 21+ seconds, enabling realistic complete service denial.
Primary rating from Vendor (https://github.com/markdown-it/markdown-it).
CVSS VectorVendor: https://github.com/markdown-it/markdown-it
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Lifecycle Timeline
2Blast Radius
ecosystem impact- 120 npm packages depend on markdown-it (36 direct, 85 indirect)
Ecosystem-wide dependent count for version 14.2.0.
DescriptionCVE.org
Summary
A quadratic time complexity vulnerability exists in markdown-it's smartquotes rule (enabled via the typographer: true option). An attacker can craft a markdown input consisting of consecutive quotation marks that causes the parser to consume excessive CPU time, leading to denial of service.
Details
The vulnerability is in the replaceAt() helper function used by the smartquotes rule in lib/rules_core/smartquotes.mjs:
function replaceAt (str, index, ch) {
return str.slice(0, index) + ch + str.slice(index + 1)
}When markdown-it processes a text token containing many quotation marks (either " or ') with typographer: true, the smartquotes rule iterates through each quote character and calls replaceAt() to substitute it with a typographic (curly) quote. Each call to replaceAt() creates three new string slices and concatenates them, which is an O(n) operation where n is the length of the string.
Since this is called once per quote character in the token, and there are n quote characters, the total time complexity becomes O(n^2).
The root cause is that the smartquotes rule modifies token.content in place using string slicing rather than building the result incrementally. The process_inlines() function (line 14) processes each quote in the text token, and for matching quote pairs, calls replaceAt() on both the opening and closing token's content (lines 151-152). When the entire input is a single text token of quote characters, this results in quadratic behavior.
PoC
const md = require('markdown-it');
const instance = md({ typographer: true });
// 160,000 consecutive double-quote characters
const payload = '"'.repeat(160000);
console.time('render');
instance.render(payload);
console.timeEnd('render');
// Output: render: ~21000ms (21 seconds)
// Compare with typographer disabled:
const safe = md({ typographer: false });
console.time('render-safe');
safe.render(payload);
console.timeEnd('render-safe');
// Output: render-safe: ~8msMeasured timing on a modern system:
- 10,000 quotes: ~19ms
- 20,000 quotes: ~51ms
- 40,000 quotes: ~212ms
- 80,000 quotes: ~5,430ms
- 160,000 quotes: ~21,198ms
The scaling is clearly superlinear (quadratic), with the 80K->160K step showing a ~3.9x increase for a 2x input increase, consistent with O(n^2).
Impact
Applications that render user-supplied markdown with typographer: true are vulnerable to denial of service. An attacker can submit a relatively small payload (160KB of quote characters) that causes the server to spend over 21 seconds processing a single request. Repeated submissions can exhaust server CPU resources and prevent legitimate users from being served.
The impact is mitigated by the fact that the typographer option defaults to false and must be explicitly enabled. However, the typographer feature is commonly enabled in production applications that want smart typography, and the markdown-it documentation prominently suggests enabling it.
A suggested fix would be to replace the replaceAt() approach with an array-based or StringBuilder-style approach that collects all replacements and applies them in a single pass, reducing the time complexity to O(n).
AnalysisAI
Denial of service in the markdown-it npm library (≤ 14.1.1) stems from a quadratic O(n²) CPU complexity bug in the smartquotes processing rule, exploitable only when typographer: true is explicitly enabled. An unauthenticated remote attacker can submit approximately 160KB of consecutive quotation marks to a server-side markdown rendering endpoint and consume over 21 seconds of CPU per request; flooding the endpoint with concurrent payloads can fully exhaust server CPU and prevent legitimate users from being served. No confirmed active exploitation (not in CISA KEV), but a detailed proof-of-concept with timing measurements is publicly documented in the upstream GitHub Security Advisory GHSA-6v5v-wf23-fmfq, and a vendor-released patch is available in version 14.2.0.
Technical ContextAI
The vulnerability resides in lib/rules_core/smartquotes.mjs of the markdown-it npm package (CPE: pkg:npm/markdown-it). When the library is instantiated with typographer: true, the smartquotes rule iterates over every quotation mark character in a text token and invokes the internal replaceAt(str, index, ch) helper to substitute ASCII quotes with typographic curly equivalents. Each replaceAt() call performs three JavaScript string slice-and-concatenate operations, making it O(n) relative to token length. Because process_inlines() calls replaceAt() once per quote character and the entire input can form a single text token, the aggregate complexity for n quote characters is O(n²). This matches CWE-400 (Uncontrolled Resource Consumption): the algorithm does not bound the work performed relative to input size. The fix approach (building results incrementally via an array pass rather than repeated slice-and-concat) reduces complexity to O(n). The root flaw is an algorithmic design choice, not a memory corruption or injection issue.
RemediationAI
The primary fix is to upgrade markdown-it to version 14.2.0 or later, where the replaceAt() string-slicing approach is replaced with a single-pass array-based method that eliminates the quadratic complexity. The vendor advisory is at https://github.com/markdown-it/markdown-it/security/advisories/GHSA-6v5v-wf23-fmfq. If an immediate upgrade is not feasible, disable the typographer option by ensuring md({ typographer: false }) or simply omitting the option (false is the default) - this completely eliminates the vulnerable code path with no impact on core markdown rendering, only losing curly-quote and smart-punctuation substitution. For applications that must retain typographer functionality, implement upstream input-length caps on user-supplied markdown (e.g., reject or truncate payloads exceeding 10,000-20,000 characters before passing to the parser) and apply per-IP or per-session rate limiting on markdown rendering endpoints to reduce the feasibility of concurrent flooding attacks. Note that input truncation may break legitimate long-form content; rate limiting adds latency under high legitimate load.
Same weakness CWE-400 – Uncontrolled Resource Consumption
View allSame technique Denial Of Service
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-6v5v-wf23-fmfq