Skip to content

Commit bf6ec78

Browse files
1 parent a6afe64 commit bf6ec78

File tree

3 files changed

+171
-4
lines changed

3 files changed

+171
-4
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-mp82-fmj6-f22v",
4+
"modified": "2026-04-16T01:21:32Z",
5+
"published": "2026-04-16T01:20:49Z",
6+
"aliases": [
7+
"CVE-2026-40594"
8+
],
9+
"summary": "pyLoad has a Session Cookie Security Downgrade via Untrusted X-Forwarded-Proto Header Spoofing (Global State Race Condition)",
10+
"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.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:L"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "pyload-ng"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.5.0b3.dev98"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 0.5.0b3.dev97"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/pyload/pyload/security/advisories/GHSA-mp82-fmj6-f22v"
45+
},
46+
{
47+
"type": "PACKAGE",
48+
"url": "https://github.com/pyload/pyload"
49+
}
50+
],
51+
"database_specific": {
52+
"cwe_ids": [
53+
"CWE-346"
54+
],
55+
"severity": "MODERATE",
56+
"github_reviewed": true,
57+
"github_reviewed_at": "2026-04-16T01:20:49Z",
58+
"nvd_published_at": null
59+
}
60+
}

advisories/unreviewed/2026/04/GHSA-pfx2-9x9m-7ghx/GHSA-pfx2-9x9m-7ghx.json renamed to advisories/github-reviewed/2026/04/GHSA-pfx2-9x9m-7ghx/GHSA-pfx2-9x9m-7ghx.json

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-pfx2-9x9m-7ghx",
4-
"modified": "2026-04-14T21:31:48Z",
4+
"modified": "2026-04-16T01:20:22Z",
55
"published": "2026-04-14T21:31:48Z",
66
"aliases": [
77
"CVE-2026-40683"
88
],
9+
"summary": "OpenStack Keystone: LDAP identity backend does not convert enabled attribute to boolean",
910
"details": "In OpenStack Keystone before 28.0.1, the LDAP identity backend does not convert the user enabled attribute to a boolean when the user_enabled_invert configuration option is False (the default). The _ldap_res_to_model method in the UserApi class only performed string-to-boolean conversion when user_enabled_invert was True. When False, the raw string value from LDAP (e.g., \"FALSE\") was used directly. Since non-empty strings are truthy in Python, users marked as disabled in LDAP were treated as enabled by Keystone, allowing them to authenticate and perform actions. All deployments using the LDAP identity backend without user_enabled_invert=True or user_enabled_emulation are affected.",
1011
"severity": [
1112
{
1213
"type": "CVSS_V3",
1314
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:H"
1415
}
1516
],
16-
"affected": [],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "keystone"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "28.0.1"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
1738
"references": [
1839
{
1940
"type": "ADVISORY",
@@ -27,6 +48,10 @@
2748
"type": "WEB",
2849
"url": "https://bugs.launchpad.net/keystone/+bug/2141713"
2950
},
51+
{
52+
"type": "PACKAGE",
53+
"url": "https://github.com/openstack/keystone"
54+
},
3055
{
3156
"type": "WEB",
3257
"url": "https://review.opendev.org/958205"
@@ -41,8 +66,8 @@
4166
"CWE-843"
4267
],
4368
"severity": "HIGH",
44-
"github_reviewed": false,
45-
"github_reviewed_at": null,
69+
"github_reviewed": true,
70+
"github_reviewed_at": "2026-04-16T01:20:22Z",
4671
"nvd_published_at": "2026-04-14T20:16:48Z"
4772
}
4873
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-rr7j-v2q5-chgv",
4+
"modified": "2026-04-16T01:20:37Z",
5+
"published": "2026-04-16T01:20:37Z",
6+
"aliases": [],
7+
"summary": "LangSmith SDK: Streaming token events bypass output redaction",
8+
"details": "## Summary\n\nThe LangSmith SDK's output redaction controls (hideOutputs in JS, hide_outputs in Python) do not apply to streaming token events. When an LLM run produces streaming output, each chunk is recorded as a new_token event containing the raw token value. These events bypass the redaction pipeline entirely — prepareRunCreateOrUpdateInputs (JS) and _hide_run_outputs (Python) only process the inputs and outputs fields on a run, never the events array. As a result, applications relying on output redaction to prevent sensitive LLM output from being stored in LangSmith will still leak the full streamed content via run events.\n\n## Details\n\n**Both JS and Python SDKs are affected.** The same pattern exists in both:\n\n- **JS SDK**: `traceable.ts:997-1003` and `traceable.ts:1044-1050`\n- **Python SDK**: `run_helpers.py:1924` and `run_helpers.py:1996`\n\nIn both SDKs, `new_token` events with raw `kwargs.token` values are added during streaming, and the redaction pipeline (`hideOutputs` in JS, `hide_outputs` in Python) only processes `inputs`/`outputs` — never `events`.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "langsmith"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "0.5.19"
30+
}
31+
]
32+
}
33+
],
34+
"database_specific": {
35+
"last_known_affected_version_range": "<= 0.5.18"
36+
}
37+
},
38+
{
39+
"package": {
40+
"ecosystem": "PyPI",
41+
"name": "langsmith"
42+
},
43+
"ranges": [
44+
{
45+
"type": "ECOSYSTEM",
46+
"events": [
47+
{
48+
"introduced": "0"
49+
},
50+
{
51+
"fixed": "0.7.31"
52+
}
53+
]
54+
}
55+
],
56+
"database_specific": {
57+
"last_known_affected_version_range": "<= 0.7.30"
58+
}
59+
}
60+
],
61+
"references": [
62+
{
63+
"type": "WEB",
64+
"url": "https://github.com/langchain-ai/langsmith-sdk/security/advisories/GHSA-rr7j-v2q5-chgv"
65+
},
66+
{
67+
"type": "PACKAGE",
68+
"url": "https://github.com/langchain-ai/langsmith-sdk"
69+
}
70+
],
71+
"database_specific": {
72+
"cwe_ids": [
73+
"CWE-200",
74+
"CWE-359",
75+
"CWE-532"
76+
],
77+
"severity": "MODERATE",
78+
"github_reviewed": true,
79+
"github_reviewed_at": "2026-04-16T01:20:37Z",
80+
"nvd_published_at": null
81+
}
82+
}

0 commit comments

Comments
 (0)