modified: security policy

- Strengthened the default security policy
- Subdivided the risky levels high and middle into high+, high, middle+, and middle
- Added support for personal_cloud network mode
- Updated README.md

fixed: invalid security message
fixed: legacy - crash when security policy violation occurred

modified: default 'use_uv' is now True
This commit is contained in:
Dr.Lt.Data
2025-06-27 01:38:38 +09:00
parent 1de6f859bf
commit 002e549a86
10 changed files with 127 additions and 75 deletions

View File

@@ -4,6 +4,7 @@ class NetworkMode(enum.Enum):
PUBLIC = "public"
PRIVATE = "private"
OFFLINE = "offline"
PERSONAL_CLOUD = "personal_cloud"
class SecurityLevel(enum.Enum):
STRONG = "strong"

View File

@@ -109,7 +109,9 @@ class SecurityLevel(str, Enum):
class RiskLevel(str, Enum):
block = "block"
high_p = "high+"
high = "high"
middle_p = "middle+"
middle = "middle"

View File

@@ -1,5 +1,6 @@
SECURITY_MESSAGE_MIDDLE_OR_BELOW = "ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_MIDDLE = "ERROR: To use this action, a security_level of `normal or below` is required. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_MIDDLE_P = "ERROR: To use this action, security_level must be `normal or below`, and network_mode must be set to `personal_cloud`. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_NORMAL_MINUS = "ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_GENERAL = "ERROR: This installation is not allowed in this security_level. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_NORMAL_MINUS_MODEL = "ERROR: Downloading models that are not in '.safetensors' format is only allowed for models registered in the 'default' channel at this security level. If you want to download this model, set the security level to 'normal-' or lower."

View File

@@ -1635,7 +1635,7 @@ def read_config():
'http_channel_enabled': get_bool('http_channel_enabled', False),
'preview_method': default_conf.get('preview_method', manager_funcs.get_current_preview_method()).lower(),
'git_exe': default_conf.get('git_exe', ''),
'use_uv': get_bool('use_uv', False),
'use_uv': get_bool('use_uv', True),
'channel_url': default_conf.get('channel_url', DEFAULT_CHANNEL),
'default_cache_as_channel_url': get_bool('default_cache_as_channel_url', False),
'share_option': default_conf.get('share_option', 'all').lower(),
@@ -1658,7 +1658,7 @@ def read_config():
'http_channel_enabled': False,
'preview_method': manager_funcs.get_current_preview_method(),
'git_exe': '',
'use_uv': False,
'use_uv': True,
'channel_url': DEFAULT_CHANNEL,
'default_cache_as_channel_url': False,
'share_option': 'all',

View File

