Skip to content

fix(security): harden CSRF with Content-Type gate and OpenAPI sync#2819

Merged
ltdrdata merged 1 commit intomainfrom
fix/csrf-post-conversion-v3
Apr 21, 2026
Merged

fix(security): harden CSRF with Content-Type gate and OpenAPI sync#2819
ltdrdata merged 1 commit intomainfrom
fix/csrf-post-conversion-v3

Conversation

@ltdrdata
Copy link
Copy Markdown
Member

@ltdrdata ltdrdata commented Apr 21, 2026

Summary

  • Convert 15 state-changing endpoints: 10 pure GET→POST + 5 dual GET(read)/POST(write) splits. Closes the <img>-triggered CSRF vector.
  • Content-Type gate on 5 no-body POST handlers rejects the three CORS simple-request types (application/x-www-form-urlencoded, multipart/form-data, text/plain) to block <form method=POST> CSRF that bypasses method-only gating. Bare POST and application/json pass through unchanged — no first-party JS changes needed for the gate.
  • Enable-stuck fix: /manager/queue/install sync-enable fast-path now emits the in_progress + done event pair. Previously only queue/start's empty-worker done fired, leaving item.restart unset and the Enable button visible after successful enable.
  • JS completion path hardening in js/custom-nodes-manager.js: await onQueueCompleted with try/catch (surfaces silent turbogrid stale-item throws), replace {}.length == 0 no-op empty guard, set install_context before queue/install to avoid sync-completion race, wrap classList.remove/grid.updateCell in try/catch.
  • OpenAPI sync: openapi.yaml updated to match converted routes (method→post, query params→requestBody JSON schema, sibling post on 5 split endpoints).
  • Version bump: 3.39.3 → 3.40 (pyproject.toml, manager_core.py).
  • Tests: tests/test_csrf_content_type_helper.py covers 5 Content-Type cases via aiohttp TestClient.

Scope

File Δ
glob/manager_server.py +245/-88 (helper + 5 wires + sync-enable event emit + 15 route conversions)
js/custom-nodes-manager.js +80/-22 (completion path hardening + install_context race fix)
js/{cm-api,comfyui-manager,common,model-manager,snapshot}.js 31 fetchApi call sites updated
openapi.yaml +198/-29 (15 endpoints resync)
tests/test_csrf_content_type_helper.py +121 (new, 5 cases)
pyproject.toml, glob/manager_core.py version 3.40

Total: 11 files, +567 / -144.

Test plan

  • python tests/test_csrf_content_type_helper.py — Ran 5 tests in 0.003s, OK (form-urlencoded→400, multipart→400, text/plain→400, no Content-Type→200, application/json→200)
  • python -c "import yaml; yaml.safe_load(open('openapi.yaml'))" — passes
  • Server/spec cross-check: every @routes.post path in glob/manager_server.py has matching post: in openapi.yaml (27/27)
  • Client/server contract verification: 31 JS fetchApi call sites verified against converted server handlers (method/URL/body match)
  • Adversarial bypass probe: aiohttp request.content_type normalization across 16 CT variants (case, whitespace, charset=, boundary=) — no bypass identified
  • Playwright E2E on real ComfyUI instance: disable flow — in_progress + done events received, loading cleared, restart indicator shown, toast displayed
  • Playwright E2E on real ComfyUI instance: enable flow (sync-enable fast-path) — in_progress + done events both received, item.restart correctly set, action cell re-renders to "Restart Required"
  • ruff check — All checks passed

Related

Reported-by: XlabAI Team of Tencent Xuanwu Lab
CVSS: 8.1 (AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H)

Defense-in-depth over GET→POST alone: reject the three CORS-safelisted
simple-form Content-Types (x-www-form-urlencoded, multipart/form-data,
text/plain) on 5 no-body POST handlers (snapshot/save,
manager/queue/{reset,start,update_comfyui}, manager/reboot) to block
<form method=POST> CSRF that bypasses method-only gating. Convert 10 pure
state-changing endpoints (fetch_updates, queue/{update_all,reset,start,
update_comfyui}, snapshot/{remove,restore,save}, comfyui_switch_version,
reboot) from GET to POST and split 5 config endpoints
(db_mode/preview_method/channel_url_list/policy/{component,update}) into
GET(read) + POST(write, JSON body). Emit the in_progress + done event pair
from the /manager/queue/install sync-enable fast-path so client UI
finalizes (previously only queue/start's empty worker done fired, leaving
item.restart unset and the Enable button visible after a successful enable).
Harden js/custom-nodes-manager.js completion path: await onQueueCompleted
with try/catch (surfaces silent turbogrid stale-item throws), replace the
{}.length == 0 no-op empty guard, set install_context before queue/install
to avoid a sync-completion race, wrap classList/updateCell in try/catch.
Resynchronize openapi.yaml with the converted routes (method → post, query
params → requestBody JSON schema, sibling post on 5 split endpoints).
Update 31 JS fetchApi call sites across 7 files; add
tests/test_csrf_content_type_helper.py covering 5 Content-Type cases via
aiohttp TestClient.

Reported-by: XlabAI Team of Tencent Xuanwu Lab
CVSS: 8.1 (AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H)
@ltdrdata ltdrdata merged commit 491f847 into main Apr 21, 2026
4 checks passed
@ltdrdata ltdrdata deleted the fix/csrf-post-conversion-v3 branch April 21, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant