diff --git a/glob/manager_server.py b/glob/manager_server.py index 8474bb9c..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,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..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 @@ -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)