@@ -82,7 +82,8 @@ from ..data_models import (
from .constants import (
model_dir_name_map,
SECURITY_MESSAGE_MIDDLE_OR_BELOW,
SECURITY_MESSAGE_MIDDLE,
SECURITY_MESSAGE_MIDDLE_P,
)
if not manager_util.is_manager_pip_package():
@@ -829,6 +830,10 @@ async def task_worker():
await core.unified_manager.reload(ManagerDatabaseSource.cache.value)
async def do_install(params: InstallPackParams) -> str:
if not security_utils.is_allowed_security_level('middle+'):
logging.error(SECURITY_MESSAGE_MIDDLE_P)
return OperationResult.failed.value
node_id = params.id
node_version = params.selected_version
channel = params.channel
@@ -887,7 +892,7 @@ async def task_worker():
core.unified_manager.unified_enable(cnr_id)
return OperationResult.success.value
async def do_update(params: UpdatePackParams) -> str:
async def do_update(params: UpdatePackParams) -> dict[str, str]:
node_name = params.node_name
node_ver = params.node_ver
@@ -977,6 +982,10 @@ async def task_worker():
return "An error occurred while updating 'comfyui'."
async def do_fix(params: FixPackParams) -> str:
if not security_utils.is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE)
return OperationResult.failed.value
node_name = params.node_name
node_ver = params.node_ver
@@ -997,6 +1006,10 @@ async def task_worker():
return f"An error occurred while fixing '{node_name}@{node_ver}'."
async def do_uninstall(params: UninstallPackParams) -> str:
if not security_utils.is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE)
return OperationResult.failed.value
node_name = params.node_name
is_unknown = params.is_unknown
@@ -1041,6 +1054,10 @@ async def task_worker():
return f"Failed to disable: '{node_name}'"
async def do_install_model(params: ModelMetadata) -> str:
if not security_utils.is_allowed_security_level('middle+'):
logging.error(SECURITY_MESSAGE_MIDDLE_P)
return OperationResult.failed.value
json_data = params.model_dump()
model_path = model_utils.get_model_path(json_data)
@@ -1099,7 +1116,7 @@ async def task_worker():
return OperationResult.success.value
except Exception as e:
logging.error(f"[ComfyUI-Manager] ERROR: {e}", file=sys.stderr)
logging.error(f"[ComfyUI-Manager] ERROR: {e}")
return f"Model installation error: {model_url}"
@@ -1413,8 +1430,8 @@ async def update_all(request: web.Request) -> web.Response:
async def _update_all(params: UpdateAllQueryParams) -> web.Response:
if not security_utils.is_allowed_security_level("middle"):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
if not security_utils.is_allowed_security_level("middle+"):
logging.error(SECURITY_MESSAGE_MIDDLE_P)
return web.Response(status=403)
# Extract client info from validated params
@@ -1513,7 +1530,7 @@ async def get_snapshot_list(request):
@routes.get("/v2/snapshot/remove")
async def remove_snapshot(request):
if not security_utils.is_allowed_security_level("middle"):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
logging.error(SECURITY_MESSAGE_MIDDLE)
return web.Response(status=403)
try:
@@ -1530,8 +1547,8 @@ async def remove_snapshot(request):
@routes.get("/v2/snapshot/restore")
async def restore_snapshot(request):
if not security_utils.is_allowed_security_level("middle"):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
if not security_utils.is_allowed_security_level("middle+"):
logging.error(SECURITY_MESSAGE_MIDDLE_P)
return web.Response(status=403)
try:
@@ -1597,7 +1614,7 @@ def unzip_install(files):
os.remove(temp_filename)
except Exception as e:
logging.error(f"Install(unzip) error: {url} / {e}", file=sys.stderr)
logging.error(f"Install(unzip) error: {url} / {e}")
return False
logging.info("Installation was successful.")
@@ -1755,7 +1772,7 @@ async def comfyui_versions(request):
content_type="application/json",
)
except Exception as e:
logging.error(f"ComfyUI update fail: {e}", file=sys.stderr)
logging.error(f"ComfyUI update fail: {e}")
return web.Response(status=400)
@@ -1787,7 +1804,7 @@ async def comfyui_switch_version(request):
{"error": "Validation error", "details": e.errors()}, status=400
)
except Exception as e:
logging.error(f"ComfyUI version switch fail: {e}", file=sys.stderr)
logging.error(f"ComfyUI version switch fail: {e}")
return web.Response(status=400)
@@ -1871,7 +1888,7 @@ async def channel_url_list(request):
@routes.get("/v2/manager/reboot")
def restart(self):
if not security_utils.is_allowed_security_level("middle"):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
logging.error(SECURITY_MESSAGE_MIDDLE)
return web.Response(status=403)
try:

View File

@@ -13,16 +13,29 @@ def is_loopback(address):
def is_allowed_security_level(level):
is_local_mode = is_loopback(args.listen)
is_personal_cloud = core.get_config()['network_mode'].lower() == 'personal_cloud'
if level == RiskLevel.block.value:
return False
elif level == RiskLevel.high_p.value:
if is_local_mode:
return core.get_config()['security_level'] in [SecurityLevel.weak.value, SecurityLevel.normal_.value]
elif is_personal_cloud:
return core.get_config()['security_level'] == SecurityLevel.weak.value
else:
return False
elif level == RiskLevel.high.value:
if is_local_mode:
return core.get_config()["security_level"] in [SecurityLevel.weak.value, SecurityLevel.normal_.value]
return core.get_config()['security_level'] in [SecurityLevel.weak.value, SecurityLevel.normal_.value]
else:
return core.get_config()["security_level"] == SecurityLevel.weak.value
return core.get_config()['security_level'] == SecurityLevel.weak.value
elif level == RiskLevel.middle_p.value:
if is_local_mode or is_personal_cloud:
return core.get_config()['security_level'] in [SecurityLevel.weak.value, SecurityLevel.normal.value, SecurityLevel.normal_.value]
else:
return False
elif level == RiskLevel.middle.value:
return core.get_config()["security_level"] in [SecurityLevel.weak.value, SecurityLevel.normal.value, SecurityLevel.normal_.value]
return core.get_config()['security_level'] in [SecurityLevel.weak.value, SecurityLevel.normal.value, SecurityLevel.normal_.value]
else:
return True
@@ -41,7 +54,7 @@ async def get_risky_level(files, pip_packages):
for x in files:
if x not in all_urls:
return RiskLevel.high.value
return RiskLevel.high_p.value
all_pip_packages = set()
for x in json_data1["custom_nodes"] + json_data2["custom_nodes"]:
@@ -51,4 +64,4 @@ async def get_risky_level(files, pip_packages):
if p not in all_pip_packages:
return RiskLevel.block.value
return RiskLevel.middle.value
return RiskLevel.middle_p.value

