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

@ -215,13 +215,14 @@ The following settings are applied based on the section marked as `is_default`.
downgrade_blacklist = <Set a list of packages to prevent downgrades. List them separated by commas.>
security_level = <Set the security level => strong|normal|normal-|weak>
always_lazy_install = <Whether to perform dependency installation on restart even in environments other than Windows.>
network_mode = <Set the network mode => public|private|offline>
network_mode = <Set the network mode => public|private|offline|personal_cloud>
```
* network_mode:
- public: An environment that uses a typical public network.
- private: An environment that uses a closed network, where a private node DB is configured via `channel_url`. (Uses cache if available)
- offline: An environment that does not use any external connections when using an offline network. (Uses cache if available)
- personal_cloud: Applies relaxed security features in cloud environments such as Google Colab or Runpod, where strong security is not required.
## Additional Feature
@ -312,31 +313,33 @@ When you run the `scan.sh` script:
## Security policy
* Edit `config.ini` file: add `security_level = <LEVEL>`
* `strong`
* doesn't allow `high` and `middle` level risky feature
* `normal`
* doesn't allow `high` level risky feature
* `middle` level risky feature is available
* `normal-`
* doesn't allow `high` level risky feature if `--listen` is specified and not starts with `127.`
* `middle` level risky feature is available
* `weak`
* all feature is available
* `high` level risky features
* `Install via git url`, `pip install`
* Installation of custom nodes registered not in the `default channel`.
* Fix custom nodes
* `middle` level risky features
* Uninstall/Update
* Installation of custom nodes registered in the `default channel`.
* Restore/Remove Snapshot
* Restart
* `low` level risky features
* Update ComfyUI
The security settings are applied based on whether the ComfyUI server's listener is non-local and whether the network mode is set to `personal_cloud`.
* **non-local**: When the server is launched with `--listen` and is bound to a network range other than the local `127.` range, allowing remote IP access.
* **personal\_cloud**: When the `network_mode` is set to `personal_cloud`.
### Risky Level Table
| Risky Level | features |
|-------------|---------------------------------------------------------------------------------------------------------------------------------------|
| high+ | * `Install via git url`, `pip install`<BR>* Installation of nodepack registered not in the `default channel`. |
| high | * Fix nodepack |
| middle+ | * Uninstall/Update<BR>* Installation of nodepack registered in the `default channel`.<BR>* Restore/Remove Snapshot<BR>* Install model |
| middle | * Restart |
| low | * Update ComfyUI |
### Security Level Table
| Security Level | local | non-local (personal_cloud) | non-local (not personal_cloud) |
|----------------|--------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|--------------------------------|
| strong | * Only `weak` level risky features are allowed | * Only `weak` level risky features are allowed | * Only `weak` level risky features are allowed |
| normal | * `high+` and `high` level risky features are not allowed<BR>* `middle+` and `middle` level risky features are available | * `high+` and `high` level risky features are not allowed<BR>* `middle+` and `middle` level risky features are available | * `high+`, `high` and `middle+` level risky features are not allowed<BR>* `middle` level risky features are available
| normal- | * All features are available | * `high+` and `high` level risky features are not allowed<BR>* `middle+` and `middle` level risky features are available | * `high+`, `high` and `middle+` level risky features are not allowed<BR>* `middle` level risky features are available
| weak | * All features are available | * All features are available | * `high+` and `middle+` level risky features are not allowed<BR>* `high`, `middle` and `low` level risky features are available
# Disclaimer

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:

View File

@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "comfyui-manager"
license = { text = "GPL-3.0-only" }
version = "4.0.0-beta.5"
version = "4.0.0-beta.6"
requires-python = ">= 3.9"
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
readme = "README.md"