Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions glob/manager_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <form method=POST>
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.

Expand All @@ -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
Expand All @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion tests/test_csrf_content_type_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

# 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

Expand Down Expand Up @@ -116,6 +116,12 @@ def test_application_json_allowed(self):
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)