mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-05-14 02:47:24 +08:00
Merge branch 'Comfy-Org:main' into main
This commit is contained in:
commit
5162e1d601
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
13659
github-stats.json
13659
github-stats.json
File diff suppressed because it is too large
Load Diff
@ -44,7 +44,7 @@ import manager_migration
|
|||||||
from node_package import InstalledNodePackage
|
from node_package import InstalledNodePackage
|
||||||
|
|
||||||
|
|
||||||
version_code = [3, 39, 2]
|
version_code = [3, 40]
|
||||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -312,6 +312,57 @@ def security_403_response():
|
|||||||
return web.json_response({"error": "security_level"}, status=403)
|
return web.json_response({"error": "security_level"}, status=403)
|
||||||
|
|
||||||
|
|
||||||
|
# CORS "simple request" Content-Type set per Fetch spec §3.2.3. Browsers send
|
||||||
|
# <form method=POST> submissions with one of these three MIME types and do NOT
|
||||||
|
# trigger a CORS preflight, so a malicious cross-origin page can silently POST
|
||||||
|
# into state-changing endpoints if we only gate on HTTP method. Blocking these
|
||||||
|
# three Content-Types on no-body mutation endpoints forces any non-same-origin
|
||||||
|
# POST to use a non-simple Content-Type (e.g. application/json), which triggers
|
||||||
|
# a preflight that this server rejects by not advertising an Access-Control-
|
||||||
|
# Allow-Origin response.
|
||||||
|
_SIMPLE_FORM_CONTENT_TYPES = frozenset({
|
||||||
|
'application/x-www-form-urlencoded',
|
||||||
|
'multipart/form-data',
|
||||||
|
'text/plain',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def _reject_simple_form_content_type(request):
|
||||||
|
"""Reject Content-Types that enable preflight-less <form method=POST> CSRF.
|
||||||
|
|
||||||
|
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 —
|
||||||
|
the attacker needs no ability to forge a valid payload, only to point a
|
||||||
|
hidden form at the URL.
|
||||||
|
|
||||||
|
Handlers that already read a body via ``await request.json()`` are NOT
|
||||||
|
gated here: a cross-origin <form method=POST> cannot forge a valid JSON
|
||||||
|
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.
|
||||||
|
|
||||||
|
aiohttp's ``request.content_type`` normalizes the header (lower-cases,
|
||||||
|
strips parameters), so ``multipart/form-data; boundary=----X`` is compared
|
||||||
|
as ``multipart/form-data``.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
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).
|
||||||
|
"""
|
||||||
|
if request.content_type in _SIMPLE_FORM_CONTENT_TYPES:
|
||||||
|
return web.Response(
|
||||||
|
status=400,
|
||||||
|
text='Invalid Content-Type for this endpoint. Use application/json or omit body.',
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_model_dir(data, show_log=False):
|
def get_model_dir(data, show_log=False):
|
||||||
if 'download_model_base' in folder_paths.folder_names_and_paths:
|
if 'download_model_base' in folder_paths.folder_names_and_paths:
|
||||||
models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0]
|
models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0]
|
||||||
@ -737,16 +788,19 @@ async def fetch_customnode_mappings(request):
|
|||||||
return web.json_response(json_obj, content_type='application/json')
|
return web.json_response(json_obj, content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/customnode/fetch_updates")
|
@routes.post("/customnode/fetch_updates")
|
||||||
async def fetch_updates(request):
|
async def fetch_updates(request):
|
||||||
try:
|
try:
|
||||||
if request.rel_url.query["mode"] == "local":
|
json_data = await request.json()
|
||||||
|
mode = json_data.get("mode", "default")
|
||||||
|
|
||||||
|
if mode == "local":
|
||||||
channel = 'local'
|
channel = 'local'
|
||||||
else:
|
else:
|
||||||
channel = core.get_config()['channel_url']
|
channel = core.get_config()['channel_url']
|
||||||
|
|
||||||
await core.unified_manager.reload(request.rel_url.query["mode"])
|
await core.unified_manager.reload(mode)
|
||||||
await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])
|
await core.unified_manager.get_custom_nodes(channel, mode)
|
||||||
|
|
||||||
res = core.unified_manager.fetch_or_pull_git_repo(is_pull=False)
|
res = core.unified_manager.fetch_or_pull_git_repo(is_pull=False)
|
||||||
|
|
||||||
@ -764,7 +818,7 @@ async def fetch_updates(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/queue/update_all")
|
@routes.post("/manager/queue/update_all")
|
||||||
async def update_all(request):
|
async def update_all(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
@ -774,16 +828,19 @@ async def update_all(request):
|
|||||||
is_processing = task_worker_thread is not None and task_worker_thread.is_alive()
|
is_processing = task_worker_thread is not None and task_worker_thread.is_alive()
|
||||||
if is_processing:
|
if is_processing:
|
||||||
return web.Response(status=401)
|
return web.Response(status=401)
|
||||||
|
|
||||||
await core.save_snapshot_with_postfix('autosave')
|
await core.save_snapshot_with_postfix('autosave')
|
||||||
|
|
||||||
if request.rel_url.query["mode"] == "local":
|
json_data = await request.json()
|
||||||
|
mode = json_data.get("mode", "default")
|
||||||
|
|
||||||
|
if mode == "local":
|
||||||
channel = 'local'
|
channel = 'local'
|
||||||
else:
|
else:
|
||||||
channel = core.get_config()['channel_url']
|
channel = core.get_config()['channel_url']
|
||||||
|
|
||||||
await core.unified_manager.reload(request.rel_url.query["mode"])
|
await core.unified_manager.reload(mode)
|
||||||
await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])
|
await core.unified_manager.get_custom_nodes(channel, mode)
|
||||||
|
|
||||||
for k, v in core.unified_manager.active_nodes.items():
|
for k, v in core.unified_manager.active_nodes.items():
|
||||||
if k == 'comfyui-manager':
|
if k == 'comfyui-manager':
|
||||||
@ -1006,14 +1063,15 @@ def get_safe_snapshot_path(target):
|
|||||||
return os.path.join(core.manager_snapshot_path, f"{target}.json")
|
return os.path.join(core.manager_snapshot_path, f"{target}.json")
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/snapshot/remove")
|
@routes.post("/snapshot/remove")
|
||||||
async def remove_snapshot(request):
|
async def remove_snapshot(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
return security_403_response()
|
return security_403_response()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
target = request.rel_url.query["target"]
|
json_data = await request.json()
|
||||||
|
target = json_data["target"]
|
||||||
path = get_safe_snapshot_path(target)
|
path = get_safe_snapshot_path(target)
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
@ -1028,14 +1086,15 @@ async def remove_snapshot(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/snapshot/restore")
|
@routes.post("/snapshot/restore")
|
||||||
async def restore_snapshot(request):
|
async def restore_snapshot(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
return security_403_response()
|
return security_403_response()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
target = request.rel_url.query["target"]
|
json_data = await request.json()
|
||||||
|
target = json_data["target"]
|
||||||
path = get_safe_snapshot_path(target)
|
path = get_safe_snapshot_path(target)
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
@ -1066,8 +1125,11 @@ async def get_current_snapshot_api(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/snapshot/save")
|
@routes.post("/snapshot/save")
|
||||||
async def save_snapshot(request):
|
async def save_snapshot(request):
|
||||||
|
resp = _reject_simple_form_content_type(request)
|
||||||
|
if resp is not None:
|
||||||
|
return resp
|
||||||
try:
|
try:
|
||||||
await core.save_snapshot_with_postfix('snapshot')
|
await core.save_snapshot_with_postfix('snapshot')
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
@ -1228,8 +1290,11 @@ async def reinstall_custom_node(request):
|
|||||||
await install_custom_node(request)
|
await install_custom_node(request)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/queue/reset")
|
@routes.post("/manager/queue/reset")
|
||||||
async def reset_queue(request):
|
async def reset_queue(request):
|
||||||
|
resp = _reject_simple_form_content_type(request)
|
||||||
|
if resp is not None:
|
||||||
|
return resp
|
||||||
global task_queue
|
global task_queue
|
||||||
task_queue = queue.Queue()
|
task_queue = queue.Queue()
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
@ -1270,6 +1335,26 @@ async def install_custom_node(request):
|
|||||||
if skip_post_install:
|
if skip_post_install:
|
||||||
if cnr_id in core.unified_manager.nightly_inactive_nodes or cnr_id in core.unified_manager.cnr_inactive_nodes:
|
if cnr_id in core.unified_manager.nightly_inactive_nodes or cnr_id in core.unified_manager.cnr_inactive_nodes:
|
||||||
core.unified_manager.unified_enable(cnr_id)
|
core.unified_manager.unified_enable(cnr_id)
|
||||||
|
# Mirror the pair of events (in_progress then done) that the async
|
||||||
|
# task_worker normally emits for queued operations. The in_progress
|
||||||
|
# event is what sets item.restart=true on the client row so the
|
||||||
|
# action cell re-renders as "Restart Required"; without it, the
|
||||||
|
# "Enable" button remains visible after successful enable. The done
|
||||||
|
# event drives the completion UI (toast, restart indicator, button
|
||||||
|
# loading clear).
|
||||||
|
ui_id = json_data.get('ui_id', cnr_id)
|
||||||
|
PromptServer.instance.send_sync(
|
||||||
|
"cm-queue-status",
|
||||||
|
{'status': 'in_progress',
|
||||||
|
'target': ui_id,
|
||||||
|
'ui_target': 'nodepack_manager',
|
||||||
|
'total_count': 1, 'done_count': 0})
|
||||||
|
PromptServer.instance.send_sync(
|
||||||
|
"cm-queue-status",
|
||||||
|
{'status': 'done',
|
||||||
|
'nodepack_result': {ui_id: 'success'},
|
||||||
|
'model_result': {},
|
||||||
|
'total_count': 1, 'done_count': 1})
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
elif selected_version is None:
|
elif selected_version is None:
|
||||||
selected_version = 'latest'
|
selected_version = 'latest'
|
||||||
@ -1311,8 +1396,11 @@ async def install_custom_node(request):
|
|||||||
|
|
||||||
task_worker_thread:threading.Thread = None
|
task_worker_thread:threading.Thread = None
|
||||||
|
|
||||||
@routes.get("/manager/queue/start")
|
@routes.post("/manager/queue/start")
|
||||||
async def queue_start(request):
|
async def queue_start(request):
|
||||||
|
resp = _reject_simple_form_content_type(request)
|
||||||
|
if resp is not None:
|
||||||
|
return resp
|
||||||
global nodepack_result
|
global nodepack_result
|
||||||
global model_result
|
global model_result
|
||||||
global task_worker_thread
|
global task_worker_thread
|
||||||
@ -1427,8 +1515,11 @@ async def update_custom_node(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/queue/update_comfyui")
|
@routes.post("/manager/queue/update_comfyui")
|
||||||
async def update_comfyui(request):
|
async def update_comfyui(request):
|
||||||
|
resp = _reject_simple_form_content_type(request)
|
||||||
|
if resp is not None:
|
||||||
|
return resp
|
||||||
is_stable = core.get_config()['update_policy'] != 'nightly-comfyui'
|
is_stable = core.get_config()['update_policy'] != 'nightly-comfyui'
|
||||||
task_queue.put(("update-comfyui", ('comfyui', is_stable)))
|
task_queue.put(("update-comfyui", ('comfyui', is_stable)))
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
@ -1445,11 +1536,12 @@ async def comfyui_versions(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/comfyui_manager/comfyui_switch_version")
|
@routes.post("/comfyui_manager/comfyui_switch_version")
|
||||||
async def comfyui_switch_version(request):
|
async def comfyui_switch_version(request):
|
||||||
try:
|
try:
|
||||||
if "ver" in request.rel_url.query:
|
json_data = await request.json()
|
||||||
core.switch_comfyui(request.rel_url.query['ver'])
|
if "ver" in json_data:
|
||||||
|
core.switch_comfyui(json_data['ver'])
|
||||||
|
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -1526,83 +1618,87 @@ async def install_model(request):
|
|||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/preview_method")
|
@routes.get("/manager/preview_method")
|
||||||
async def preview_method(request):
|
async def get_preview_method(request):
|
||||||
# Setting change request
|
if COMFYUI_HAS_PER_QUEUE_PREVIEW:
|
||||||
if "value" in request.rel_url.query:
|
return web.Response(text="DISABLED", status=200)
|
||||||
# Reject setting change if per-queue preview feature is available
|
return web.Response(text=core.manager_funcs.get_current_preview_method(), status=200)
|
||||||
if COMFYUI_HAS_PER_QUEUE_PREVIEW:
|
|
||||||
return web.Response(text="DISABLED", status=403)
|
|
||||||
|
|
||||||
# Process normally if not available
|
|
||||||
set_preview_method(request.rel_url.query['value'])
|
|
||||||
core.write_config()
|
|
||||||
return web.Response(status=200)
|
|
||||||
|
|
||||||
# Status query request
|
@routes.post("/manager/preview_method")
|
||||||
else:
|
async def set_preview_method_handler(request):
|
||||||
# Return DISABLED if per-queue preview feature is available
|
if COMFYUI_HAS_PER_QUEUE_PREVIEW:
|
||||||
if COMFYUI_HAS_PER_QUEUE_PREVIEW:
|
return web.Response(text="DISABLED", status=403)
|
||||||
return web.Response(text="DISABLED", status=200)
|
|
||||||
|
|
||||||
# Return current value if not available
|
json_data = await request.json()
|
||||||
return web.Response(text=core.manager_funcs.get_current_preview_method(), status=200)
|
set_preview_method(json_data['value'])
|
||||||
|
core.write_config()
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/db_mode")
|
@routes.get("/manager/db_mode")
|
||||||
async def db_mode(request):
|
async def get_db_mode(request):
|
||||||
if "value" in request.rel_url.query:
|
return web.Response(text=core.get_config()['db_mode'], status=200)
|
||||||
set_db_mode(request.rel_url.query['value'])
|
|
||||||
core.write_config()
|
|
||||||
else:
|
|
||||||
return web.Response(text=core.get_config()['db_mode'], status=200)
|
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/manager/db_mode")
|
||||||
|
async def set_db_mode_handler(request):
|
||||||
|
json_data = await request.json()
|
||||||
|
set_db_mode(json_data['value'])
|
||||||
|
core.write_config()
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/policy/component")
|
@routes.get("/manager/policy/component")
|
||||||
async def component_policy(request):
|
async def get_component_policy(request):
|
||||||
if "value" in request.rel_url.query:
|
return web.Response(text=core.get_config()['component_policy'], status=200)
|
||||||
set_component_policy(request.rel_url.query['value'])
|
|
||||||
core.write_config()
|
|
||||||
else:
|
|
||||||
return web.Response(text=core.get_config()['component_policy'], status=200)
|
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/manager/policy/component")
|
||||||
|
async def set_component_policy_handler(request):
|
||||||
|
json_data = await request.json()
|
||||||
|
set_component_policy(json_data['value'])
|
||||||
|
core.write_config()
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/policy/update")
|
@routes.get("/manager/policy/update")
|
||||||
async def update_policy(request):
|
async def get_update_policy(request):
|
||||||
if "value" in request.rel_url.query:
|
return web.Response(text=core.get_config()['update_policy'], status=200)
|
||||||
set_update_policy(request.rel_url.query['value'])
|
|
||||||
core.write_config()
|
|
||||||
else:
|
|
||||||
return web.Response(text=core.get_config()['update_policy'], status=200)
|
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/manager/policy/update")
|
||||||
|
async def set_update_policy_handler(request):
|
||||||
|
json_data = await request.json()
|
||||||
|
set_update_policy(json_data['value'])
|
||||||
|
core.write_config()
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/channel_url_list")
|
@routes.get("/manager/channel_url_list")
|
||||||
async def channel_url_list(request):
|
async def get_channel_url_list(request):
|
||||||
channels = core.get_channel_dict()
|
channels = core.get_channel_dict()
|
||||||
if "value" in request.rel_url.query:
|
selected = 'custom'
|
||||||
channel_url = channels.get(request.rel_url.query['value'])
|
selected_url = core.get_config()['channel_url']
|
||||||
if channel_url is not None:
|
|
||||||
core.get_config()['channel_url'] = channel_url
|
|
||||||
core.write_config()
|
|
||||||
else:
|
|
||||||
selected = 'custom'
|
|
||||||
selected_url = core.get_config()['channel_url']
|
|
||||||
|
|
||||||
for name, url in channels.items():
|
for name, url in channels.items():
|
||||||
if url == selected_url:
|
if url == selected_url:
|
||||||
selected = name
|
selected = name
|
||||||
break
|
break
|
||||||
|
|
||||||
res = {'selected': selected,
|
res = {'selected': selected,
|
||||||
'list': core.get_channel_list()}
|
'list': core.get_channel_list()}
|
||||||
return web.json_response(res, status=200)
|
return web.json_response(res, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/manager/channel_url_list")
|
||||||
|
async def set_channel_url_list(request):
|
||||||
|
json_data = await request.json()
|
||||||
|
channels = core.get_channel_dict()
|
||||||
|
channel_url = channels.get(json_data['value'])
|
||||||
|
if channel_url is not None:
|
||||||
|
core.get_config()['channel_url'] = channel_url
|
||||||
|
core.write_config()
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@ -1700,8 +1796,11 @@ async def get_startup_alerts(request):
|
|||||||
return web.json_response(alerts)
|
return web.json_response(alerts)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/reboot")
|
@routes.post("/manager/reboot")
|
||||||
def restart(self):
|
def restart(self):
|
||||||
|
resp = _reject_simple_form_content_type(self)
|
||||||
|
if resp is not None:
|
||||||
|
return resp
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
return security_403_response()
|
return security_403_response()
|
||||||
|
|||||||
@ -53,6 +53,40 @@ And kill and remove /tmp/ultralytics_runner
|
|||||||
|
|
||||||
The version 8.3.41 to 8.3.42 of the Ultralytics package you installed is compromised. Please uninstall that version and reinstall the latest version.
|
The version 8.3.41 to 8.3.42 of the Ultralytics package you installed is compromised. Please uninstall that version and reinstall the latest version.
|
||||||
https://blog.comfy.org/comfyui-statement-on-the-ultralytics-crypto-miner-situation/
|
https://blog.comfy.org/comfyui-statement-on-the-ultralytics-crypto-miner-situation/
|
||||||
|
""",
|
||||||
|
"litellm==1.82.7": f"""
|
||||||
|
Execute following commands:
|
||||||
|
{sys.executable} -m pip uninstall litellm
|
||||||
|
|
||||||
|
The litellm PyPI package versions 1.82.7 and 1.82.8 were compromised via a supply chain attack.
|
||||||
|
Malicious code harvests SSH keys, environment variables, API keys, cloud credentials, and exfiltrates them to an attacker-controlled server.
|
||||||
|
Version 1.82.8 also installs a .pth file that executes malware on ANY Python startup, even without importing litellm.
|
||||||
|
|
||||||
|
1. Uninstall litellm immediately.
|
||||||
|
2. Assume all credentials accessible to the litellm environment are compromised.
|
||||||
|
3. Rotate all API keys, cloud credentials, SSH keys, and database passwords.
|
||||||
|
4. Check site-packages for unexpected .pth files (e.g. litellm_init.pth) and remove them.
|
||||||
|
5. Run a full malware scan.
|
||||||
|
|
||||||
|
Details: https://github.com/BerriAI/litellm/issues/24518
|
||||||
|
Advisory: PYSEC-2026-2
|
||||||
|
""",
|
||||||
|
"litellm==1.82.8": f"""
|
||||||
|
Execute following commands:
|
||||||
|
{sys.executable} -m pip uninstall litellm
|
||||||
|
|
||||||
|
The litellm PyPI package versions 1.82.7 and 1.82.8 were compromised via a supply chain attack.
|
||||||
|
Malicious code harvests SSH keys, environment variables, API keys, cloud credentials, and exfiltrates them to an attacker-controlled server.
|
||||||
|
Version 1.82.8 also installs a .pth file that executes malware on ANY Python startup, even without importing litellm.
|
||||||
|
|
||||||
|
1. Uninstall litellm immediately.
|
||||||
|
2. Assume all credentials accessible to the litellm environment are compromised.
|
||||||
|
3. Rotate all API keys, cloud credentials, SSH keys, and database passwords.
|
||||||
|
4. Check site-packages for unexpected .pth files (e.g. litellm_init.pth) and remove them.
|
||||||
|
5. Run a full malware scan.
|
||||||
|
|
||||||
|
Details: https://github.com/BerriAI/litellm/issues/24518
|
||||||
|
Advisory: PYSEC-2026-2
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +94,10 @@ https://blog.comfy.org/comfyui-statement-on-the-ultralytics-crypto-miner-situati
|
|||||||
|
|
||||||
pip_blacklist = {
|
pip_blacklist = {
|
||||||
"AppleBotzz": "ComfyUI_LLMVISION",
|
"AppleBotzz": "ComfyUI_LLMVISION",
|
||||||
"ultralytics==8.3.41": "ultralytics==8.3.41"
|
"ultralytics==8.3.41": "ultralytics==8.3.41",
|
||||||
|
"ultralytics==8.3.42": "ultralytics==8.3.42",
|
||||||
|
"litellm==1.82.7": "litellm==1.82.7",
|
||||||
|
"litellm==1.82.8": "litellm==1.82.8",
|
||||||
}
|
}
|
||||||
|
|
||||||
file_blacklist = {
|
file_blacklist = {
|
||||||
@ -93,10 +130,15 @@ https://blog.comfy.org/comfyui-statement-on-the-ultralytics-crypto-miner-situati
|
|||||||
print(f"[SECURITY ALERT] custom node '{k}' is dangerous.")
|
print(f"[SECURITY ALERT] custom node '{k}' is dangerous.")
|
||||||
detected.add(v)
|
detected.add(v)
|
||||||
|
|
||||||
|
installed_pip_set = set(installed_pips.strip().split('\n'))
|
||||||
|
|
||||||
for k, v in pip_blacklist.items():
|
for k, v in pip_blacklist.items():
|
||||||
if k in installed_pips:
|
if '==' in k:
|
||||||
detected.add(v)
|
if k in installed_pip_set:
|
||||||
break
|
detected.add(v)
|
||||||
|
else:
|
||||||
|
if any(line.split('==')[0] == k for line in installed_pip_set):
|
||||||
|
detected.add(v)
|
||||||
|
|
||||||
for k, v in file_blacklist.items():
|
for k, v in file_blacklist.items():
|
||||||
for x in v:
|
for x in v:
|
||||||
@ -105,10 +147,14 @@ https://blog.comfy.org/comfyui-statement-on-the-ultralytics-crypto-miner-situati
|
|||||||
break
|
break
|
||||||
|
|
||||||
if len(detected) > 0:
|
if len(detected) > 0:
|
||||||
for line in installed_pips.split('\n'):
|
for line in installed_pip_set:
|
||||||
for k, v in pip_blacklist.items():
|
for k, v in pip_blacklist.items():
|
||||||
if k in line:
|
if '==' in k:
|
||||||
print(f"[SECURITY ALERT] '{line}' is dangerous.")
|
if line == k:
|
||||||
|
print(f"[SECURITY ALERT] '{line}' is dangerous.")
|
||||||
|
else:
|
||||||
|
if line.split('==')[0] == k:
|
||||||
|
print(f"[SECURITY ALERT] '{line}' is dangerous.")
|
||||||
|
|
||||||
print("\n########################################################################")
|
print("\n########################################################################")
|
||||||
print(" Malware has been detected, forcibly terminating ComfyUI execution.")
|
print(" Malware has been detected, forcibly terminating ComfyUI execution.")
|
||||||
|
|||||||
@ -52,7 +52,7 @@ async function tryInstallCustomNode(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = await api.fetchApi("/manager/reboot");
|
let response = await api.fetchApi("/manager/reboot", { method: 'POST' });
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
await handle403Response(response);
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -470,12 +470,12 @@ async function updateComfyUI() {
|
|||||||
|
|
||||||
set_inprogress_mode();
|
set_inprogress_mode();
|
||||||
|
|
||||||
const response = await api.fetchApi('/manager/queue/update_comfyui');
|
const response = await api.fetchApi('/manager/queue/update_comfyui', { method: 'POST' });
|
||||||
|
|
||||||
showTerminal();
|
showTerminal();
|
||||||
|
|
||||||
is_updating = true;
|
is_updating = true;
|
||||||
await api.fetchApi('/manager/queue/start');
|
await api.fetchApi('/manager/queue/start', { method: 'POST' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function showVersionSelectorDialog(versions, current, onSelect) {
|
function showVersionSelectorDialog(versions, current, onSelect) {
|
||||||
@ -625,14 +625,14 @@ async function switchComfyUI() {
|
|||||||
showVersionSelectorDialog(versions, obj.current, async (selected_version) => {
|
showVersionSelectorDialog(versions, obj.current, async (selected_version) => {
|
||||||
if(selected_version == 'nightly') {
|
if(selected_version == 'nightly') {
|
||||||
update_policy_combo.value = 'nightly-comfyui';
|
update_policy_combo.value = 'nightly-comfyui';
|
||||||
api.fetchApi('/manager/policy/update?value=nightly-comfyui');
|
api.fetchApi('/manager/policy/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: 'nightly-comfyui' }) });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
update_policy_combo.value = 'stable-comfyui';
|
update_policy_combo.value = 'stable-comfyui';
|
||||||
api.fetchApi('/manager/policy/update?value=stable-comfyui');
|
api.fetchApi('/manager/policy/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: 'stable-comfyui' }) });
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = await api.fetchApi(`/comfyui_manager/comfyui_switch_version?ver=${selected_version}`, { cache: "no-store" });
|
let response = await api.fetchApi('/comfyui_manager/comfyui_switch_version', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ver: selected_version }), cache: "no-store" });
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
infoToast(`ComfyUI version is switched to ${selected_version}`);
|
infoToast(`ComfyUI version is switched to ${selected_version}`);
|
||||||
}
|
}
|
||||||
@ -769,10 +769,10 @@ async function updateAll(update_comfyui) {
|
|||||||
|
|
||||||
if(update_comfyui) {
|
if(update_comfyui) {
|
||||||
update_all_button.innerText = "Updating ComfyUI...";
|
update_all_button.innerText = "Updating ComfyUI...";
|
||||||
await api.fetchApi('/manager/queue/update_comfyui');
|
await api.fetchApi('/manager/queue/update_comfyui', { method: 'POST' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await api.fetchApi(`/manager/queue/update_all?mode=${mode}`);
|
const response = await api.fetchApi('/manager/queue/update_all', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: mode }) });
|
||||||
|
|
||||||
if (response.status == 403) {
|
if (response.status == 403) {
|
||||||
await handle403Response(response);
|
await handle403Response(response);
|
||||||
@ -784,7 +784,7 @@ async function updateAll(update_comfyui) {
|
|||||||
}
|
}
|
||||||
else if(response.status == 200) {
|
else if(response.status == 200) {
|
||||||
is_updating = true;
|
is_updating = true;
|
||||||
await api.fetchApi('/manager/queue/start');
|
await api.fetchApi('/manager/queue/start', { method: 'POST' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -813,7 +813,7 @@ function restartOrStop() {
|
|||||||
rebootAPI();
|
rebootAPI();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
api.fetchApi('/manager/queue/reset');
|
api.fetchApi('/manager/queue/reset', { method: 'POST' });
|
||||||
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
|
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -967,7 +967,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
.then(data => { this.datasrc_combo.value = data; });
|
.then(data => { this.datasrc_combo.value = data; });
|
||||||
|
|
||||||
this.datasrc_combo.addEventListener('change', function (event) {
|
this.datasrc_combo.addEventListener('change', function (event) {
|
||||||
api.fetchApi(`/manager/db_mode?value=${event.target.value}`);
|
api.fetchApi('/manager/db_mode', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: event.target.value }) });
|
||||||
});
|
});
|
||||||
|
|
||||||
const dbRetrievalSetttingItem = createSettingsCombo("DB", this.datasrc_combo);
|
const dbRetrievalSetttingItem = createSettingsCombo("DB", this.datasrc_combo);
|
||||||
@ -1043,7 +1043,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal operation
|
// Normal operation
|
||||||
api.fetchApi(`/manager/preview_method?value=${event.target.value}`)
|
api.fetchApi('/manager/preview_method', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: event.target.value }) })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.status === 403) {
|
if (response.status === 403) {
|
||||||
// Feature transitioned to native
|
// Feature transitioned to native
|
||||||
@ -1087,7 +1087,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
channel_combo.addEventListener('change', function (event) {
|
channel_combo.addEventListener('change', function (event) {
|
||||||
api.fetchApi(`/manager/channel_url_list?value=${event.target.value}`);
|
api.fetchApi('/manager/channel_url_list', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: event.target.value }) });
|
||||||
});
|
});
|
||||||
|
|
||||||
channel_combo.value = data.selected;
|
channel_combo.value = data.selected;
|
||||||
@ -1152,7 +1152,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
});
|
});
|
||||||
|
|
||||||
component_policy_combo.addEventListener('change', function (event) {
|
component_policy_combo.addEventListener('change', function (event) {
|
||||||
api.fetchApi(`/manager/policy/component?value=${event.target.value}`);
|
api.fetchApi('/manager/policy/component', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: event.target.value }) });
|
||||||
set_component_policy(event.target.value);
|
set_component_policy(event.target.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1171,7 +1171,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
});
|
});
|
||||||
|
|
||||||
update_policy_combo.addEventListener('change', function (event) {
|
update_policy_combo.addEventListener('change', function (event) {
|
||||||
api.fetchApi(`/manager/policy/update?value=${event.target.value}`);
|
api.fetchApi('/manager/policy/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: event.target.value }) });
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateSetttingItem = createSettingsCombo("Update", update_policy_combo);
|
const updateSetttingItem = createSettingsCombo("Update", update_policy_combo);
|
||||||
|
|||||||
@ -185,7 +185,7 @@ export async function rebootAPI() {
|
|||||||
const isConfirmed = await customConfirm("Are you sure you'd like to reboot the server?");
|
const isConfirmed = await customConfirm("Are you sure you'd like to reboot the server?");
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
try {
|
try {
|
||||||
const response = await api.fetchApi("/manager/reboot");
|
const response = await api.fetchApi("/manager/reboot", { method: 'POST' });
|
||||||
if (response.status == 403) {
|
if (response.status == 403) {
|
||||||
await handle403Response(response);
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -462,7 +462,7 @@ export class CustomNodesManager {
|
|||||||
|
|
||||||
".cn-manager-stop": {
|
".cn-manager-stop": {
|
||||||
click: () => {
|
click: () => {
|
||||||
api.fetchApi('/manager/queue/reset');
|
api.fetchApi('/manager/queue/reset', { method: 'POST' });
|
||||||
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
|
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1476,9 +1476,15 @@ export class CustomNodesManager {
|
|||||||
let needRestart = false;
|
let needRestart = false;
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
|
|
||||||
await api.fetchApi('/manager/queue/reset');
|
await api.fetchApi('/manager/queue/reset', { method: 'POST' });
|
||||||
|
|
||||||
|
// Set install_context BEFORE per-item queue enqueue calls so that any
|
||||||
|
// server-side synchronous completion (e.g., sync enable of an inactive
|
||||||
|
// node) that emits cm-queue-status before we return here still finds
|
||||||
|
// install_context populated in onQueueCompleted. target_items is shared
|
||||||
|
// by reference so further pushes below remain visible.
|
||||||
let target_items = [];
|
let target_items = [];
|
||||||
|
this.install_context = {btn: btn, targets: target_items};
|
||||||
|
|
||||||
for (const hash of list) {
|
for (const hash of list) {
|
||||||
const item = this.grid.getRowItemBy("hash", hash);
|
const item = this.grid.getRowItemBy("hash", hash);
|
||||||
@ -1550,8 +1556,6 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.install_context = {btn: btn, targets: target_items};
|
|
||||||
|
|
||||||
if(errorMsg) {
|
if(errorMsg) {
|
||||||
this.showError(errorMsg);
|
this.showError(errorMsg);
|
||||||
show_message("[Installation Errors]\n"+errorMsg);
|
show_message("[Installation Errors]\n"+errorMsg);
|
||||||
@ -1563,7 +1567,7 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await api.fetchApi('/manager/queue/start');
|
await api.fetchApi('/manager/queue/start', { method: 'POST' });
|
||||||
this.showStop();
|
this.showStop();
|
||||||
showTerminal();
|
showTerminal();
|
||||||
}
|
}
|
||||||
@ -1576,6 +1580,10 @@ export class CustomNodesManager {
|
|||||||
|
|
||||||
const item = self.grid.getRowItemBy("hash", hash);
|
const item = self.grid.getRowItemBy("hash", hash);
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
item.restart = true;
|
item.restart = true;
|
||||||
self.restartMap[item.hash] = true;
|
self.restartMap[item.hash] = true;
|
||||||
self.grid.updateCell(item, "action");
|
self.grid.updateCell(item, "action");
|
||||||
@ -1583,45 +1591,81 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
else if(event.detail.status == 'done') {
|
else if(event.detail.status == 'done') {
|
||||||
self.hideStop();
|
self.hideStop();
|
||||||
self.onQueueCompleted(event.detail);
|
// Await + error logging so any unhandled rejection surfaces to the
|
||||||
|
// console instead of silently swallowing completion finalization
|
||||||
|
// (root cause of disable/enable button staying loading with no toast).
|
||||||
|
try {
|
||||||
|
await self.onQueueCompleted(event.detail);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ComfyUI-Manager] onQueueCompleted failed:", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onQueueCompleted(info) {
|
async onQueueCompleted(info) {
|
||||||
|
// `nodepack_result` is a dict serialized from a Python dict, not an array.
|
||||||
|
// `dict.length` is `undefined` and `undefined == 0` is `false`, so the
|
||||||
|
// previous `result.length == 0` guard was a no-op; switch to a correct
|
||||||
|
// empty-check that also tolerates null/undefined.
|
||||||
let result = info.nodepack_result;
|
let result = info.nodepack_result;
|
||||||
|
|
||||||
if(result.length == 0) {
|
if (!result || Object.keys(result).length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let self = CustomNodesManager.instance;
|
let self = CustomNodesManager.instance;
|
||||||
|
|
||||||
if(!self.install_context) {
|
if (!self || !self.install_context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { target, label, mode } = self.install_context.btn;
|
const { target, label, mode } = self.install_context.btn;
|
||||||
target.classList.remove("cn-btn-loading");
|
const targets = self.install_context.targets || [];
|
||||||
|
|
||||||
|
// Compute errorMsg upfront so the downstream user-visible finalization
|
||||||
|
// (showRestart / showMessage / infoToast) fires regardless of whether
|
||||||
|
// any DOM-touching step below throws.
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
|
for (let hash in result) {
|
||||||
for(let hash in result){
|
|
||||||
let v = result[hash];
|
let v = result[hash];
|
||||||
|
if (v != 'success' && v != 'skip') {
|
||||||
if(v != 'success' && v != 'skip')
|
errorMsg += v + '\n';
|
||||||
errorMsg += v+'\n';
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let k in self.install_context.targets) {
|
// Defensive: `target` may be a detached DOM node (the in_progress
|
||||||
let item = self.install_context.targets[k];
|
// handler's updateCell can re-render the row and replace the button
|
||||||
self.grid.updateCell(item, "action");
|
// element). classList.remove on a detached node is a no-op, but we
|
||||||
|
// still guard in case target was torn down entirely.
|
||||||
|
try {
|
||||||
|
if (target && target.classList) {
|
||||||
|
target.classList.remove("cn-btn-loading");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[ComfyUI-Manager] Failed to clear button loading state:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defensive: grid.updateCell can throw if the item was removed or the
|
||||||
|
// grid was re-rendered between in_progress and done. Do NOT let this
|
||||||
|
// loop abort the completion finalization below — that was the observed
|
||||||
|
// failure mode for disable/enable (no toast, no "restart required"
|
||||||
|
// message).
|
||||||
|
try {
|
||||||
|
for (let k in targets) {
|
||||||
|
let item = targets[k];
|
||||||
|
if (item) {
|
||||||
|
self.grid.updateCell(item, "action");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[ComfyUI-Manager] Failed to refresh target cells after queue completion:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
self.showError(errorMsg);
|
self.showError(errorMsg);
|
||||||
show_message("Installation Error:\n"+errorMsg);
|
show_message("Installation Error:\n" + errorMsg);
|
||||||
} else {
|
} else {
|
||||||
self.showStatus(`${label} ${result.length} custom node(s) successfully`);
|
self.showStatus(`${label} ${Object.keys(result).length} custom node(s) successfully`);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.showRestart();
|
self.showRestart();
|
||||||
|
|||||||
@ -170,7 +170,7 @@ export class ModelManager {
|
|||||||
|
|
||||||
".cmm-manager-stop": {
|
".cmm-manager-stop": {
|
||||||
click: () => {
|
click: () => {
|
||||||
api.fetchApi('/manager/queue/reset');
|
api.fetchApi('/manager/queue/reset', { method: 'POST' });
|
||||||
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
|
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -444,7 +444,7 @@ export class ModelManager {
|
|||||||
let needRefresh = false;
|
let needRefresh = false;
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
|
|
||||||
await api.fetchApi('/manager/queue/reset');
|
await api.fetchApi('/manager/queue/reset', { method: 'POST' });
|
||||||
|
|
||||||
let target_items = [];
|
let target_items = [];
|
||||||
|
|
||||||
@ -503,7 +503,7 @@ export class ModelManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await api.fetchApi('/manager/queue/start');
|
await api.fetchApi('/manager/queue/start', { method: 'POST' });
|
||||||
this.showStop();
|
this.showStop();
|
||||||
showTerminal();
|
showTerminal();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ loadCss("./snapshot.css");
|
|||||||
async function restore_snapshot(target) {
|
async function restore_snapshot(target) {
|
||||||
if(SnapshotManager.instance) {
|
if(SnapshotManager.instance) {
|
||||||
try {
|
try {
|
||||||
const response = await api.fetchApi(`/snapshot/restore?target=${target}`, { cache: "no-store" });
|
const response = await api.fetchApi('/snapshot/restore', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ target: target }), cache: "no-store" });
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
await handle403Response(response);
|
await handle403Response(response);
|
||||||
@ -37,7 +37,7 @@ async function restore_snapshot(target) {
|
|||||||
async function remove_snapshot(target) {
|
async function remove_snapshot(target) {
|
||||||
if(SnapshotManager.instance) {
|
if(SnapshotManager.instance) {
|
||||||
try {
|
try {
|
||||||
const response = await api.fetchApi(`/snapshot/remove?target=${target}`, { cache: "no-store" });
|
const response = await api.fetchApi('/snapshot/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ target: target }), cache: "no-store" });
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
await handle403Response(response);
|
await handle403Response(response);
|
||||||
@ -63,7 +63,7 @@ async function remove_snapshot(target) {
|
|||||||
|
|
||||||
async function save_current_snapshot() {
|
async function save_current_snapshot() {
|
||||||
try {
|
try {
|
||||||
const response = await api.fetchApi('/snapshot/save', { cache: "no-store" });
|
const response = await api.fetchApi('/snapshot/save', { method: 'POST', cache: "no-store" });
|
||||||
app.ui.dialog.close();
|
app.ui.dialog.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,165 @@
|
|||||||
{
|
{
|
||||||
"custom_nodes": [
|
"custom_nodes": [
|
||||||
|
{
|
||||||
|
"author": "andreszs",
|
||||||
|
"title": "ComfyUI-Lora-Pipeline",
|
||||||
|
"reference": "https://github.com/andreszs/ComfyUI-Lora-Pipeline",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/andreszs/ComfyUI-Lora-Pipeline"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Area-based LoRA and conditioning workflow tools for ComfyUI with multi-region control."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Zinigo",
|
||||||
|
"title": "Prompt Builder [NAME CONFLICT]",
|
||||||
|
"reference": "https://github.com/zinigo-creations/comfyui-prompt-builder",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/zinigo-creations/comfyui-prompt-builder"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Dropdown-based prompt builder for ComfyUI. Create characters, scenes, and styles without writing prompts. Designed for beginners and fast iteration."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "ethanfel",
|
||||||
|
"title": "Comfyui-Return-Run-Logic",
|
||||||
|
"reference": "https://github.com/ethanfel/Comfyui-Return-Run-Logic",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/ethanfel/Comfyui-Return-Run-Logic"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Adds a toggle button to quickly switch between Run and Run (Instant) modes without opening the queue button dropdown menu."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "outrun32",
|
||||||
|
"title": "[WIP] comfyui-chat-assistant",
|
||||||
|
"reference": "https://github.com/outrun32/comfyui-chat-assistant",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/outrun32/comfyui-chat-assistant"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A streamlined ComfyUI extension that adds an AI-powered chat interface to the sidebar for converting complex briefs/specs into generation-ready prompts. (Description by CC)\nNOTE: The files in the repo are not organized."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "ComfyuiGY",
|
||||||
|
"title": "Comfyui-Memory-Clear",
|
||||||
|
"reference": "https://github.com/ComfyuiGY/Comfyui-Memory-Clear",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/ComfyuiGY/Comfyui-Memory-Clear"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI node for clearing memory. (Description by CC)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "lying1324a-glitch",
|
||||||
|
"title": "comfyuiautofirelaod",
|
||||||
|
"reference": "https://github.com/lying1324a-glitch/comfyuiautofirelaod",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/lying1324a-glitch/comfyuiautofirelaod"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI auto-fire loader extension. (Description by CC)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "skkut",
|
||||||
|
"title": "[WIP] ComfyUI-detailed-jobstatus",
|
||||||
|
"reference": "https://github.com/skkut/ComfyUI-detailed-jobstatus",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/skkut/ComfyUI-detailed-jobstatus"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A simple ComfyUI extension to show a execution timer.\nNOTE: The files in the repo are not organized."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "bob-bar-b-que",
|
||||||
|
"title": "ComfyUI CLIP Token Counter [WIP]",
|
||||||
|
"reference": "https://github.com/Bob-Bar-B-Que/ComfyUI_clip_token_counter",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Bob-Bar-B-Que/ComfyUI_clip_token_counter"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Shows real-time token count and adds a copy button to CLIPTextEncode nodes in ComfyUI.\nNOTE: The files in the repo are not organized."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Lisa-Mays",
|
||||||
|
"title": "wan-model-downloader",
|
||||||
|
"reference": "https://github.com/Lisa-Mays/wan-model-downloader",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Lisa-Mays/wan-model-downloader"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Auto-downloads required models on startup."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "fya16838",
|
||||||
|
"title": "Comfyui-Xiufu",
|
||||||
|
"reference": "https://github.com/fya16838/Comfyui-Xiufu",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/fya16838/Comfyui-Xiufu"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Fix ComfyUI models and nodes after importing workflows from others. (Description by CC)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "fengye12",
|
||||||
|
"title": "ComfyUI_XL_OpenAPI",
|
||||||
|
"reference": "https://github.com/fengye12/ComfyUI_XL_OpenAPI",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/fengye12/ComfyUI_XL_OpenAPI"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "OpenAPI wrapper for ComfyUI XL, providing standardized API access. (Description by CC)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "scruffynerf",
|
||||||
|
"title": "_0_comfyui_transformers_fix",
|
||||||
|
"reference": "https://github.com/scruffynerf/_0_comfyui_transformers_fix",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/scruffynerf/_0_comfyui_transformers_fix"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Quick patch that solves compatibility problems caused by MeshGraphormer-DepthMapPreprocessor by injecting missing functions into transformers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "pixelmavenai",
|
||||||
|
"title": "comfyui-custom-branding",
|
||||||
|
"reference": "https://github.com/pixelmavenai/comfyui-custom-branding",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/pixelmavenai/comfyui-custom-branding"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Custom branding for ComfyUI tab title and favicon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "ka4ok424",
|
||||||
|
"title": "ComfyUI-NodeLibrary",
|
||||||
|
"reference": "https://github.com/ka4ok424/ComfyUI-NodeLibrary",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/ka4ok424/ComfyUI-NodeLibrary"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "No ComfyUI nodes detected. May not be a ComfyUI custom node repository."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "skkut",
|
||||||
|
"title": "ComfyUI-Auto-DarkMode [WIP]",
|
||||||
|
"reference": "https://github.com/skkut/ComfyUI-Auto-DarkMode",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/skkut/ComfyUI-Auto-DarkMode"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Lightweight ComfyUI extension that automatically detects and matches your system's Light/Dark theme in real-time.\nNOTE: The files in the repo are not organized."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "xWris3",
|
||||||
|
"title": "jus_multifruit_comfyui",
|
||||||
|
"reference": "https://github.com/xWris3/jus_multifruit_comfyui",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/xWris3/jus_multifruit_comfyui"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Collection of custom nodes."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"author": "synthetai",
|
"author": "synthetai",
|
||||||
"title": "ComfyUI-ToolBox [NAME CONFLICT]",
|
"title": "ComfyUI-ToolBox [NAME CONFLICT]",
|
||||||
@ -140,16 +300,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "ComfyUI custom nodes for AudioX/AudioX-MAF video-to-audio generation"
|
"description": "ComfyUI custom nodes for AudioX/AudioX-MAF video-to-audio generation"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "1hew",
|
|
||||||
"title": "ComfyUI-1hewHungryStudio [WIP]",
|
|
||||||
"reference": "https://github.com/1hew/ComfyUI-1hewHungryStudio",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/1hew/ComfyUI-1hewHungryStudio"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "Custom ComfyUI nodes for image cloning and shape drawing operations. (Description by CC)\nNOTE: The files in the repo are not organized."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "Ahmed791996",
|
"author": "Ahmed791996",
|
||||||
"title": "SplatViwer_comfyUI",
|
"title": "SplatViwer_comfyUI",
|
||||||
@ -230,16 +380,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "A ComfyUI node implementing SMC-CFG (Sliding Mode Control CFG) that replaces standard linear CFG with a nonlinear sliding mode controller for stable guidance at any CFG scale. NOT WORKING - Work in progress. (Description by CC)"
|
"description": "A ComfyUI node implementing SMC-CFG (Sliding Mode Control CFG) that replaces standard linear CFG with a nonlinear sliding mode controller for stable guidance at any CFG scale. NOT WORKING - Work in progress. (Description by CC)"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "AiSatan",
|
|
||||||
"title": "ComfyUI_CSM [NAME CONFLICT]",
|
|
||||||
"reference": "https://github.com/AiSatan/ComfyUI_CSM",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/AiSatan/ComfyUI_CSM"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "A ComfyUI node for the CSM model featuring text-to-speech, voice cloning, and automatic model downloading from Hugging Face."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "unobtuse",
|
"author": "unobtuse",
|
||||||
"title": "comfyui-topaz-ai-upscale",
|
"title": "comfyui-topaz-ai-upscale",
|
||||||
@ -583,16 +723,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "ComfyUI nodes for GLM image generation, image-to-image translation, and flexible input handling. (Description by CC)"
|
"description": "ComfyUI nodes for GLM image generation, image-to-image translation, and flexible input handling. (Description by CC)"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "DailyMok",
|
|
||||||
"title": "ComfyUI-PromptMixerNode",
|
|
||||||
"reference": "https://github.com/DailyMok/ComfyUI-PromptMixerNode",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/DailyMok/ComfyUI-PromptMixerNode"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "ComfyUI custom node for prompt mixing with PromptMixerDaily node. (Description by CC)"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "shin131002",
|
"author": "shin131002",
|
||||||
"title": "[WIP] ComfyUI-Prompt-Preset-Selector",
|
"title": "[WIP] ComfyUI-Prompt-Preset-Selector",
|
||||||
@ -1684,16 +1814,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "ComfyUI custom node that splits multi-line prompts by line, enabling batch image generation with each line triggering one execution and supporting custom prompt boxes. (Description by CC)\nNOTE: The files in the repo are not organized."
|
"description": "ComfyUI custom node that splits multi-line prompts by line, enabling batch image generation with each line triggering one execution and supporting custom prompt boxes. (Description by CC)\nNOTE: The files in the repo are not organized."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "rookiestar28",
|
|
||||||
"title": "ComfyUI_Security_Audit",
|
|
||||||
"reference": "https://github.com/rookiestar28/ComfyUI_Security_Audit",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/rookiestar28/ComfyUI_Security_Audit"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "A lightweight, dual-layer security extension for ComfyUI using AST-based static analysis and runtime monitoring to detect malicious code in custom nodes."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "c1660181647-hash",
|
"author": "c1660181647-hash",
|
||||||
"title": "ComfyUI-MM-Visual-Encryption",
|
"title": "ComfyUI-MM-Visual-Encryption",
|
||||||
@ -1995,16 +2115,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "Custom ComfyUI nodes for preset latent image generation including EmptyLatentImagePresetsAIO, FLUX, QWEN, and SD1.5 variants. (Description by CC)"
|
"description": "Custom ComfyUI nodes for preset latent image generation including EmptyLatentImagePresetsAIO, FLUX, QWEN, and SD1.5 variants. (Description by CC)"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "silveroxides",
|
|
||||||
"title": "ComfyUI_LoHalo",
|
|
||||||
"reference": "https://github.com/silveroxides/ComfyUI_LoHalo",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/silveroxides/ComfyUI_LoHalo"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "ComfyUI upscaling node package with Lohalo high-fidelity scaling and kernel-based image enhancement capabilities. (Description by CC)"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "jorin91",
|
"author": "jorin91",
|
||||||
"title": "ComfyUI-JSG-Utils [UNSAFE]",
|
"title": "ComfyUI-JSG-Utils [UNSAFE]",
|
||||||
@ -2528,16 +2638,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "NODES: Amor's Image WebpSaver, Amor's Image Resizer, Amor's Text Translator, Amor's CogView Image Generator, ..."
|
"description": "NODES: Amor's Image WebpSaver, Amor's Image Resizer, Amor's Text Translator, Amor's CogView Image Generator, ..."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "Solarish",
|
|
||||||
"title": "fyUI-MidnightLook [WIP]",
|
|
||||||
"reference": "https://github.com/Solarish/ComfyUI-MidnightLook",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/Solarish/ComfyUI-MidnightLook"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "Custom node for MidnightLook.com\nNOTE: The files in the repo are not organized."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "hujuying",
|
"author": "hujuying",
|
||||||
"title": "ComfyUI-QwenVL3-image-Plus [WIP]",
|
"title": "ComfyUI-QwenVL3-image-Plus [WIP]",
|
||||||
@ -2558,16 +2658,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "ComfyUI custom nodes and utilities for workflow building, type conversions, checkpoint/pipe loaders and file utilities.[w/This nodepack includes an endpoint that access files from arbitrary paths.]"
|
"description": "ComfyUI custom nodes and utilities for workflow building, type conversions, checkpoint/pipe loaders and file utilities.[w/This nodepack includes an endpoint that access files from arbitrary paths.]"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "silveroxides",
|
|
||||||
"title": "ComfyUI_LogicMath",
|
|
||||||
"reference": "https://github.com/silveroxides/ComfyUI_LogicMath",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/silveroxides/ComfyUI_LogicMath"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "Custom node for with some basic logic and math nodes based on pull request [a/comfyanonymous/ComfyUI#8024](https://github.com/comfyanonymous/ComfyUI/pull/8024)"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "lyra-ai",
|
"author": "lyra-ai",
|
||||||
"title": "lyra-nodes",
|
"title": "lyra-nodes",
|
||||||
@ -2808,16 +2898,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "NODES: Morpheus · Batch Images + crop image, Morpheus · NanoBanana Mask, Morpheus · Image Editing Prompt, ...\nNOTE: The files in the repo are not organized."
|
"description": "NODES: Morpheus · Batch Images + crop image, Morpheus · NanoBanana Mask, Morpheus · Image Editing Prompt, ...\nNOTE: The files in the repo are not organized."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "silveroxides",
|
|
||||||
"title": "ComfyUI_PromptAttention [WIP]",
|
|
||||||
"reference": "https://github.com/silveroxides/ComfyUI_PromptAttention",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/silveroxides/ComfyUI_PromptAttention"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "NODES: Text Encode with Attention Bias"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "cedarconnor",
|
"author": "cedarconnor",
|
||||||
"title": "ComfyUI Motion Transfer Pack [WIP]",
|
"title": "ComfyUI Motion Transfer Pack [WIP]",
|
||||||
@ -2908,26 +2988,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "NODES: ImageScaleToTotalPixelsRound64, ImageBlendLighter, ImageOffset, RGBtoRYGCBM, RYGCBMtoRGB, ExtractImageChannel, MatchRYGCBMColors, TextCommaToWeighted, TextCommaToRandomWeighted, RGBtoLAB, LABtoRGB, ImageMirrorPad, ImageCropBorders, ImageStitcher, ImageScaleToQwen, ..."
|
"description": "NODES: ImageScaleToTotalPixelsRound64, ImageBlendLighter, ImageOffset, RGBtoRYGCBM, RYGCBMtoRGB, ExtractImageChannel, MatchRYGCBMColors, TextCommaToWeighted, TextCommaToRandomWeighted, RGBtoLAB, LABtoRGB, ImageMirrorPad, ImageCropBorders, ImageStitcher, ImageScaleToQwen, ..."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "basenc",
|
|
||||||
"title": "ComfyUI Environment Variable Node [UNSAFE]",
|
|
||||||
"reference": "https://github.com/basenc/Comfyui-EnvVarNode",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/basenc/Comfyui-EnvVarNode"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "Exposes environment variables inside ComfyUI workflows. Use it to keep API keys and other secrets out of your graph files while still wiring them into nodes that need them.[w/This nodepack has a vulnerability that allows reading arbitrary environment variables.]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "silveroxides",
|
|
||||||
"title": "Sampling Utility Toolkit",
|
|
||||||
"reference": "https://github.com/silveroxides/ComfyUI_SamplingUtils",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/silveroxides/ComfyUI_SamplingUtils"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "Utilities making it easy to set up inference parameters."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "henjicc",
|
"author": "henjicc",
|
||||||
"title": "ComfyUI-CC-ImageLoader [UNSAFE]",
|
"title": "ComfyUI-CC-ImageLoader [UNSAFE]",
|
||||||
@ -3068,16 +3128,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "NODES: Concatenate Sigmas Node, Splice Sigmas At %"
|
"description": "NODES: Concatenate Sigmas Node, Splice Sigmas At %"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "silveroxides",
|
|
||||||
"title": "WIP Conditioning Toolkit [UNSAFE]",
|
|
||||||
"reference": "https://github.com/silveroxides/ComfyUI_CondsUtils",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/silveroxides/ComfyUI_CondsUtils"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "Custom nodes for modifying, saving and loading of various conditionings.[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "ThuckMaBaws",
|
"author": "ThuckMaBaws",
|
||||||
"title": "TMB Camera Modifier for ComfyUI [WIP]",
|
"title": "TMB Camera Modifier for ComfyUI [WIP]",
|
||||||
@ -4850,16 +4900,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "Package that adds nodes to retrieve the system date and time.\nNOTE: The files in the repo are not organized."
|
"description": "Package that adds nodes to retrieve the system date and time.\nNOTE: The files in the repo are not organized."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "rodpl",
|
|
||||||
"title": "comfyui-asset-manager",
|
|
||||||
"reference": "https://github.com/rodpl/comfyui-asset-manager",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/rodpl/comfyui-asset-manager"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "ComfyUI Asset Manager for managing assets in ComfyUI"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "blepping",
|
"author": "blepping",
|
||||||
"title": "ComfyUI 'dum' samplers [WIP]",
|
"title": "ComfyUI 'dum' samplers [WIP]",
|
||||||
@ -5851,16 +5891,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "This comfyui node helps save image[w/This nodepack contains a node that can write files to an arbitrary path.]"
|
"description": "This comfyui node helps save image[w/This nodepack contains a node that can write files to an arbitrary path.]"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "Oct7",
|
|
||||||
"title": "ComfyUI-LaplaMask",
|
|
||||||
"reference": "https://github.com/Oct7/ComfyUI-LaplaMask",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/Oct7/ComfyUI-LaplaMask"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "NODES: Blur→Mask"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "etng",
|
"author": "etng",
|
||||||
"title": "ComfyUI-Heartbeat [UNSAFE]",
|
"title": "ComfyUI-Heartbeat [UNSAFE]",
|
||||||
@ -6192,16 +6222,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "NODES: TelegramSend, TelegramReply"
|
"description": "NODES: TelegramSend, TelegramReply"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "qlikpetersen",
|
|
||||||
"title": "ComfyUI-AI_Tools [UNSAFE]",
|
|
||||||
"reference": "https://github.com/qlikpetersen/ComfyUI-AI_Tools",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/qlikpetersen/ComfyUI-AI_Tools"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "NODES: DoLogin, HttpRequest, Json2String, String2Json, CreateListString, CreateListJSON, Query_OpenAI, Image_Attachment, JSON_Attachment, String_Attachment, RunPython\n[w/This nodepack contains a node with a vulnerability that allows arbitrary code execution.]"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "MuAIGC",
|
"author": "MuAIGC",
|
||||||
"title": "DMXAPI Nodes [WIP]",
|
"title": "DMXAPI Nodes [WIP]",
|
||||||
@ -6552,16 +6572,6 @@
|
|||||||
"description": "NODES: Properties, Apply SVG to Image",
|
"description": "NODES: Properties, Apply SVG to Image",
|
||||||
"install_type": "git-clone"
|
"install_type": "git-clone"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "Kur0butiMegane",
|
|
||||||
"title": "Comfyui-StringUtils",
|
|
||||||
"reference": "https://github.com/Kur0butiMegane/Comfyui-StringUtils2",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/Kur0butiMegane/Comfyui-StringUtils2"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "NODES: Normalizer, Splitter, Selector, XML Parser, XML Parser, Make Property, Add XML Tag, Is String Empty, Cond Passthrough, CLIP Passthrough, ClipRegion Passthrough, Scheduler Selector (Impact), Scheduler Selector (Inspire), Save Text, XML to Cutoff"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "ronaldstg",
|
"author": "ronaldstg",
|
||||||
"title": "comfyui-plus-integrations [WIP]",
|
"title": "comfyui-plus-integrations [WIP]",
|
||||||
@ -6602,16 +6612,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "ComfyUI implementation of the partfield nvidea segmentation models\nNOTE: The files in the repo are not organized."
|
"description": "ComfyUI implementation of the partfield nvidea segmentation models\nNOTE: The files in the repo are not organized."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "silveroxides",
|
|
||||||
"title": "ComfyUI_ReduxEmbedToolkit",
|
|
||||||
"reference": "https://github.com/silveroxides/ComfyUI_ReduxEmbedToolkit",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/silveroxides/ComfyUI_ReduxEmbedToolkit"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "Custom nodes for managing, saving and loading of Redux/Style based embeddings."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "StaffsGull",
|
"author": "StaffsGull",
|
||||||
"title": "comfyui_scene_builder [WIP]",
|
"title": "comfyui_scene_builder [WIP]",
|
||||||
@ -11273,16 +11273,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "Nodes: Node Jumper. Various quality of life testing nodes"
|
"description": "Nodes: Node Jumper. Various quality of life testing nodes"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "IvanZhd",
|
|
||||||
"title": "comfyui-codeformer [WIP]",
|
|
||||||
"reference": "https://github.com/IvanZhd/comfyui-codeformer",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/IvanZhd/comfyui-codeformer"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "Nodes:Image Inverter"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "romeobuilderotti",
|
"author": "romeobuilderotti",
|
||||||
"title": "ComfyUI-EZ-Pipes",
|
"title": "ComfyUI-EZ-Pipes",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,360 @@
|
|||||||
{
|
{
|
||||||
"custom_nodes": [
|
"custom_nodes": [
|
||||||
|
{
|
||||||
|
"author": "qlikpetersen",
|
||||||
|
"title": "ComfyUI-AI_Tools [UNSAFE] [REMOVED]",
|
||||||
|
"reference": "https://github.com/qlikpetersen/ComfyUI-AI_Tools",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/qlikpetersen/ComfyUI-AI_Tools"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "NODES: DoLogin, HttpRequest, Json2String, String2Json, CreateListString, CreateListJSON, Query_OpenAI, Image_Attachment, JSON_Attachment, String_Attachment, RunPython\n[w/This nodepack contains a node with a vulnerability that allows arbitrary code execution.]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "AiSatan",
|
||||||
|
"title": "ComfyUI_CSM [NAME CONFLICT] [REMOVED]",
|
||||||
|
"reference": "https://github.com/AiSatan/ComfyUI_CSM",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/AiSatan/ComfyUI_CSM"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A ComfyUI node for the CSM model featuring text-to-speech, voice cloning, and automatic model downloading from Hugging Face."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "shiertier",
|
||||||
|
"title": "ComfyUI-TeaCache-Lumina [REMOVED]",
|
||||||
|
"reference": "https://github.com/shiertier/ComfyUI-TeaCache-lumina2",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/shiertier/ComfyUI-TeaCache-lumina2"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI Node Implementation: TeaCache Acceleration Specifically Designed for the Lumina Model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Daniel Lewis",
|
||||||
|
"title": "ComfyUI-TTS [REMOVED]",
|
||||||
|
"reference": "https://github.com/daniel-lewis-ab/ComfyUI-TTS",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/daniel-lewis-ab/ComfyUI-TTS"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Text To Speech (TTS) for ComfyUI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Daniel Lewis",
|
||||||
|
"title": "ComfyUI-Llama [REMOVED]",
|
||||||
|
"reference": "https://github.com/daniel-lewis-ab/ComfyUI-Llama",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/daniel-lewis-ab/ComfyUI-Llama"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "This is a set of nodes to interact with llama-cpp-python"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Solarish",
|
||||||
|
"title": "fyUI-MidnightLook [REMOVED]",
|
||||||
|
"reference": "https://github.com/Solarish/ComfyUI-MidnightLook",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Solarish/ComfyUI-MidnightLook"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Custom node for MidnightLook.com\nNOTE: The files in the repo are not organized."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "pdale-boop",
|
||||||
|
"title": "Network Guardian [REMOVED]",
|
||||||
|
"reference": "https://github.com/pdale-boop/Comfyui-Network-Guardian",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/pdale-boop/Comfyui-Network-Guardian"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Protects ComfyUI workflows from network interruptions with automatic retry and fallback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "rodpl",
|
||||||
|
"title": "comfyui-asset-manager [INCOMPLETE]",
|
||||||
|
"reference": "https://github.com/rodpl/comfyui-asset-manager",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/rodpl/comfyui-asset-manager"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI Asset Manager for managing assets in ComfyUI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "DailyMok",
|
||||||
|
"title": "ComfyUI-PromptMixerNode [REMOVED]",
|
||||||
|
"reference": "https://github.com/DailyMok/ComfyUI-PromptMixerNode",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/DailyMok/ComfyUI-PromptMixerNode"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI custom node for prompt mixing with PromptMixerDaily node. (Description by CC)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "IvanZhd",
|
||||||
|
"title": "comfyui-codeformer [REMOVED]",
|
||||||
|
"reference": "https://github.com/IvanZhd/comfyui-codeformer",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/IvanZhd/comfyui-codeformer"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Nodes:Image Inverter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Kur0butiMegane",
|
||||||
|
"title": "Comfyui-StringUtils [REMOVED]",
|
||||||
|
"reference": "https://github.com/Kur0butiMegane/Comfyui-StringUtils2",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Kur0butiMegane/Comfyui-StringUtils2"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "NODES: Normalizer, Splitter, Selector, XML Parser, XML Parser, Make Property, Add XML Tag, Is String Empty, Cond Passthrough, CLIP Passthrough, ClipRegion Passthrough, Scheduler Selector (Impact), Scheduler Selector (Inspire), Save Text, XML to Cutoff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "rookiestar28",
|
||||||
|
"title": "ComfyUI_Security_Audit [REMOVED]",
|
||||||
|
"reference": "https://github.com/rookiestar28/ComfyUI_Security_Audit",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/rookiestar28/ComfyUI_Security_Audit"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A lightweight, dual-layer security extension for ComfyUI using AST-based static analysis and runtime monitoring to detect malicious code in custom nodes."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "hw5511",
|
||||||
|
"title": "Woohee HF Upscaler Loader [REMOVED]",
|
||||||
|
"reference": "https://github.com/hw5511/comfyui_hf_upscaler_loader",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/hw5511/comfyui_hf_upscaler_loader"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Load upscale models directly from Hugging Face Hub in ComfyUI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "arthur",
|
||||||
|
"title": "Video Output Bridge [REMOVED]",
|
||||||
|
"reference": "https://github.com/arthurtravers/ComfyUI-VideoOutputBridge",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/arthurtravers/ComfyUI-VideoOutputBridge"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Bridge VHS_VideoCombine video outputs to standard image payloads for serverless runners (RunPod, Modal). Enables automatic S3 uploads of MP4/WebP files in worker-comfyui deployments."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "UmeAiRT",
|
||||||
|
"title": "ComfyUI-UmeAiRT-Sync [REMOVED]",
|
||||||
|
"reference": "https://github.com/UmeAiRT/ComfyUI-UmeAiRT-Sync",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/UmeAiRT/ComfyUI-UmeAiRT-Sync"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Automatic workflow synchronizer/loader for UmeAiRT Workflows (Flux, SDXL, WAN, etc.)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "UmeAiRT",
|
||||||
|
"title": "UmeAiRT-Toolkit [REMOVED]",
|
||||||
|
"reference": "https://github.com/UmeAiRT/ComfyUI-UmeAiRT-Toolkit",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/UmeAiRT/ComfyUI-UmeAiRT-Toolkit"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A Wireless, Nodes 2.0 Ready, and Aesthetic Toolkit for ComfyUI."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "sln77",
|
||||||
|
"title": "ComfyUI-Camie-Tagger [REMOVED]",
|
||||||
|
"reference": "https://github.com/sln77/ComfyUI-Camie-Tagger",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/sln77/ComfyUI-Camie-Tagger"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI node integration for the Camie tagger v2 model from Hugging Face for image tagging. (Description by CC)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "vegaflowltd",
|
||||||
|
"title": "Vega Flow V8.9 — Temporal Stabilisation [REMOVED]",
|
||||||
|
"id": "comfyui-vegaflow",
|
||||||
|
"reference": "https://github.com/vegaflowltd/ComfyUI-VegaFlow",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/vegaflowltd/ComfyUI-VegaFlow"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Professional temporal stabilisation engine. Eliminates flicker, luminance drift, and frame-to-frame inconsistency in AI-generated and traditional video footage."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "seanhan19911990-source",
|
||||||
|
"title": "ComfyUI-LTX2-Visual-LoRA [REMOVED",
|
||||||
|
"reference": "https://github.com/seanhan19911990-source/ComfyUI-LTX2-Visual-LoRA",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/seanhan19911990-source/ComfyUI-LTX2-Visual-LoRA"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A ComfyUI custom node that filters LTX-2 LoRAs to strip out audio-weight interference, ensuring clean, high-fidelity sound while maintaining visual fine-tuning."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "chrisgoringe",
|
||||||
|
"title": "Comfy Controller [REMOVED",
|
||||||
|
"id": "cg-comfycontroller",
|
||||||
|
"reference": "https://github.com/chrisgoringe/cg-controller",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/chrisgoringe/cg-controller"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Quickly and easily build a GUI on top of your workflow. Gather just the nodes that you want to see, with no spaghetti, onto controller panels, leaving your workflow untouched in the background."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Ada123-a",
|
||||||
|
"title": "ComfyUI-Kandinsky [REMOVED]",
|
||||||
|
"reference": "https://github.com/Ada123-a/ComfyUI-Kandinsky",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Ada123-a/ComfyUI-Kandinsky"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Kandinsky 2/20B ComfyUI support with FP8/GGUF/blockswap support."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "lgldlk",
|
||||||
|
"title": "ComfyUI-PSD-Replace [REMOVED]",
|
||||||
|
"reference": "https://github.com/lgldlk/ComfyUI-PSD-Replace",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/lgldlk/ComfyUI-PSD-Replace"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "One click replacement of smart objects or layers in PSD"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Rizzlord",
|
||||||
|
"title": "ComfyUI-RizzNodes [REMOVED]",
|
||||||
|
"reference": "https://github.com/Rizzlord/ComfyUI-RizzNodes",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Rizzlord/ComfyUI-RizzNodes"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "RizzNodes for ComfyUI Welcome to RizzNodes, a collection of custom nodes for ComfyUI designed to streamline various workflows, from loading images and models in batches to dynamic prompt generation and memory management."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "palant",
|
||||||
|
"title": "Image Resize for ComfyUI [REMOVED]",
|
||||||
|
"id": "image-resize",
|
||||||
|
"reference": "https://github.com/palant/image-resize-comfyui",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/palant/image-resize-comfyui"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "This custom node provides various tools for resizing images. The goal is resizing without distorting proportions, yet without having to perform any calculations with the size of the original image. If a mask is present, it is resized and modified along with the image."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "palant",
|
||||||
|
"title": "Integrated Nodes for ComfyUI [REMOVED]",
|
||||||
|
"reference": "https://github.com/palant/integrated-nodes-comfyui",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/palant/integrated-nodes-comfyui"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "This tool will turn entire workflows or parts of them into single integrated nodes. In a way, it is similar to the Node Templates functionality but hides the inner structure. This is useful if all you want is to reuse and quickly configure a bunch of nodes without caring how they are interconnected."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "zfkun",
|
||||||
|
"title": "ComfyUI_zfkun [REMOVED]",
|
||||||
|
"reference": "https://github.com/zfkun/ComfyUI_zfkun",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/zfkun/ComfyUI_zfkun"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A collection of nodes for common tools, including text preview, text translation (multi-platform, multi-language), image loader, webcamera capture."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "xXAdonesXx",
|
||||||
|
"title": "NodeGPT",
|
||||||
|
"id": "nodegpt",
|
||||||
|
"reference": "https://github.com/xXAdonesXx/NodeGPT",
|
||||||
|
"reference2": "https://github.com/antonym-git/NodeGPT",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/xXAdonesXx/NodeGPT"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Implementation of AutoGen inside ComfyUI. This repository is under development, and not everything is functioning correctly yet."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "basenc",
|
||||||
|
"title": "ComfyUI Environment Variable Node [UNSAFE/REMOVED]",
|
||||||
|
"reference": "https://github.com/basenc/Comfyui-EnvVarNode",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/basenc/Comfyui-EnvVarNode"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Exposes environment variables inside ComfyUI workflows. Use it to keep API keys and other secrets out of your graph files while still wiring them into nodes that need them.[w/This nodepack has a vulnerability that allows reading arbitrary environment variables.]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Bharanidharan",
|
||||||
|
"title": "iSeeBetter Node for ComfyUI [REMOVED]",
|
||||||
|
"reference": "https://github.com/llikethat/ComfyUI-iseebetter",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/llikethat/ComfyUI-iseebetter"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Custom Node to implement iSeeBetter upscaling method."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "uczensokratesa",
|
||||||
|
"title": "ComfyUI-Gemini-VAE-Fix [REMOVED]",
|
||||||
|
"reference": "https://github.com/uczensokratesa/ComfyUI-Gemini-VAE-Fix",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/uczensokratesa/ComfyUI-Gemini-VAE-Fix"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Advanced VAE decoder node with audio sync precision and crash-proof memory management for long videos. (Description by CC)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "uczensokratesa",
|
||||||
|
"title": "ComfyUI-UniversalSmartVAE [REMOVED]",
|
||||||
|
"reference": "https://github.com/uczensokratesa/ComfyUI-UniversalSmartVAE",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/uczensokratesa/ComfyUI-UniversalSmartVAE"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "VRAM-safe VAE decoder for video latents in ComfyUI. Decodes 5D video latents in temporal batches to prevent GPU memory explosions."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "uczensokratesa",
|
||||||
|
"title": "ComfyUI-Claude-VAE [REMOVED]",
|
||||||
|
"reference": "https://github.com/uczensokratesa/ComfyUI-Claude-VAE",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/uczensokratesa/ComfyUI-Claude-VAE"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "VAE Decode node that solves VRAM problems with mathematically precise temporal stitching, maintaining perfect frame counts across batch sizes for video generation."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "siray-ai",
|
||||||
|
"title": "siray-comfyui [REMOVED]",
|
||||||
|
"reference": "https://github.com/siray-ai/siray-comfyui",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/siray-ai/siray-comfyui"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Custom ComfyUI nodes for Siray image/video models with dynamic schema-based inputs, authentication, and video streaming. (Description by CC)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "1hew",
|
||||||
|
"title": "ComfyUI-1hewHungryStudio [REMOVED]",
|
||||||
|
"reference": "https://github.com/1hew/ComfyUI-1hewHungryStudio",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/1hew/ComfyUI-1hewHungryStudio"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Custom ComfyUI nodes for image cloning and shape drawing operations. (Description by CC)\nNOTE: The files in the repo are not organized."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Oct7",
|
||||||
|
"title": "ComfyUI-LaplaMask [REMOVED]",
|
||||||
|
"reference": "https://github.com/Oct7/ComfyUI-LaplaMask",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Oct7/ComfyUI-LaplaMask"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "NODES: Blur→Mask"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"author": "aliabougazia",
|
"author": "aliabougazia",
|
||||||
"title": "comfyui_pt_security_scanner [REMOVED]",
|
"title": "comfyui_pt_security_scanner [REMOVED]",
|
||||||
@ -313,16 +668,6 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "ComfyUI nodes for loading images and drawing polygon masks interactively on them"
|
"description": "ComfyUI nodes for loading images and drawing polygon masks interactively on them"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"author": "siray-ai",
|
|
||||||
"title": "siray-comfyui [REMOVED]",
|
|
||||||
"reference": "https://github.com/siray-ai/siray-comfyui",
|
|
||||||
"files": [
|
|
||||||
"https://github.com/siray-ai/siray-comfyui"
|
|
||||||
],
|
|
||||||
"install_type": "git-clone",
|
|
||||||
"description": "Custom ComfyUI nodes for Siray image/video models with dynamic schema-based inputs, authentication, and video streaming. (Description by CC)"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"author": "easelhub",
|
"author": "easelhub",
|
||||||
"title": "ComfyUI_EaselHub_Nodes [REMOVED]",
|
"title": "ComfyUI_EaselHub_Nodes [REMOVED]",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
217
openapi.yaml
217
openapi.yaml
@ -191,11 +191,20 @@ paths:
|
|||||||
description: Mapping of node packages to node classes
|
description: Mapping of node packages to node classes
|
||||||
|
|
||||||
/customnode/fetch_updates:
|
/customnode/fetch_updates:
|
||||||
get:
|
post:
|
||||||
summary: Check for updates
|
summary: Check for updates
|
||||||
description: Fetches updates for custom nodes
|
description: Fetches updates for custom nodes
|
||||||
parameters:
|
requestBody:
|
||||||
- $ref: '#/components/parameters/modeParam'
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
mode:
|
||||||
|
type: string
|
||||||
|
enum: [local, remote, default]
|
||||||
|
description: Source mode (e.g., "local", "remote")
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: No updates available
|
description: No updates available
|
||||||
@ -423,13 +432,22 @@ paths:
|
|||||||
|
|
||||||
# Queue Management Endpoints
|
# Queue Management Endpoints
|
||||||
/manager/queue/update_all:
|
/manager/queue/update_all:
|
||||||
get:
|
post:
|
||||||
summary: Update all custom nodes
|
summary: Update all custom nodes
|
||||||
description: Queues update operations for all installed custom nodes
|
description: Queues update operations for all installed custom nodes
|
||||||
security:
|
security:
|
||||||
- securityLevel: []
|
- securityLevel: []
|
||||||
parameters:
|
requestBody:
|
||||||
- $ref: '#/components/parameters/modeParam'
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
mode:
|
||||||
|
type: string
|
||||||
|
enum: [local, remote, default]
|
||||||
|
description: Source mode (e.g., "local", "remote")
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Update queued successfully
|
description: Update queued successfully
|
||||||
@ -439,7 +457,7 @@ paths:
|
|||||||
description: Security policy violation
|
description: Security policy violation
|
||||||
|
|
||||||
/manager/queue/reset:
|
/manager/queue/reset:
|
||||||
get:
|
post:
|
||||||
summary: Reset queue
|
summary: Reset queue
|
||||||
description: Resets the operation queue
|
description: Resets the operation queue
|
||||||
responses:
|
responses:
|
||||||
@ -479,7 +497,7 @@ paths:
|
|||||||
description: Target node not found or security issue
|
description: Target node not found or security issue
|
||||||
|
|
||||||
/manager/queue/start:
|
/manager/queue/start:
|
||||||
get:
|
post:
|
||||||
summary: Start queue processing
|
summary: Start queue processing
|
||||||
description: Starts processing the operation queue
|
description: Starts processing the operation queue
|
||||||
responses:
|
responses:
|
||||||
@ -575,7 +593,7 @@ paths:
|
|||||||
description: Disable operation queued successfully
|
description: Disable operation queued successfully
|
||||||
|
|
||||||
/manager/queue/update_comfyui:
|
/manager/queue/update_comfyui:
|
||||||
get:
|
post:
|
||||||
summary: Update ComfyUI
|
summary: Update ComfyUI
|
||||||
description: Queues an update operation for ComfyUI itself
|
description: Queues an update operation for ComfyUI itself
|
||||||
responses:
|
responses:
|
||||||
@ -621,13 +639,22 @@ paths:
|
|||||||
$ref: '#/components/schemas/SnapshotItem'
|
$ref: '#/components/schemas/SnapshotItem'
|
||||||
|
|
||||||
/snapshot/remove:
|
/snapshot/remove:
|
||||||
get:
|
post:
|
||||||
summary: Remove snapshot
|
summary: Remove snapshot
|
||||||
description: Removes a specified snapshot
|
description: Removes a specified snapshot
|
||||||
security:
|
security:
|
||||||
- securityLevel: []
|
- securityLevel: []
|
||||||
parameters:
|
requestBody:
|
||||||
- $ref: '#/components/parameters/targetParam'
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [target]
|
||||||
|
properties:
|
||||||
|
target:
|
||||||
|
type: string
|
||||||
|
description: Target identifier
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Snapshot removed successfully
|
description: Snapshot removed successfully
|
||||||
@ -637,13 +664,22 @@ paths:
|
|||||||
description: Security policy violation
|
description: Security policy violation
|
||||||
|
|
||||||
/snapshot/restore:
|
/snapshot/restore:
|
||||||
get:
|
post:
|
||||||
summary: Restore snapshot
|
summary: Restore snapshot
|
||||||
description: Restores a specified snapshot
|
description: Restores a specified snapshot
|
||||||
security:
|
security:
|
||||||
- securityLevel: []
|
- securityLevel: []
|
||||||
parameters:
|
requestBody:
|
||||||
- $ref: '#/components/parameters/targetParam'
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [target]
|
||||||
|
properties:
|
||||||
|
target:
|
||||||
|
type: string
|
||||||
|
description: Target identifier
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Snapshot restoration scheduled
|
description: Snapshot restoration scheduled
|
||||||
@ -667,7 +703,7 @@ paths:
|
|||||||
description: Error creating snapshot
|
description: Error creating snapshot
|
||||||
|
|
||||||
/snapshot/save:
|
/snapshot/save:
|
||||||
get:
|
post:
|
||||||
summary: Save snapshot
|
summary: Save snapshot
|
||||||
description: Saves the current system state as a new snapshot
|
description: Saves the current system state as a new snapshot
|
||||||
responses:
|
responses:
|
||||||
@ -699,15 +735,19 @@ paths:
|
|||||||
description: Error retrieving versions
|
description: Error retrieving versions
|
||||||
|
|
||||||
/comfyui_manager/comfyui_switch_version:
|
/comfyui_manager/comfyui_switch_version:
|
||||||
get:
|
post:
|
||||||
summary: Switch ComfyUI version
|
summary: Switch ComfyUI version
|
||||||
description: Switches to a specified ComfyUI version
|
description: Switches to a specified ComfyUI version
|
||||||
parameters:
|
requestBody:
|
||||||
- name: ver
|
required: false
|
||||||
in: query
|
content:
|
||||||
description: Target version
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: object
|
||||||
|
properties:
|
||||||
|
ver:
|
||||||
|
type: string
|
||||||
|
description: Target version
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Version switch successful
|
description: Version switch successful
|
||||||
@ -715,7 +755,7 @@ paths:
|
|||||||
description: Error switching version
|
description: Error switching version
|
||||||
|
|
||||||
/manager/reboot:
|
/manager/reboot:
|
||||||
get:
|
post:
|
||||||
summary: Reboot ComfyUI
|
summary: Reboot ComfyUI
|
||||||
description: Restarts the ComfyUI server
|
description: Restarts the ComfyUI server
|
||||||
security:
|
security:
|
||||||
@ -746,7 +786,32 @@ paths:
|
|||||||
text/plain:
|
text/plain:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
post:
|
||||||
|
summary: Set preview method
|
||||||
|
description: Sets the latent preview method (write-only; use GET to read)
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [value]
|
||||||
|
properties:
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
enum: [auto, latent2rgb, taesd, none]
|
||||||
|
description: New preview method
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Setting updated
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'400':
|
||||||
|
description: Invalid value
|
||||||
|
|
||||||
|
|
||||||
/manager/db_mode:
|
/manager/db_mode:
|
||||||
get:
|
get:
|
||||||
summary: Get or set database mode
|
summary: Get or set database mode
|
||||||
@ -766,7 +831,32 @@ paths:
|
|||||||
text/plain:
|
text/plain:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
post:
|
||||||
|
summary: Set database mode
|
||||||
|
description: Sets the database mode (write-only; use GET to read)
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [value]
|
||||||
|
properties:
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
enum: [channel, local, remote]
|
||||||
|
description: New database mode
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Setting updated
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'400':
|
||||||
|
description: Invalid value
|
||||||
|
|
||||||
|
|
||||||
/manager/policy/component:
|
/manager/policy/component:
|
||||||
get:
|
get:
|
||||||
summary: Get or set component policy
|
summary: Get or set component policy
|
||||||
@ -785,7 +875,29 @@ paths:
|
|||||||
text/plain:
|
text/plain:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
post:
|
||||||
|
summary: Set component policy
|
||||||
|
description: Sets the component policy (write-only; use GET to read)
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [value]
|
||||||
|
properties:
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
description: New component policy
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Setting updated
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
|
||||||
/manager/policy/update:
|
/manager/policy/update:
|
||||||
get:
|
get:
|
||||||
summary: Get or set update policy
|
summary: Get or set update policy
|
||||||
@ -805,7 +917,32 @@ paths:
|
|||||||
text/plain:
|
text/plain:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
post:
|
||||||
|
summary: Set update policy
|
||||||
|
description: Sets the update policy (write-only; use GET to read)
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [value]
|
||||||
|
properties:
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
enum: [stable, nightly, nightly-comfyui]
|
||||||
|
description: New update policy
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Setting updated
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'400':
|
||||||
|
description: Invalid value
|
||||||
|
|
||||||
|
|
||||||
/manager/channel_url_list:
|
/manager/channel_url_list:
|
||||||
get:
|
get:
|
||||||
summary: Get or set channel URL
|
summary: Get or set channel URL
|
||||||
@ -836,7 +973,29 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
|
post:
|
||||||
|
summary: Set channel URL
|
||||||
|
description: Sets the channel URL for custom node sources (write-only; use GET to read current + list)
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required: [value]
|
||||||
|
properties:
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
description: New channel name
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Setting updated
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
|
||||||
# Component Management Endpoints
|
# Component Management Endpoints
|
||||||
/manager/component/save:
|
/manager/component/save:
|
||||||
post:
|
post:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-manager"
|
name = "comfyui-manager"
|
||||||
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
||||||
version = "3.39.2"
|
version = "3.40"
|
||||||
license = { file = "LICENSE.txt" }
|
license = { file = "LICENSE.txt" }
|
||||||
dependencies = ["GitPython", "PyGithub", "matrix-nio", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
dependencies = ["GitPython", "PyGithub", "matrix-nio", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
||||||
|
|
||||||
|
|||||||
121
tests/test_csrf_content_type_helper.py
Normal file
121
tests/test_csrf_content_type_helper.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
"""AC verification for wi-001 (B2): Content-Type rejection helper.
|
||||||
|
|
||||||
|
Validates _reject_simple_form_content_type against the 5-item curl matrix
|
||||||
|
from the WI's acceptance criteria using aiohttp test client. The test
|
||||||
|
builds a minimal aiohttp app that mirrors the helper's wiring into a
|
||||||
|
no-body POST handler, so we exercise the real request.content_type
|
||||||
|
parsing path rather than a mock.
|
||||||
|
|
||||||
|
AC matrix:
|
||||||
|
form-url → 400
|
||||||
|
multipart → 400
|
||||||
|
text/plain → 400
|
||||||
|
no-CT → 200
|
||||||
|
application/json → 200
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
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
|
||||||
|
# Python's stdlib `glob` module and break pytest collection.
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def _load_helper():
|
||||||
|
"""Parse manager_server.py and execute only the helper definition."""
|
||||||
|
import ast
|
||||||
|
|
||||||
|
source = (REPO_ROOT / "glob" / "manager_server.py").read_text()
|
||||||
|
tree = ast.parse(source)
|
||||||
|
wanted = {"_SIMPLE_FORM_CONTENT_TYPES", "_reject_simple_form_content_type"}
|
||||||
|
nodes = []
|
||||||
|
for node in tree.body:
|
||||||
|
if isinstance(node, ast.Assign):
|
||||||
|
for target in node.targets:
|
||||||
|
if isinstance(target, ast.Name) and target.id in wanted:
|
||||||
|
nodes.append(node)
|
||||||
|
elif isinstance(node, ast.FunctionDef) and node.name in wanted:
|
||||||
|
nodes.append(node)
|
||||||
|
module = ast.Module(body=nodes, type_ignores=[])
|
||||||
|
ns = {"web": web, "frozenset": frozenset}
|
||||||
|
exec(compile(module, "manager_server_helpers", "exec"), ns)
|
||||||
|
return ns["_reject_simple_form_content_type"]
|
||||||
|
|
||||||
|
|
||||||
|
_reject_simple_form_content_type = _load_helper()
|
||||||
|
|
||||||
|
|
||||||
|
async def _handler(request):
|
||||||
|
resp = _reject_simple_form_content_type(request)
|
||||||
|
if resp is not None:
|
||||||
|
return resp
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
class ContentTypeRejectionTest(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(cls.loop)
|
||||||
|
app = web.Application()
|
||||||
|
app.router.add_post("/noop", _handler)
|
||||||
|
cls.server = TestServer(app, loop=cls.loop)
|
||||||
|
cls.client = TestClient(cls.server, loop=cls.loop)
|
||||||
|
cls.loop.run_until_complete(cls.client.start_server())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
cls.loop.run_until_complete(cls.client.close())
|
||||||
|
cls.loop.close()
|
||||||
|
|
||||||
|
def _post(self, headers):
|
||||||
|
async def go():
|
||||||
|
return await self.client.post("/noop", headers=headers, data=b"")
|
||||||
|
|
||||||
|
return self.loop.run_until_complete(go())
|
||||||
|
|
||||||
|
def test_form_urlencoded_rejected(self):
|
||||||
|
r = self._post({"Content-Type": "application/x-www-form-urlencoded"})
|
||||||
|
self.assertEqual(r.status, 400)
|
||||||
|
|
||||||
|
def test_multipart_form_data_rejected(self):
|
||||||
|
# aiohttp requires a boundary for multipart; helper should still reject
|
||||||
|
# based on the primary mimetype.
|
||||||
|
r = self._post({"Content-Type": "multipart/form-data; boundary=xyz"})
|
||||||
|
self.assertEqual(r.status, 400)
|
||||||
|
|
||||||
|
def test_text_plain_rejected(self):
|
||||||
|
r = self._post({"Content-Type": "text/plain"})
|
||||||
|
self.assertEqual(r.status, 400)
|
||||||
|
|
||||||
|
def test_no_content_type_allowed(self):
|
||||||
|
# Explicitly strip Content-Type: aiohttp client may add a default,
|
||||||
|
# so we use a raw request to ensure absence is tested.
|
||||||
|
async def go():
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(
|
||||||
|
self.client.make_url("/noop"),
|
||||||
|
data=None,
|
||||||
|
skip_auto_headers=["Content-Type"],
|
||||||
|
) as resp:
|
||||||
|
return resp.status
|
||||||
|
|
||||||
|
status = self.loop.run_until_complete(go())
|
||||||
|
self.assertEqual(status, 200)
|
||||||
|
|
||||||
|
def test_application_json_allowed(self):
|
||||||
|
r = self._post({"Content-Type": "application/json"})
|
||||||
|
self.assertEqual(r.status, 200)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main(verbosity=2)
|
||||||
Loading…
Reference in New Issue
Block a user