Skip to content

Commit 77febc8

Browse files
1 parent ba7e7b9 commit 77febc8

7 files changed

Lines changed: 504 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-2wc2-fm75-p42x",
4+
"modified": "2026-07-09T13:37:40Z",
5+
"published": "2026-07-09T13:37:40Z",
6+
"aliases": [
7+
"CVE-2026-49476"
8+
],
9+
"summary": "Soup Sieve has Memory Exhaustion via Large Comma-Separated Selector Lists",
10+
"details": "### Summary\n\nThe CSS selector parser in soupsieve (the CSS selector engine for Beautiful Soup 4) allocates unbounded memory when compiling large comma-separated selector lists. An attacker who can supply a crafted CSS selector string to `soupsieve.compile()` or Beautiful Soup's `.select()` / `.select_one()` can cause the application to allocate hundreds of megabytes of heap memory from a relatively small input, leading to memory exhaustion and denial of service.\n\nTo be completely transparent, AI tools helped surface this issue. However, it was independently reproduced and carefully validated. Researchers follow responsible disclosure practices and originally shared this report privately.\n\nA **500 KB** selector string triggers allocation of approximately **244 MB** of heap memory - a 488x— amplification ratio**.\n\n### Details\n\n**Affected code:** `soupsieve/css_parser.py`, lines ~204, 925, 1106\n\nThe soupsieve CSS parser splits comma-separated selector lists and creates one `CSSSelector` object per list item. Each `CSSSelector` object contains parsed selector data structures including `SelectorList`, `Selector`, and associated tag/attribute/pseudo-class metadata.\n\nWhen a selector string such as `a,a,a,...` (with 250,000 comma-separated items) is passed to `sv.compile()`, the parser:\n\n1. Tokenises the entire string and identifies each comma-delimited segment (line ~1106)\n2. Parses each segment into a full `Selector` object with all associated metadata (line ~925)\n3. Stores all parsed selectors in a `SelectorList` (line ~204)\n\n**Root cause:** No limit is enforced on the number of selectors in a comma-separated list. The parser will attempt to parse and store an arbitrary number of selectors, with each selector object consuming approximately **976 bytes** of heap memory. The total allocation scales linearly with the number of list items, but the amplification ratio (output memory / input bytes) is extremely high because each single-character selector like `a` expands into a complex object graph.\n\n**Attack surface:** Any application that passes user-supplied CSS selectors to `soupsieve.compile()` or Beautiful Soup's `.select()` / `.select_one()`.\n\n### Proof of Concept\n\n```python\nimport tracemalloc\nimport soupsieve as sv\n\ntracemalloc.start()\n\n# Build a 500 KB selector string: \"a,a,a,...,a\" (250,000 items)\ncount = 250_000\nselector = \",\".join(\"a\" for _ in range(count))\nprint(f\"Selector string size: {len(selector):,} bytes ({len(selector) / 1024:.0f} KB)\")\n\n# Compile the selector — this allocates ~244 MB\ncompiled = sv.compile(selector)\n\ncurrent, peak = tracemalloc.get_traced_memory()\ntracemalloc.stop()\n\nprint(f\"Compiled selector count: {len(compiled.selectors):,}\")\nprint(f\"Current memory: {current / 1024 / 1024:.1f} MB\")\nprint(f\"Peak memory: {peak / 1024 / 1024:.1f} MB\")\nprint(f\"Amplification ratio: {peak / len(selector):.0f}x\")\n\n# Expected output:\n# Selector string size: 499,999 bytes (488 KB)\n# Compiled selector count: 250,000\n# Current memory: ~244 MB\n# Peak memory: ~244 MB\n# Amplification ratio: ~488x\n```\n\n### Impact\n\n**Severity: High**\n\nAn attacker can exhaust available memory on any server-side Python application that compiles user-supplied CSS selectors via soupsieve. This can cause:\n\n- **OOM kills** in containerised deployments (Kubernetes pods, Docker containers) with memory limits\n- **Swap thrashing** on bare-metal servers, degrading performance for all co-located processes\n- **Process termination** via Python's `MemoryError` exception if the system runs out of addressable memory\n\n| Parameter | Value |\n|---|---|\n| Input size | ~500 KB selector string |\n| Memory allocated | ~244 MB |\n| Amplification ratio | ~488× |\n| Per-object overhead | ~976 bytes per selector |\n| Authentication required | None |\n| User interaction required | None |\n\n**Scalability of attack:** The memory allocation scales linearly - doubling the selector count doubles memory usage. An attacker can tune the payload to exactly exhaust a target's memory limits. Multiple concurrent requests multiply the effect.\n\n**Downstream exposure:** soupsieve is an automatic dependency of `beautifulsoup4`, one of the most widely installed Python packages. Any web application accepting CSS selectors from users (e.g., web scraping APIs, content filtering tools, CMS preview features) is potentially affected.\n\n---\n### Credit\n\nDiscovered by a security research team from the University of Sydney, focused on detecting open source software vulnerabilities.\nLiyi Zhou: https://lzhou1110.github.io/\nZiyue Wang: https://zyy0530.github.io/\nStrick: https://str1ckl4nd.github.io/\nMaurice: https://maurice.busystar.org/\nChenchen Yu: https://7thparkk.github.io/",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "soupsieve"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "2.8.4"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 2.8.3"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/facelessuser/soupsieve/security/advisories/GHSA-2wc2-fm75-p42x"
45+
},
46+
{
47+
"type": "PACKAGE",
48+
"url": "https://github.com/facelessuser/soupsieve"
49+
}
50+
],
51+
"database_specific": {
52+
"cwe_ids": [
53+
"CWE-400",
54+
"CWE-770"
55+
],
56+
"severity": "HIGH",
57+
"github_reviewed": true,
58+
"github_reviewed_at": "2026-07-09T13:37:40Z",
59+
"nvd_published_at": null
60+
}
61+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-387m-935m-c4vw",
4+
"modified": "2026-07-09T13:36:49Z",
5+
"published": "2026-07-09T13:36:49Z",
6+
"aliases": [],
7+
"summary": "Micronaut doesn't set a maximum redirect count for its HTTP Client, enabling infinite loop DoS",
8+
"details": "The Netty-based Micronaut HTTP Client does not impose a limit on HTTP redirections, potentially allowing an infinite redirect loop that could lead to a denial-of-service attack.\n\n### Patches\n\nThe following versions are patched: \n\n- For Micronaut 5, versions equal or greater than [5.0.1](https://github.com/micronaut-projects/micronaut-core/releases/v5.0.1) >=\n- For Micronaut 4, versions equal or greater than [4.10.24](https://github.com/micronaut-projects/micronaut-core/releases/v4.10.24) >=\n- For Micronaut 3, versions equal or greater than [3.10.7](https://github.com/micronaut-projects/micronaut-core/releases/v3.10.7) >=\n\n### Workarounds\nNo\n\n### Resources\nMicronaut 5 Patch: https://github.com/micronaut-projects/micronaut-core/commit/6e88a972718d6e1521c5b3bb7766451798dba4e3\nMicronaut 4 Patch: https://github.com/micronaut-projects/micronaut-core/commit/f1dffffec8fb5e3b7e94ae907ce0be3831e499d4\nMicronaut 3 Patch: https://github.com/micronaut-projects/micronaut-core/commit/c06a2715ca7f78321bc3ca05f41cca78cd351320",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "Maven",
19+
"name": "io.micronaut:micronaut-http-client"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "3.10.7"
30+
}
31+
]
32+
}
33+
]
34+
},
35+
{
36+
"package": {
37+
"ecosystem": "Maven",
38+
"name": "io.micronaut:micronaut-http-client"
39+
},
40+
"ranges": [
41+
{
42+
"type": "ECOSYSTEM",
43+
"events": [
44+
{
45+
"introduced": "4.0.0-M1"
46+
},
47+
{
48+
"fixed": "4.10.24"
49+
}
50+
]
51+
}
52+
]
53+
},
54+
{
55+
"package": {
56+
"ecosystem": "Maven",
57+
"name": "io.micronaut:micronaut-http-client"
58+
},
59+
"ranges": [
60+
{
61+
"type": "ECOSYSTEM",
62+
"events": [
63+
{
64+
"introduced": "5.0.0-M1"
65+
},
66+
{
67+
"fixed": "5.0.1"
68+
}
69+
]
70+
}
71+
]
72+
}
73+
],
74+
"references": [
75+
{
76+
"type": "WEB",
77+
"url": "https://github.com/micronaut-projects/micronaut-core/security/advisories/GHSA-387m-935m-c4vw"
78+
},
79+
{
80+
"type": "WEB",
81+
"url": "https://github.com/micronaut-projects/micronaut-core/commit/6e88a972718d6e1521c5b3bb7766451798dba4e3"
82+
},
83+
{
84+
"type": "WEB",
85+
"url": "https://github.com/micronaut-projects/micronaut-core/commit/c06a2715ca7f78321bc3ca05f41cca78cd351320"
86+
},
87+
{
88+
"type": "WEB",
89+
"url": "https://github.com/micronaut-projects/micronaut-core/commit/f1dffffec8fb5e3b7e94ae907ce0be3831e499d4"
90+
},
91+
{
92+
"type": "PACKAGE",
93+
"url": "https://github.com/micronaut-projects/micronaut-core"
94+
}
95+
],
96+
"database_specific": {
97+
"cwe_ids": [],
98+
"severity": "HIGH",
99+
"github_reviewed": true,
100+
"github_reviewed_at": "2026-07-09T13:36:49Z",
101+
"nvd_published_at": null
102+
}
103+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-52vm-mxx8-f227",
4+
"modified": "2026-07-09T13:37:34Z",
5+
"published": "2026-07-09T13:37:34Z",
6+
"aliases": [],
7+
"summary": "Phantom: Arbitrary file write and decode-bomb DoS via unconfined MCP tool paths",
8+
"details": "### Impact\n\nIn Phantom <= 1.3.0, when `PHANTOM_OUTPUT_DIR` was unset (the default), the MCP tools accepted arbitrary absolute output paths with no confinement. Anything able to send tool calls (e.g. an AI agent driving the MCP interface) could **write or overwrite arbitrary files** the process user can write — including shell startup files (`~/.zshrc`) or a Reaper `__startup.lua`, which is effectively local code execution on a developer workstation.\n\nSeparately, the stem-separation and render paths decoded input audio with no size/duration cap (the analysis path was already guarded). A small, highly compressed FLAC/OGG could expand to multi-gigabyte PCM, causing memory-exhaustion DoS, and widened exposure to decoder bugs including libsndfile CVE-2026-37555.\n\n### Patches\nFixed in **1.3.1**:\n- File writes are always confined to `PHANTOM_OUTPUT_DIR` (default `~/.phantom/output`); symlinks resolved and re-verified on the final path.\n- Decode/duration/size guards mirrored onto the separation and render paths (plus ffmpeg `-max_alloc`/`-t`/`-fs`).\n- Atomic `O_CREAT|O_EXCL` output creation in reference matching and symlink-TOCTOU hardening on confined input reads.\n\n### Workarounds\nSet `PHANTOM_OUTPUT_DIR` (and optionally `PHANTOM_AUDIO_DIR`) to dedicated directories before starting the server.\n\n### Credit\nFound during an internal security audit.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "PyPI",
19+
"name": "phantom-audio"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "1.3.1"
30+
}
31+
]
32+
}
33+
],
34+
"database_specific": {
35+
"last_known_affected_version_range": "<= 1.3.0"
36+
}
37+
}
38+
],
39+
"references": [
40+
{
41+
"type": "WEB",
42+
"url": "https://github.com/fadelabs/phantom/security/advisories/GHSA-52vm-mxx8-f227"
43+
},
44+
{
45+
"type": "PACKAGE",
46+
"url": "https://github.com/fadelabs/phantom"
47+
}
48+
],
49+
"database_specific": {
50+
"cwe_ids": [
51+
"CWE-22",
52+
"CWE-400",
53+
"CWE-73"
54+
],
55+
"severity": "HIGH",
56+
"github_reviewed": true,
57+
"github_reviewed_at": "2026-07-09T13:37:34Z",
58+
"nvd_published_at": null
59+
}
60+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-836r-79rf-4m37",
4+
"modified": "2026-07-09T13:37:46Z",
5+
"published": "2026-07-09T13:37:46Z",
6+
"aliases": [
7+
"CVE-2026-49477"
8+
],
9+
"summary": "Soup Sieve: Regular Expression Denial of Service (ReDoS) via Selector Parser",
10+
"details": "### Summary\n\nThe CSS selector parser in soupsieve (the CSS selector engine for Beautiful Soup 4) contains a regular expression vulnerable to catastrophic backtracking. When processing an attribute selector with an unterminated quoted value, the `VALUE` regex pattern in `css_parser.py` enters exponential backtracking. A payload of only **300 bytes** causes the regex engine to hang for **over 3 seconds**, enabling a trivial Regular Expression Denial of Service (ReDoS) attack.\n\nTo be completely transparent, AI tools helped surface this issue. However, this was independently reproduced and carefully validated.\n\nAny application that passes untrusted CSS selector strings to `soupsieve.compile()` or Beautiful Soup's `.select()` / `.select_one()` is affected.\n\n### Details\n\n**Affected code:** `soupsieve/css_parser.py`, line ~121 - `RE_VALUES` / `VALUE` regex pattern\n\nThe soupsieve CSS parser uses a compiled regular expression to tokenise attribute selector values. This pattern matches both quoted strings (`\"value\"` or `'value'`) and unquoted identifiers. The regex contains alternation branches for:\n\n1. Double-quoted strings: `\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"`\n2. Single-quoted strings: `'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'`\n3. Unquoted identifiers\n\nWhen an attribute selector contains an **unterminated quoted value** - e.g., `[a=\"xxxx...` (opening `\"` but no closing `\"`) -” the regex engine attempts to match the quoted-string branch. After that branch fails (no closing quote), the engine backtracks and attempts to match the remaining input against subsequent alternation branches and parent patterns. The structure of the pattern causes **catastrophic backtracking** where the number of backtracking steps grows exponentially with the length of the content between the opening quote and the end of the string.\n\n**Root cause:** The regex pattern does not anchor or guard against the case where a quoted string is never terminated. The overlapping character classes across alternation branches create exponential backtracking when the quoted-string branch fails on long input.\n\n**Key characteristics:**\n- **Input size:** Only 300 bytes are needed to trigger a >3 second hang\n- **Amplification:** Each additional character approximately doubles the backtracking time\n- **No memory impact:** The attack consumes CPU only (regex backtracking is compute-bound)\n\n### Proof of Concept\n\n```python\nimport time\nimport soupsieve as sv\n\nPAYLOAD_LEN = 300\n\n# Control: well-formed selector with terminated quote (completes instantly)\nwell_formed = '[a=\"' + ('x' * PAYLOAD_LEN) + '\"]'\nstart = time.perf_counter()\ntry:\n sv.compile(well_formed)\nexcept Exception:\n pass\ncontrol_time = time.perf_counter() - start\nprint(f\"Well-formed selector ({len(well_formed)} bytes): {control_time:.4f}s\")\n\n# Exploit: unterminated quote triggers catastrophic regex backtracking\nmalformed = '[a=\"' + ('x' * PAYLOAD_LEN)\nstart = time.perf_counter()\ntry:\n sv.compile(malformed) # WARNING: This will hang for >3 seconds\nexcept Exception:\n pass\nexploit_time = time.perf_counter() - start\nprint(f\"Malformed selector ({len(malformed)} bytes): {exploit_time:.4f}s\")\n\nslowdown = exploit_time / max(control_time, 1e-9)\nprint(f\"Slowdown: {slowdown:.0f}x\")\n\n# Expected output:\n# Well-formed selector (306 bytes): ~0.001s\n# Malformed selector (304 bytes): >3.0s (may need to be killed)\n# Slowdown: >3000x\n#\n# NOTE: On some systems the malformed selector may hang indefinitely.\n# Use a timeout mechanism (signal.alarm, threading.Timer) when testing.\n```\n\n**Safe testing variant with timeout:**\n\n```python\nimport signal\nimport soupsieve as sv\n\ndef timeout_handler(signum, frame):\n raise TimeoutError(\"ReDoS confirmed: regex backtracking exceeded timeout\")\n\nPAYLOAD_LEN = 300\nmalformed = '[a=\"' + ('x' * PAYLOAD_LEN)\n\nsignal.signal(signal.SIGALRM, timeout_handler)\nsignal.alarm(3) # 3-second timeout\n\ntry:\n sv.compile(malformed)\n print(\"Selector compiled (not vulnerable)\")\nexcept TimeoutError as e:\n print(f\"VULNERABLE: {e}\")\nexcept Exception as e:\n print(f\"Other error: {e}\")\nfinally:\n signal.alarm(0) # Cancel the alarm\n```\n\n### Impact\n\n**Severity: High**\n\nAn attacker can cause CPU exhaustion on any server-side Python application that compiles user-supplied CSS selectors via soupsieve. The attack is particularly dangerous because:\n\n1. **Tiny payload:** Only 300 bytes are needed - well within typical URL parameter, form field, or API request limits\n2. **No special characters:** The payload consists entirely of printable ASCII characters (`[a=\"xxx...`)\n3. **Exponential scaling:** Each additional byte approximately doubles the backtracking time, making the attack easily tuneable\n4. **Thread blocking:** The regex engine blocks the calling thread with no opportunity for interruption (except via OS signals)\n\n| Parameter | Value |\n|---|---|\n| Input size | 300 bytes |\n| CPU time consumed | >3 seconds (exponential with payload length) |\n| Memory consumed | Negligible (CPU-only attack) |\n| Authentication required | None |\n| User interaction required | None |\n\n**Deployment impact:** In threaded or async web applications, a single malicious request blocks a worker thread for the duration of the backtracking. An attacker can submit multiple concurrent requests to exhaust all available workers, causing complete service denial. The small payload size makes the attack easy to deliver and difficult to detect via request size limits.\n\n**Downstream exposure:** soupsieve is an automatic dependency of `beautifulsoup4`, one of the most widely installed Python packages. Any web application, API, or service that accepts CSS selectors from users is potentially affected.\n\n---\n\n### Credit\n\nThe vulnerability was discovered by a security research team from the University of Sydney, whose focus is detecting open source software vulnerabilities.\nLiyi Zhou: https://lzhou1110.github.io/\nZiyue Wang: https://zyy0530.github.io/\nStrick: https://str1ckl4nd.github.io/\nMaurice: https://maurice.busystar.org/\nChenchen Yu: https://7thparkk.github.io/",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "soupsieve"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "2.8.4"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 2.8.3"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/facelessuser/soupsieve/security/advisories/GHSA-836r-79rf-4m37"
45+
},
46+
{
47+
"type": "PACKAGE",
48+
"url": "https://github.com/facelessuser/soupsieve"
49+
}
50+
],
51+
"database_specific": {
52+
"cwe_ids": [
53+
"CWE-1333",
54+
"CWE-400"
55+
],
56+
"severity": "HIGH",
57+
"github_reviewed": true,
58+
"github_reviewed_at": "2026-07-09T13:37:46Z",
59+
"nvd_published_at": null
60+
}
61+
}

0 commit comments

Comments
 (0)