CVE-2026-40594: Pyload ng PyLoad Has A Session Cookie Vulnerability

MODERATE
Advisory Identifier: GHSA-mp82-fmj6-f22v
CVSS 4.8
CWE-346
Published
Advisory Summary

CVE-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

CVE Identifier CVE-2026-40594
GHSA Identifier GHSA-mp82-fmj6-f22v
Severity MODERATE
CVSS v3.1 Base Score 4.8
CWE Classification CWE-346: Origin Validation Error
Affected Product Pyload ng
Package Ecosystem PyPI
Published Date
Last Updated

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).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity None (I:N)
Availability Low (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.

pyload-ng fixed: 0.5.0b3.dev98
pip install pyload-ng>=0.5.0b3.dev98
Verify Direct and Transitive Dependencies

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

## Summary The `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. ## Details The vulnerable code is in `src/pyload/webui/app/__init__.py:75-84`: ```python # Dynamically set SESSION_COOKIE_SECURE according to the value of X-Forwarded-Proto # TODO: Add trusted proxy check @app.before_request def set_session_cookie_secure(): x_forwarded_proto = flask.request.headers.get("X-Forwarded-Proto", "") is_secure = ( x_forwarded_proto.split(',')[0].strip() == "https" or app.config["PYLOAD_API"].get_config_value("webui", "use_ssl") ) flask.current_app.config['SESSION_COOKIE_SECURE'] = is_secure ``` The root cause has two components: 1. **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. 2. **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. The Cheroot WSGI server is configured with `request_queue_size=512` in `src/pyload/webui/webserver_thread.py:46`, confirming concurrent multi-threaded request processing. No `ProxyFix` or equivalent middleware is configured anywhere in the codebase (confirmed via codebase-wide search). ## PoC **Attack Path 1 — Cookie Security Downgrade (behind TLS-terminating proxy, `use_ssl=False`):** An 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`: ```bash # Attacker floods backend directly, bypassing TLS proxy for i in $(seq 1 200); do curl -s -H 'X-Forwarded-Proto: http' http://pyload-backend:8000/ & done # Meanwhile, a legitimate user behind the TLS proxy receives a session cookie # During the race window, their Set-Cookie header lacks the Secure flag # The cookie is then vulnerable to interception over plain HTTP ``` **Attack Path 2 — Session Denial of Service (default plain HTTP deployment):** ```bash # Attacker causes SESSION_COOKIE_SECURE=True on a plain HTTP server for i in $(seq 1 200); do curl -s -H 'X-Forwarded-Proto: https' http://localhost:8000/ & done # Concurrent legitimate users receive Set-Cookie with Secure flag # Browser refuses to send Secure cookies over HTTP # Users' sessions silently break — they appear logged out ``` The second attack path works against the default configuration (`use_ssl=False`) and requires no special network position. ## Impact - **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. - **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. - **No authentication required:** Both attack paths are fully unauthenticated — the `before_request` handler fires before any auth checks. ## Recommended Fix Replace the global config mutation with per-response cookie handling, and add proxy validation: ```python # Option A: Set Secure flag per-response instead of mutating global config @app.after_request def set_session_cookie_secure(response): # Only trust X-Forwarded-Proto if ProxyFix is configured is_secure = app.config["PYLOAD_API"].get_config_value("webui", "use_ssl") if 'Set-Cookie' in response.headers: # Modify cookie flags per-response, not global config cookies = response.headers.getlist('Set-Cookie') response.headers.remove('Set-Cookie') for cookie in cookies: if is_secure and 'Secure' not in cookie: cookie += '; Secure' response.headers.add('Set-Cookie', cookie) return response # Option B (preferred): Use Werkzeug's ProxyFix with explicit trust from werkzeug.middleware.proxy_fix import ProxyFix # In App.__new__, before returning: if trusted_proxy_count: # from config app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=trusted_proxy_count) # Then set SESSION_COOKIE_SECURE once at startup based on use_ssl config, # and let ProxyFix handle X-Forwarded-Proto transparently ``` At 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.

CVE-2026-40594 Affected Pyload ng Versions

1 component(s)
Fixed Versions:
0.5.0b3.dev98
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?
CVE-2026-40594 is a security vulnerability in Pyload ng involving pyLoad has a Session Cookie Security Downgrade via Untrusted X-Forwarded-Proto Header Spoofing (Global State Race Condition) under CWE-346: Origin Validation Error.
Which Pyload ng versions are affected?
Affected versions include releases prior to 0.5.0b3.dev98 across affected packages: pyload-ng.
What version fixes CVE-2026-40594?
Upgrading to 0.5.0b3.dev98 (or the latest patched release for your respective branch) resolves the vulnerability.
Is pyload-ng affected?
Yes, pyload-ng is affected in releases prior to 0.5.0b3.dev98. Upgrade your dependency to 0.5.0b3.dev98 or later.
What is the CVSS score of CVE-2026-40594?
CVE-2026-40594 has a CVSS v3.1 base score of 4.8 (MODERATE) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:L.
What CWE is associated with CVE-2026-40594?
CVE-2026-40594 is classified under CWE-346: Origin Validation Error (The product does not properly verify that the source of data or communication is valid.).
How can I check PyPI dependencies for CVE-2026-40594?
Run the dependency tree command: 'pip show pyload-ng'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "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"
  }
}