mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-07-27 02:27:30 +08:00
feat(security): dedicated install flags decouple git_url/pip from security_level
Add two boolean config.ini [default] flags — allow_git_url_install and allow_pip_install (both default false) — that fully REPLACE the security_level term on the legacy install surfaces: - POST /v2/customnode/install/git_url (S-A) and POST /v2/customnode/install/pip (S-B) are now gated solely by their dedicated flag AND the retained network-position invariant (loopback listener OR network_mode=personal_cloud). security_level no longer affects these two surfaces in either direction. - The batch unknown-URL branch (S-C) routes through the same predicate; the unknown-pip branch stays unconditionally blocked; the general middle+ batch entry gate is unchanged. - New pure predicate is_dedicated_install_allowed() in common/manager_security.py (config-import-free; callers pass values from their own reader). Both config readers (glob + legacy) register the keys in read/write/fallback paths. - Denial logs and frontend copy name the responsible flag instead of the misleading security_level guidance. Public listeners remain denied regardless of the flags (no exposure widening). - README security policy updated: config keys documented, git-url/pip removed from the security_level risky table, and a dedicated-flags subsection (REPLACE semantics, network rule, batch behavior, restart-only activation, weak/normal- opt-in migration note). - Migration: existing weak/normal- users must opt in via the new flags (CHANGELOG note; deliberate no auto-seed). Includes the unit/config/guard test suites (88 tests): predicate truth table, dual-reader config contract (missing/malformed keys read false, round-trip, cache staleness), security_level-matrix freeze guards, and suite-order-independent test stubs.
This commit is contained in:
@@ -83,6 +83,25 @@ def is_loopback(address):
|
||||
return False
|
||||
|
||||
|
||||
def is_dedicated_install_allowed(flag_value: bool, listen_address: str, network_mode: str) -> bool:
|
||||
"""P-direct predicate for the dedicated install flags (goal265-spec.md §1.2).
|
||||
|
||||
allowed iff flag AND (loopback listener OR network_mode == 'personal_cloud').
|
||||
|
||||
Gates the git-URL / standalone-pip install surfaces via the dedicated
|
||||
``allow_git_url_install`` / ``allow_pip_install`` config flags, fully
|
||||
decoupled from ``security_level`` (REPLACE, not AND — spec §1.1 inv. 1).
|
||||
The network-position term retains today's invariant that a public
|
||||
(non-loopback, non-personal_cloud) listener stays denied regardless of
|
||||
the flags (spec §1.1 inv. 2).
|
||||
|
||||
Pure function — NO config access; callers resolve ``flag_value`` and
|
||||
``network_mode`` through their own config reader and pass values in
|
||||
(preserves common/ layering: this module must stay config-import-free).
|
||||
"""
|
||||
return bool(flag_value) and (is_loopback(listen_address) or network_mode.lower() == 'personal_cloud')
|
||||
|
||||
|
||||
def do_nothing():
|
||||
pass
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ SECURITY_MESSAGE_HIGH_P = "ERROR: To use this action, '--listen' must be set to
|
||||
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."
|
||||
SECURITY_MESSAGE_FLAG_GIT_URL = "ERROR: This action requires 'allow_git_url_install = true' in config.ini ([default] section). This setting is independent of security_level. Please contact the administrator.\nReference: https://github.com/Comfy-Org/ComfyUI-Manager#security-policy"
|
||||
SECURITY_MESSAGE_FLAG_PIP = "ERROR: This action requires 'allow_pip_install = true' in config.ini ([default] section). This setting is independent of security_level. Please contact the administrator.\nReference: https://github.com/Comfy-Org/ComfyUI-Manager#security-policy"
|
||||
|
||||
|
||||
def is_loopback(address):
|
||||
|
||||
@@ -1706,6 +1706,8 @@ def write_config():
|
||||
'network_mode': get_config()['network_mode'],
|
||||
'db_mode': get_config()['db_mode'],
|
||||
'verbose': get_config()['verbose'],
|
||||
'allow_git_url_install': get_config()['allow_git_url_install'],
|
||||
'allow_pip_install': get_config()['allow_pip_install'],
|
||||
}
|
||||
|
||||
# Sanitize all string values to prevent CRLF injection attacks
|
||||
@@ -1755,6 +1757,8 @@ def read_config():
|
||||
'security_level': default_conf.get('security_level', SecurityLevel.NORMAL.value).lower(),
|
||||
'db_mode': default_conf.get('db_mode', DBMode.CACHE.value).lower(),
|
||||
'verbose': get_bool('verbose', False),
|
||||
'allow_git_url_install': get_bool('allow_git_url_install', False),
|
||||
'allow_pip_install': get_bool('allow_pip_install', False),
|
||||
}
|
||||
|
||||
except Exception:
|
||||
@@ -1783,6 +1787,8 @@ def read_config():
|
||||
'security_level': SecurityLevel.NORMAL.value,
|
||||
'db_mode': DBMode.CACHE.value,
|
||||
'verbose': False,
|
||||
'allow_git_url_install': False,
|
||||
'allow_pip_install': False,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ export async function install_pip(packages) {
|
||||
});
|
||||
|
||||
if(res.status == 403) {
|
||||
show_message('This action is not allowed with this security level configuration.');
|
||||
show_message("To use this feature, set <code>allow_pip_install = true</code> in the [default] section of config.ini. This setting is independent of security_level.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ export async function install_via_git_url(url, manager_dialog) {
|
||||
});
|
||||
|
||||
if(res.status == 403) {
|
||||
show_message('This action is not allowed with this security level configuration.');
|
||||
show_message("To use this feature, set <code>allow_git_url_install = true</code> in the [default] section of config.ini. This setting is independent of security_level.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1690,6 +1690,8 @@ def write_config():
|
||||
'always_lazy_install': get_config()['always_lazy_install'],
|
||||
'network_mode': get_config()['network_mode'],
|
||||
'db_mode': get_config()['db_mode'],
|
||||
'allow_git_url_install': get_config()['allow_git_url_install'],
|
||||
'allow_pip_install': get_config()['allow_pip_install'],
|
||||
}
|
||||
|
||||
# Sanitize all string values to prevent CRLF injection attacks
|
||||
@@ -1734,6 +1736,8 @@ def read_config():
|
||||
'network_mode': default_conf.get('network_mode', NetworkMode.PUBLIC.value).lower(),
|
||||
'security_level': default_conf.get('security_level', SecurityLevel.NORMAL.value).lower(),
|
||||
'db_mode': default_conf.get('db_mode', DBMode.CACHE.value).lower(),
|
||||
'allow_git_url_install': get_bool('allow_git_url_install', False),
|
||||
'allow_pip_install': get_bool('allow_pip_install', False),
|
||||
}
|
||||
|
||||
except Exception:
|
||||
@@ -1757,6 +1761,8 @@ def read_config():
|
||||
'network_mode': NetworkMode.PUBLIC.value,
|
||||
'security_level': SecurityLevel.NORMAL.value,
|
||||
'db_mode': DBMode.CACHE.value,
|
||||
'allow_git_url_install': False,
|
||||
'allow_pip_install': False,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ SECURITY_MESSAGE_HIGH_P = "ERROR: To use this action, '--listen' must be set to
|
||||
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."
|
||||
SECURITY_MESSAGE_FLAG_GIT_URL = "ERROR: This action requires 'allow_git_url_install = true' in config.ini ([default] section). This setting is independent of security_level. Please contact the administrator.\nReference: https://github.com/Comfy-Org/ComfyUI-Manager#security-policy"
|
||||
SECURITY_MESSAGE_FLAG_PIP = "ERROR: This action requires 'allow_pip_install = true' in config.ini ([default] section). This setting is independent of security_level. Please contact the administrator.\nReference: https://github.com/Comfy-Org/ComfyUI-Manager#security-policy"
|
||||
|
||||
routes = PromptServer.instance.routes
|
||||
|
||||
@@ -122,6 +124,18 @@ def is_allowed_security_level(level):
|
||||
return True
|
||||
|
||||
|
||||
def _dedicated_install_allowed(flag_key: str) -> bool:
|
||||
"""goal265: P-direct gate for the dedicated install flags (spec §1.2).
|
||||
|
||||
allowed iff config[flag_key] AND (loopback listener OR
|
||||
network_mode == 'personal_cloud') — fully decoupled from security_level.
|
||||
Resolves config through the LEGACY reader and delegates the pure
|
||||
predicate to common/manager_security (which stays config-import-free).
|
||||
"""
|
||||
return manager_security.is_dedicated_install_allowed(
|
||||
core.get_config()[flag_key], args.listen, core.get_config()['network_mode'])
|
||||
|
||||
|
||||
async def get_risky_level(files, pip_packages):
|
||||
json_data1 = await core.get_data_by_mode('local', 'custom-node-list.json')
|
||||
json_data2 = await core.get_data_by_mode('cache', 'custom-node-list.json', channel_url='https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main')
|
||||
@@ -1473,7 +1487,15 @@ async def _install_custom_node(json_data):
|
||||
else:
|
||||
return web.Response(status=404, text=f"Following node pack doesn't provide `nightly` version: ${git_url}")
|
||||
|
||||
if not is_allowed_security_level(risky_level):
|
||||
# goal265 S-C (middle+ entry gate above UNCHANGED): unknown git URL ('high+')
|
||||
# -> dedicated-flag full predicate replaces the security_level check (spec §1.2);
|
||||
# unknown pip ('block') -> unconditional deny via is_allowed_security_level (Q1).
|
||||
# Flag-deny PRESERVES today's 404 response shape at this position (R1).
|
||||
if risky_level == 'high+':
|
||||
if not _dedicated_install_allowed('allow_git_url_install'):
|
||||
logging.error(SECURITY_MESSAGE_FLAG_GIT_URL)
|
||||
return web.Response(status=404, text="A security error has occurred. Please check the terminal logs")
|
||||
elif not is_allowed_security_level(risky_level):
|
||||
logging.error(SECURITY_MESSAGE_GENERAL)
|
||||
return web.Response(status=404, text="A security error has occurred. Please check the terminal logs")
|
||||
|
||||
@@ -1527,8 +1549,9 @@ 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+'):
|
||||
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||
# goal265 S-A: dedicated-flag gate, decoupled from security_level (spec §1.2).
|
||||
if not _dedicated_install_allowed('allow_git_url_install'):
|
||||
logging.error(SECURITY_MESSAGE_FLAG_GIT_URL)
|
||||
return web.Response(status=403)
|
||||
|
||||
url = await request.text()
|
||||
@@ -1547,8 +1570,9 @@ 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+'):
|
||||
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||
# goal265 S-B: dedicated-flag gate, decoupled from security_level (spec §1.2).
|
||||
if not _dedicated_install_allowed('allow_pip_install'):
|
||||
logging.error(SECURITY_MESSAGE_FLAG_PIP)
|
||||
return web.Response(status=403)
|
||||
|
||||
packages = await request.text()
|
||||
|
||||
Reference in New Issue
Block a user