From 481c4b6772e74ef10fbac91a594e891befadd6f6 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Sat, 2 May 2026 15:17:34 -0700 Subject: [PATCH 1/2] fix: skip Content-Type gate when request is None (#2843) Legacy-UI batch flows invoke route handlers internally with request=None, which previously raised AttributeError on request.content_type. Signed-off-by: SAY-5 --- glob/manager_server.py | 6 +++++- tests/test_csrf_content_type_helper.py | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/glob/manager_server.py b/glob/manager_server.py index 8474bb9c..2b97cb23 100644 --- a/glob/manager_server.py +++ b/glob/manager_server.py @@ -353,8 +353,12 @@ def _reject_simple_form_content_type(request): web.Response(status=400) when the request has a simple-form Content-Type that must be rejected. None when the request is allowed to proceed (no Content-Type, application/json, or any non-simple - Content-Type). + Content-Type, or no request object — internal caller). """ + # Internal callers (e.g. legacy-UI batch flows) invoke route handlers + # directly with request=None; there is no Content-Type to gate. + if request is None: + return None if request.content_type in _SIMPLE_FORM_CONTENT_TYPES: return web.Response( status=400, diff --git a/tests/test_csrf_content_type_helper.py b/tests/test_csrf_content_type_helper.py index e1467475..f2e67482 100644 --- a/tests/test_csrf_content_type_helper.py +++ b/tests/test_csrf_content_type_helper.py @@ -116,6 +116,12 @@ class ContentTypeRejectionTest(unittest.TestCase): r = self._post({"Content-Type": "application/json"}) self.assertEqual(r.status, 200) + def test_none_request_allowed(self): + # Internal callers (e.g. legacy-UI batch flows) invoke route handlers + # directly with request=None; helper must not raise AttributeError. + # Regression test for issue #2843. + self.assertIsNone(_reject_simple_form_content_type(None)) + if __name__ == "__main__": unittest.main(verbosity=2) From 46566848bb664c5c5dab15c06a58b7befd008ba2 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Mon, 11 May 2026 13:01:13 -0700 Subject: [PATCH 2/2] chore: remove em-dashes from comments --- glob/manager_server.py | 8 ++++---- tests/test_csrf_content_type_helper.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/glob/manager_server.py b/glob/manager_server.py index 2b97cb23..ae83270b 100644 --- a/glob/manager_server.py +++ b/glob/manager_server.py @@ -333,7 +333,7 @@ def _reject_simple_form_content_type(request): Applied ONLY to POST handlers that do not consume a request body (e.g., /snapshot/save, /manager/queue/{reset,start,update_comfyui}, /manager/reboot). These are vulnerable to cross-origin
- attacks because the handler accepts the request without parsing any body — + attacks because the handler accepts the request without parsing any body, the attacker needs no ability to forge a valid payload, only to point a hidden form at the URL. @@ -342,8 +342,8 @@ def _reject_simple_form_content_type(request): body because the browser refuses to send ``application/json`` without a CORS preflight, which this server does not answer. - DO NOT add this gate to body-reading handlers — redundant and UX-breaking. - DO NOT remove this gate from no-body handlers — this is the bypass vector. + DO NOT add this gate to body-reading handlers, redundant and UX-breaking. + DO NOT remove this gate from no-body handlers, this is the bypass vector. aiohttp's ``request.content_type`` normalizes the header (lower-cases, strips parameters), so ``multipart/form-data; boundary=----X`` is compared @@ -353,7 +353,7 @@ def _reject_simple_form_content_type(request): web.Response(status=400) when the request has a simple-form Content-Type that must be rejected. None when the request is allowed to proceed (no Content-Type, application/json, or any non-simple - Content-Type, or no request object — internal caller). + Content-Type, or no request object, internal caller). """ # Internal callers (e.g. legacy-UI batch flows) invoke route handlers # directly with request=None; there is no Content-Type to gate. diff --git a/tests/test_csrf_content_type_helper.py b/tests/test_csrf_content_type_helper.py index f2e67482..0a167a67 100644 --- a/tests/test_csrf_content_type_helper.py +++ b/tests/test_csrf_content_type_helper.py @@ -23,7 +23,7 @@ from aiohttp.test_utils import TestClient, TestServer # Parse the helper from manager_server.py without importing it, to avoid # pulling in the full ComfyUI/PromptServer stack. Note: we intentionally do -# NOT add the `glob/` directory to sys.path — the dir name would shadow +# NOT add the `glob/` directory to sys.path, the dir name would shadow # Python's stdlib `glob` module and break pytest collection. REPO_ROOT = Path(__file__).resolve().parent.parent