View File

@@ -1634,7 +1634,7 @@ def read_config():
'http_channel_enabled': get_bool('http_channel_enabled', False),
'preview_method': default_conf.get('preview_method', manager_funcs.get_current_preview_method()).lower(),
'git_exe': default_conf.get('git_exe', ''),
'use_uv': get_bool('use_uv', False),
'use_uv': get_bool('use_uv', True),
'channel_url': default_conf.get('channel_url', DEFAULT_CHANNEL),
'default_cache_as_channel_url': get_bool('default_cache_as_channel_url', False),
'share_option': default_conf.get('share_option', 'all').lower(),
@@ -1657,7 +1657,7 @@ def read_config():
'http_channel_enabled': False,
'preview_method': manager_funcs.get_current_preview_method(),
'git_exe': '',
'use_uv': False,
'use_uv': True,
'channel_url': DEFAULT_CHANNEL,
'default_cache_as_channel_url': False,
'share_option': 'all',

View File

@@ -36,7 +36,8 @@ logging.info("[ComfyUI-Manager] network_mode: " + network_mode_description)
comfy_ui_hash = "-"
comfyui_tag = None
SECURITY_MESSAGE_MIDDLE_OR_BELOW = "ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.\nReference: https://github.com/Comfy-Org/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_MIDDLE = "ERROR: To use this action, a security_level of `normal or below` is required. Please contact the administrator.\nReference: https://github.com/Comfy-Org/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_MIDDLE_P = "ERROR: To use this action, security_level must be `normal or below`, and network_mode must be set to `personal_cloud`. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_NORMAL_MINUS = "ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.\nReference: https://github.com/Comfy-Org/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_GENERAL = "ERROR: This installation is not allowed in this security_level. Please contact the administrator.\nReference: https://github.com/Comfy-Org/ComfyUI-Manager#security-policy"
SECURITY_MESSAGE_NORMAL_MINUS_MODEL = "ERROR: Downloading models that are not in '.safetensors' format is only allowed for models registered in the 'default' channel at this security level. If you want to download this model, set the security level to 'normal-' or lower."
@@ -93,13 +94,27 @@ model_dir_name_map = {
def is_allowed_security_level(level):
is_personal_cloud = core.get_config()['network_mode'].lower() == 'personal_cloud'
if level == 'block':
return False
elif level == 'high+':
if is_local_mode:
return core.get_config()['security_level'] in ['weak', 'normal-']
elif is_personal_cloud:
return core.get_config()['security_level'] == 'weak'
else:
return False
elif level == 'high':
if is_local_mode:
return core.get_config()['security_level'] in ['weak', 'normal-']
else:
return core.get_config()['security_level'] == 'weak'
elif level == 'middle+':
if is_local_mode or is_personal_cloud:
return core.get_config()['security_level'] in ['weak', 'normal', 'normal-']
else:
return False
elif level == 'middle':
return core.get_config()['security_level'] in ['weak', 'normal', 'normal-']
else:
@@ -116,7 +131,7 @@ async def get_risky_level(files, pip_packages):
for x in files:
if x not in all_urls:
return "high"
return "high+"
all_pip_packages = set()
for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:
@@ -126,7 +141,7 @@ async def get_risky_level(files, pip_packages):
if p not in all_pip_packages:
return "block"
return "middle"
return "middle+"
class ManagerFuncsInComfyUI(core.ManagerFuncs):
@@ -758,29 +773,29 @@ async def queue_batch(request):
for x in v:
res = await _uninstall_custom_node(x)
if res.status != 200:
failed.add(x[0])
failed.add(x['id'])
else:
res = await _install_custom_node(x)
if res.status != 200:
failed.add(x[0])
failed.add(x['id'])
elif k == 'install':
for x in v:
res = await _install_custom_node(x)
if res.status != 200:
failed.add(x[0])
failed.add(x['id'])
elif k == 'uninstall':
for x in v:
res = await _uninstall_custom_node(x)
if res.status != 200:
failed.add(x[0])
failed.add(x['id'])
elif k == 'update':
for x in v:
res = await _update_custom_node(x)
if res.status != 200:
failed.add(x[0])
failed.add(x['id'])
elif k == 'update_comfyui':
await update_comfyui(None)
@@ -793,13 +808,13 @@ async def queue_batch(request):
for x in v:
res = await _install_model(x)
if res.status != 200:
failed.add(x[0])
failed.add(x['id'])
elif k == 'fix':
for x in v:
res = await _fix_custom_node(x)
if res.status != 200:
failed.add(x[0])
failed.add(x['id'])
with task_worker_lock:
finalize_temp_queue_batch(json_data, failed)
@@ -910,8 +925,8 @@ async def update_all(request):
async def _update_all(json_data):
if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
if not is_allowed_security_level('middle+'):
logging.error(SECURITY_MESSAGE_MIDDLE_P)
return web.Response(status=403)
with task_worker_lock:
@@ -1162,7 +1177,7 @@ async def get_snapshot_list(request):
@routes.get("/v2/snapshot/remove")
async def remove_snapshot(request):
if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
logging.error(SECURITY_MESSAGE_MIDDLE)
return web.Response(status=403)
try:
@@ -1179,8 +1194,8 @@ async def remove_snapshot(request):
@routes.get("/v2/snapshot/restore")
async def restore_snapshot(request):
if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
if not is_allowed_security_level('middle+'):
logging.error(SECURITY_MESSAGE_MIDDLE_P)
return web.Response(status=403)
try:
@@ -1356,8 +1371,8 @@ async def install_custom_node(request):
async def _install_custom_node(json_data):
if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
if not is_allowed_security_level('middle+'):
logging.error(SECURITY_MESSAGE_MIDDLE_P)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
# non-nightly cnr is safe
@@ -1462,7 +1477,7 @@ async def _fix_custom_node(json_data):
@routes.post("/v2/customnode/install/git_url")
async def install_custom_node_git_url(request):
if not is_allowed_security_level('high'):
if not is_allowed_security_level('high+'):
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
return web.Response(status=403)
@@ -1482,7 +1497,7 @@ async def install_custom_node_git_url(request):
@routes.post("/v2/customnode/install/pip")
async def install_custom_node_pip(request):
if not is_allowed_security_level('high'):
if not is_allowed_security_level('high+'):
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
return web.Response(status=403)
@@ -1500,7 +1515,7 @@ async def uninstall_custom_node(request):
async def _uninstall_custom_node(json_data):
if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
logging.error(SECURITY_MESSAGE_MIDDLE)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
node_id = json_data.get('id')
@@ -1526,7 +1541,7 @@ async def update_custom_node(request):
async def _update_custom_node(json_data):
if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
logging.error(SECURITY_MESSAGE_MIDDLE)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
node_id = json_data.get('id')
@@ -1617,8 +1632,8 @@ async def install_model(request):
async def _install_model(json_data):
if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
if not is_allowed_security_level('middle+'):
logging.error(SECURITY_MESSAGE_MIDDLE_P)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
# validate request
@@ -1626,7 +1641,7 @@ async def _install_model(json_data):
logging.error(f"[ComfyUI-Manager] Invalid model install request is detected: {json_data}")
return web.Response(status=400, text="Invalid model install request is detected")
if not json_data['filename'].endswith('.safetensors') and not is_allowed_security_level('high'):
if not json_data['filename'].endswith('.safetensors') and not is_allowed_security_level('high+'):
models_json = await core.get_data_by_mode('cache', 'model-list.json', 'default')
is_belongs_to_whitelist = False
@@ -1783,7 +1798,7 @@ async def get_notice_legacy(request):
@routes.get("/v2/manager/reboot")
def restart(self):
if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
logging.error(SECURITY_MESSAGE_MIDDLE)
return web.Response(status=403)
try: