CVE-2026-40594: Pyload ng PyLoad Has A Session Cookie Vulnerability
MODERATECVE-2026-40594 is a MODERATE (CVSS 4.8) pyload has a session cookie vulnerability (CWE-346: Origin Validation Error) affecting Pyload ng libraries in the PyPI ecosystem. The issue involves pyload has a session cookie security downgrade via untrusted x-forwarded-proto header spoofing (global state race condition). Remediation is available by upgrading affected packages to version 0.5.0b3.dev98.
CVE-2026-40594 at a Glance
Security Impact
CVSS: 4.8
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:L).
How to Fix CVE-2026-40594
To remediate this vulnerability, upgrade all affected Pyload ng dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install pyload-ng>=0.5.0b3.dev98
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show pyload-ng
How to Check if You Are Affected
To determine whether your Python environment is affected by CVE-2026-40594, inspect your installed packages using pip:
pip show pyload-ng
Analyze the terminal output to locate any version of Pyload ng in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.5.0b3.dev98), your application is affected.
Note: The pip show pyload-ng command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-346: Origin Validation Error, where the product does not properly verify that the source of data or communication is valid. In Pyload ng, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-40594 Affected Pyload ng Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
pyload-ng
|
PyPI |
< 0.5.0b3.dev98
|
0.5.0b3.dev98
|
References
Frequently Asked Questions
What is CVE-2026-40594?
Which Pyload ng versions are affected?
What version fixes CVE-2026-40594?
Is pyload-ng affected?
What is the CVSS score of CVE-2026-40594?
What CWE is associated with CVE-2026-40594?
How can I check PyPI dependencies for CVE-2026-40594?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-mp82-fmj6-f22v",
"modified" : "2026-06-08T19:47:41Z",
"published" : "2026-04-16T01:20:49Z",
"aliases" : [ "CVE-2026-40594" ],
"summary" : "pyLoad has a Session Cookie Security Downgrade via Untrusted X-Forwarded-Proto Header Spoofing (Global State Race Condition)",
"details" : "## Summary\n\nThe `set_session_cookie_secure` `before_request` handler in `src/pyload/webui/app/__init__.py` reads the `X-Forwarded-Proto` header from any HTTP request without validating that the request originates from a trusted proxy, then mutates the **global** Flask configuration `SESSION_COOKIE_SECURE` on every request. Because pyLoad uses the multi-threaded Cheroot WSGI server (`request_queue_size=512`), this creates a race condition where an attacker's request can influence the `Secure` flag on other users' session cookies — either downgrading cookie security behind a TLS proxy or causing a session denial-of-service on plain HTTP deployments.\n\n## Details\n\nThe vulnerable code is in `src/pyload/webui/app/__init__.py:75-84`:\n\n```python\n# Dynamically set SESSION_COOKIE_SECURE according to the value of X-Forwarded-Proto\n# TODO: Add trusted proxy check\n@app.before_request\ndef set_session_cookie_secure():\n x_forwarded_proto = flask.request.headers.get(\"X-Forwarded-Proto\", \"\")\n is_secure = (\n x_forwarded_proto.split(',')[0].strip() == \"https\" or\n app.config[\"PYLOAD_API\"].get_config_value(\"webui\", \"use_ssl\")\n )\n flask.current_app.config['SESSION_COOKIE_SECURE'] = is_secure\n```\n\nThe root cause has two components:\n\n1. **No origin validation (CWE-346):** The `X-Forwarded-Proto` header is read from any client request. This header is only trustworthy when set by a known reverse proxy. Without `ProxyFix` middleware or a trusted proxy allowlist, any client can spoof it. The code itself acknowledges this with the TODO on line 76.\n\n2. **Global state mutation in a multi-threaded server:** `flask.current_app.config['SESSION_COOKIE_SECURE']` is application-wide shared state. When Thread A (attacker) writes `False` to this config, Thread B (victim) may read `False` when Flask's `save_session()` runs in the after_request phase, producing a `Set-Cookie` response without the `Secure` flag.\n\nThe Cheroot WSGI server is configured with `request_queue_size=512` in `src/pyload/webui/webserver_thread.py:46`, confirming concurrent multi-threaded request processing.\n\nNo `ProxyFix` or equivalent middleware is configured anywhere in the codebase (confirmed via codebase-wide search).\n\n## PoC\n\n**Attack Path 1 — Cookie Security Downgrade (behind TLS-terminating proxy, `use_ssl=False`):**\n\nAn attacker with direct access to the backend (e.g., in a containerized/Kubernetes deployment) sends concurrent requests to keep `SESSION_COOKIE_SECURE` set to `False`:\n\n```bash\n# Attacker floods backend directly, bypassing TLS proxy\nfor i in $(seq 1 200); do\n curl -s -H 'X-Forwarded-Proto: http' http://pyload-backend:8000/ &\ndone\n\n# Meanwhile, a legitimate user behind the TLS proxy receives a session cookie\n# During the race window, their Set-Cookie header lacks the Secure flag\n# The cookie is then vulnerable to interception over plain HTTP\n```\n\n**Attack Path 2 — Session Denial of Service (default plain HTTP deployment):**\n\n```bash\n# Attacker causes SESSION_COOKIE_SECURE=True on a plain HTTP server\nfor i in $(seq 1 200); do\n curl -s -H 'X-Forwarded-Proto: https' http://localhost:8000/ &\ndone\n\n# Concurrent legitimate users receive Set-Cookie with Secure flag\n# Browser refuses to send Secure cookies over HTTP\n# Users' sessions silently break — they appear logged out\n```\n\nThe second attack path works against the default configuration (`use_ssl=False`) and requires no special network position.\n\n## Impact\n\n- **Session cookie exposure (Attack Path 1):** When deployed behind a TLS-terminating proxy, an attacker can cause session cookies to be issued without the `Secure` flag. If the victim's browser subsequently makes an HTTP request (e.g., via a mixed-content link or downgrade attack), the session cookie is transmitted in cleartext, enabling session hijacking.\n\n- **Session denial of service (Attack Path 2):** On default plain HTTP deployments, an attacker can continuously set `SESSION_COOKIE_SECURE=True`, causing browsers to refuse sending session cookies back to the server. This silently breaks all concurrent users' sessions with no user-visible error message, only a redirect to login.\n\n- **No authentication required:** Both attack paths are fully unauthenticated — the `before_request` handler fires before any auth checks.\n\n## Recommended Fix\n\nReplace the global config mutation with per-response cookie handling, and add proxy validation:\n\n```python\n# Option A: Set Secure flag per-response instead of mutating global config\n@app.after_request\ndef set_session_cookie_secure(response):\n # Only trust X-Forwarded-Proto if ProxyFix is configured\n is_secure = app.config[\"PYLOAD_API\"].get_config_value(\"webui\", \"use_ssl\")\n if 'Set-Cookie' in response.headers:\n # Modify cookie flags per-response, not global config\n cookies = response.headers.getlist('Set-Cookie')\n response.headers.remove('Set-Cookie')\n for cookie in cookies:\n if is_secure and 'Secure' not in cookie:\n cookie += '; Secure'\n response.headers.add('Set-Cookie', cookie)\n return response\n\n# Option B (preferred): Use Werkzeug's ProxyFix with explicit trust\nfrom werkzeug.middleware.proxy_fix import ProxyFix\n\n# In App.__new__, before returning:\nif trusted_proxy_count: # from config\n app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=trusted_proxy_count)\n# Then set SESSION_COOKIE_SECURE once at startup based on use_ssl config,\n# and let ProxyFix handle X-Forwarded-Proto transparently\n```\n\nAt minimum, remove the `before_request` handler entirely and set `SESSION_COOKIE_SECURE` once at startup (line 130 already does this in `_configure_session`). The dynamic per-request adjustment is the root cause of both the spoofing and the race condition.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:L"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "pyload-ng"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.5.0b3.dev98"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 0.5.0b3.dev97"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/pyload/pyload/security/advisories/GHSA-mp82-fmj6-f22v"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-40594"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/pyload/pyload"
}, {
"type" : "WEB",
"url" : "https://github.com/pypa/advisory-database/tree/main/vulns/pyload-ng/PYSEC-2026-125.yaml"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-346" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-04-16T01:20:49Z",
"nvd_published_at" : "2026-04-21T18:16:51Z"
}
}