renovate[bot] 0d3952d3c5 Update dependency linkify-it to v5 [SECURITY] (#5116)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [linkify-it](https://redirect.github.com/markdown-it/linkify-it) |
[`3.0.3` →
`5.0.2`](https://renovatebot.com/diffs/npm/linkify-it/3.0.3/5.0.2) |
![age](https://developer.mend.io/api/mc/badges/age/npm/linkify-it/5.0.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/linkify-it/3.0.3/5.0.2?slim=true)
|

---

### LinkifyIt#match scan loop has quadratic algorithmic complexity
[CVE-2026-48801](https://nvd.nist.gov/vuln/detail/CVE-2026-48801) /
[GHSA-22p9-wv53-3rq4](https://redirect.github.com/advisories/GHSA-22p9-wv53-3rq4)

<details>
<summary>More information</summary>

#### Details
##### 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.

#### Severity
- CVSS Score: 8.7 / 10 (High)
- Vector String:
`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`

#### References
-
[https://github.com/markdown-it/linkify-it/security/advisories/GHSA-22p9-wv53-3rq4](https://redirect.github.com/markdown-it/linkify-it/security/advisories/GHSA-22p9-wv53-3rq4)
-
[https://github.com/advisories/GHSA-22p9-wv53-3rq4](https://redirect.github.com/advisories/GHSA-22p9-wv53-3rq4)

This data is provided by the [GitHub Advisory
Database](https://redirect.github.com/advisories/GHSA-22p9-wv53-3rq4)
([CC-BY
4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### linkify-it: Quadratic-complexity DoS via the `mailto:` validator
scan-loop on attacker text
[CVE-2026-59887](https://nvd.nist.gov/vuln/detail/CVE-2026-59887) /
[GHSA-v245-v573-v5vm](https://redirect.github.com/advisories/GHSA-v245-v573-v5vm)

<details>
<summary>More information</summary>

#### Details
##### Summary
`linkify-it`'s schema-scan loop (`.test()` / `.match()`, the documented
public API) invokes the `mailto:`
schema validator at **every** `mailto:` occurrence in the input text.
For each occurrence the validator does
`text.slice(pos)` (an O(n) copy) and runs an email regex whose
local-part class `src_email_name` greedily
scans the **entire remaining tail** (O(n)) before failing. With N
`mailto:` occurrences that is
**N × O(n) = O(n²)**. Because linkify-it runs on arbitrary user text
(markdown-it feeds it whole documents
when `linkify:true`), an unauthenticated attacker can block the
single-threaded event loop for many seconds
with a small input. No length bound (unlike an HTTP header).

##### Root cause — `index.mjs` + `lib/re.mjs`
```js
// index.mjs (mailto validator) — runs at every "mailto:" hit
'mailto:': { validate: function (text, pos, self) {
  const tail = text.slice(pos)                                  // O(n) copy per hit
  if (!self.re.mailto) self.re.mailto = new RegExp('^' + self.re.src_email_name + '@&#8203;' + self.re.src_host_strict, 'i')
  if (self.re.mailto.test(tail)) { ... }                        // scans the whole O(n) tail
  return 0
}}
// lib/re.mjs:91-93 — every char of "mailto:" (incl. ':','-',';') is in this class:
re.src_email_name = '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*'
```
The `while ((m = re.exec(text)) !== null) { …testSchemaAt… }` scan loop
calls the validator at each
`mailto:` hit; `src_email_name` greedily consumes the whole tail (all
chars are in its class) then fails for
lack of `@`. `http:`/`https:` do NOT blow up — their validator requires
the tail to start with `//`, failing
in O(1) per hit.

##### Proof of Concept (confirmed, linkify-it 5.0.1, Node v24)
```js
const LinkifyIt = require('linkify-it');
const lf = new LinkifyIt();
lf.match('mailto:'.repeat(48000));   // ~336 KB of "mailto:mailto:…" -> seconds of blocked event loop
```
| input (same bytes) | 56 KB | 112 KB | 224 KB | 336 KB |
|---|---:|---:|---:|---:|
| **`mailto:` contiguous** | 97 ms | 357 ms | 1438 ms | 3272 ms |
| `mailto:` space-separated | 2 ms | 3 ms | 5 ms | 8 ms |
| `http://` contiguous | 12 ms | 17 ms | 33 ms | 49 ms |

×~4 per 2× input ⇒ O(n²); equal-byte controls stay flat ⇒ algorithmic,
not a GC/allocation artifact.
Real-world via markdown-it 14.x (`{linkify:true}`),
`md.render('mailto:'.repeat(n))`: 219 KB ≈ ~5 s.
<img width="737" height="161" alt="image"
src="https://github.com/user-attachments/assets/b5d390f3-68d0-4861-9c47-ad8aff0203d5"
/>

##### Impact
Reachable on arbitrary user text via the documented `.test()`/`.match()`
API and through markdown-it's
linkifier — comment systems, chat, forums, wikis, note apps that render
user markdown with linkify enabled.
A ~220 KB post hangs the event loop ~5 s; a few hundred KB → tens of
seconds. Availability only.

##### Suggested remediation
Bound the email local-part per RFC 5321 (≤64) so per-hit work is O(1),
and avoid the full-tail slice:
```js
// lib/re.mjs — cap the greedy run:
re.src_email_name = '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]{0,63}'
// index.mjs — prefer a sticky regex anchored at `pos` over text.slice(pos).
```

##### Affected / disclosure
All versions through 5.0.1 (latest); same code on `master`. cve-mcp/OSV
report no known vulnerability for
linkify-it. Distinct from markdown-it's own `*`-run ReDoS
(CVE-2026-2327, different package/path) and the
recent markdown-it DoS. Reported privately; happy to test a patch
against the PoC.

#### Severity
- CVSS Score: 7.5 / 10 (High)
- Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`

#### References
-
[https://github.com/markdown-it/linkify-it/security/advisories/GHSA-v245-v573-v5vm](https://redirect.github.com/markdown-it/linkify-it/security/advisories/GHSA-v245-v573-v5vm)
-
[https://nvd.nist.gov/vuln/detail/CVE-2026-59887](https://nvd.nist.gov/vuln/detail/CVE-2026-59887)
-
[105e5d77f7)
-
[https://github.com/markdown-it/linkify-it/releases/tag/5.0.2](https://redirect.github.com/markdown-it/linkify-it/releases/tag/5.0.2)
-
[https://github.com/advisories/GHSA-v245-v573-v5vm](https://redirect.github.com/advisories/GHSA-v245-v573-v5vm)

This data is provided by the [GitHub Advisory
Database](https://redirect.github.com/advisories/GHSA-v245-v573-v5vm)
([CC-BY
4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Release Notes

<details>
<summary>markdown-it/linkify-it (linkify-it)</summary>

###
[`v5.0.2`](https://redirect.github.com/markdown-it/linkify-it/blob/HEAD/CHANGELOG.md#502--2026-07-02)

[Compare
Source](https://redirect.github.com/markdown-it/linkify-it/compare/5.0.1...5.0.2)

- Fixed DoS in `mailto:` links (restrict user name to 64 chars).
- Restricted user/pass part length in links.

###
[`v5.0.1`](https://redirect.github.com/markdown-it/linkify-it/blob/HEAD/CHANGELOG.md#501--2026-05-23)

[Compare
Source](https://redirect.github.com/markdown-it/linkify-it/compare/5.0.0...5.0.1)

- Fixed DoS in fuzzy links/emails search.
- Reworked search logic - check each pattern separate, use `g` regexes
instead
  of slice.
- Removed internal cache - useless overcomplication.

###
[`v5.0.0`](https://redirect.github.com/markdown-it/linkify-it/blob/HEAD/CHANGELOG.md#500--2023-12-01)

[Compare
Source](https://redirect.github.com/markdown-it/linkify-it/compare/4.0.1...5.0.0)

- Rewrite to ESM.

###
[`v4.0.1`](https://redirect.github.com/markdown-it/linkify-it/blob/HEAD/CHANGELOG.md#401--2022-05-02)

[Compare
Source](https://redirect.github.com/markdown-it/linkify-it/compare/4.0.0...4.0.1)

- Fix `http://` incorrectly returned as a link by matchStart.

###
[`v4.0.0`](https://redirect.github.com/markdown-it/linkify-it/blob/HEAD/CHANGELOG.md#400--2022-04-22)

[Compare
Source](https://redirect.github.com/markdown-it/linkify-it/compare/3.0.3...4.0.0)

- Add `matchAtStart` method to match full URLs at the start of the
string.
- Fixed paired symbols (`()`, `{}`, `""`, etc.) after punctuation.
- `---` option now affects parsing of emails (e.g.
`user@example.com---`)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/thelounge/thelounge).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI3NS4yIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbIlR5cGU6IFNlY3VyaXR5Il19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-07-28 12:30:43 -07:00
2023-12-24 16:55:45 +01:00
2026-07-13 11:40:38 -07:00

The Lounge

Modern web IRC client designed for self-hosting

WebsiteDocsDemoDocker

#thelounge IRC channel on Libera.Chat npm version Build Status

Overview

  • Modern features brought to IRC. Push notifications, link previews, new message markers, and more bring IRC to the 21st century.
  • Always connected. Remains connected to IRC servers while you are offline.
  • Cross platform. It doesn't matter what OS you use, it just works wherever Node.js runs.
  • Responsive interface. The client works smoothly on every desktop, smartphone and tablet.
  • Synchronized experience. Always resume where you left off no matter what device.

To learn more about configuration, usage and features of The Lounge, take a look at the website.

The Lounge is the official and community-managed fork of Shout, by Mattias Erming.

Installation and usage

The Lounge requires latest Node.js LTS version or more recent. The Yarn package manager is also recommended. If you want to install with npm, --unsafe-perm is required for a correct install.

Running stable releases

Please refer to the install and upgrade documentation on our website for all available installation methods.

Running from source

The following commands install and run the development version of The Lounge:

git clone https://github.com/thelounge/thelounge.git
cd thelounge
yarn install
NODE_ENV=production yarn build
yarn start

When installed like this, thelounge executable is not created. Use node index <command> to run commands.

⚠️ While it is the most recent codebase, this is not production-ready! Run at your own risk. It is also not recommended to run this as root.

Development setup

Simply follow the instructions to run The Lounge from source above, on your own fork.

Before submitting any change, make sure to:

  • Read the Contributing instructions
  • Run yarn test to execute linters and the test suite
    • Run yarn format:prettier if linting fails
  • Run yarn build:client if you change or add anything in client/js or client/components
    • The built files will be output to public/ by webpack
  • Run yarn build:server if you change anything in server/
    • The built files will be output to dist/ by tsc
  • yarn dev can be used to start The Lounge with hot module reloading

To ensure that you don't commit files that fail the linting, you can install a pre-commit git hook. Execute yarn githooks-install to do so.

Description
No description provided
Readme MIT 60 MiB
Languages
TypeScript 63%
Vue 23.7%
CSS 7.3%
JavaScript 5.7%
HTML 0.3%