Skip to content

Commit 7c09be6

Browse files
committed
feat(security): dedicated install flags decouple git_url/pip from security_level
Install via git URL and pip install are no longer gated by security_level. Each surface gets a dedicated config.ini flag — allow_git_url_install / allow_pip_install (both default false, secure by default) — that fully REPLACES the security-level term for these two features. The network-position invariant is retained: a non-local listener stays denied regardless of the flags unless network_mode = personal_cloud. - New pure predicate is_dedicated_install_allowed() in common/manager_security (no config access; callers resolve config) - Legacy endpoints /v2/customnode/install/git_url and .../pip switch from is_allowed_security_level('high+') to the flag gate; batch installs of unknown git URLs likewise (middle+ entry gate unchanged, unknown-pip 'block' stays unconditional; response shapes preserved) - Config readers/writers (glob + legacy) parse and persist the flags; denial logs and frontend 403 messages name the responsible flag and note the non-local-listener requirement (network_mode=personal_cloud) - No auto-seed from security_level — users previously on weak/normal- must opt in explicitly (see CHANGELOG migration notes; README documents the new contract) - Update the pre-existing permissive E2E harness (start_comfyui_permissive.sh + test_e2e_legacy_real_ops.py) to the new contract: it now also sets allow_git_url_install / allow_pip_install = true, since security_level = normal- alone no longer opens the git_url/pip endpoints Tests: predicate truth table proving security_level independence in both directions, dual-reader config contract, security-level-matrix freeze guards, legacy gate regression guards (121 unit), plus 22 real-server E2E tests incl. URL-form pip install with self-clean.
1 parent 8b98723 commit 7c09be6

16 files changed

Lines changed: 2512 additions & 33 deletions

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@ All notable changes to **ComfyUI-Manager** are documented in this file.
55
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Security
11+
12+
- **Dedicated install flags decouple git-URL / pip installs from `security_level`**:
13+
`POST /v2/customnode/install/git_url` and `POST /v2/customnode/install/pip`
14+
(and the batch install path for git URLs not in the custom-node DB) are now
15+
gated by two new `config.ini` `[default]` flags — `allow_git_url_install`
16+
and `allow_pip_install` — instead of `security_level`. Both default to
17+
`false` (secure by default), and a non-loopback listener stays denied unless
18+
`network_mode = personal_cloud` (the existing network-position invariant is
19+
retained — the flags never widen exposure beyond what was possible before).
20+
`security_level` no longer has any effect on these two endpoints, in either
21+
direction. The unknown-pip-package block in batch installs remains
22+
unconditional. Activation requires a restart (no hot reload).
23+
24+
### Migration notes
25+
26+
- **Users running `security_level = weak` or `normal-`**: these environments
27+
could previously use the git-URL / pip install endpoints; after upgrading
28+
they are denied (HTTP 403) until you explicitly opt in by setting
29+
`allow_git_url_install = true` and/or `allow_pip_install = true` in the
30+
`[default]` section of `config.ini`. The flags are NOT auto-seeded from
31+
your `security_level` — explicit opt-in is intentional.
32+
833
## [4.2.1] - 2026-04-22
934

1035
Security-hardening release. Contains breaking-ish API changes for

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ The following settings are applied based on the section marked as `is_default`.
214214
model_download_by_agent = <When downloading models, use an agent instead of torchvision_download_url.>
215215
downgrade_blacklist = <Set a list of packages to prevent downgrades. List them separated by commas.>
216216
security_level = <Set the security level => strong|normal|normal-|weak>
217+
allow_git_url_install = <Allow installing custom nodes from arbitrary git URLs. Independent of security_level. Default: False>
218+
allow_pip_install = <Allow installing arbitrary pip packages via the Manager. Independent of security_level. Default: False>
217219
always_lazy_install = <Whether to perform dependency installation on restart even in environments other than Windows.>
218220
network_mode = <Set the network mode => public|private|offline|personal_cloud>
219221
```
@@ -324,12 +326,14 @@ The security settings are applied based on whether the ComfyUI server's listener
324326
325327
| Risky Level | features |
326328
|-------------|---------------------------------------------------------------------------------------------------------------------------------------|
327-
| high+ | * `Install via git url`, `pip install`<BR>* Installation of nodepack registered not in the `default channel`.<BR>* **Switch ComfyUI version**<BR>* **Fix nodepack** |
329+
| high+ | * **Switch ComfyUI version**<BR>* **Fix nodepack** |
328330
| high | _(no features at this tier — `Fix nodepack` promoted to `high+` to align the enforcement gate with the `SECURITY_MESSAGE_HIGH_P` log text)_ |
329331
| middle+ | * Uninstall/Update<BR>* Installation of nodepack registered in the `default channel`.<BR>* Restore/Remove Snapshot<BR>* Install model |
330332
| middle | * Restart |
331333
| low | * Update ComfyUI |
332334
335+
* **Note**: `Install via git url` and `pip install` are no longer gated by `security_level` — they moved to the dedicated flags `allow_git_url_install` / `allow_pip_install`. Installation of a nodepack registered not in the `default channel` likewise requires `allow_git_url_install` (in addition to the `middle+` level preconditions) instead of a `high+` security level. See the [Dedicated install flags](#dedicated-install-flags-allow_git_url_install--allow_pip_install) subsection below.
336+
333337
334338
### Security Level Table
335339
@@ -341,6 +345,20 @@ The security settings are applied based on whether the ComfyUI server's listener
341345
| 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
342346
343347
348+
### Dedicated install flags (`allow_git_url_install` / `allow_pip_install`)
349+
350+
The `Install via git url` and `pip install` features are governed by two dedicated `config.ini` flags instead of `security_level`:
351+
352+
* `allow_git_url_install`: Allows installing custom nodes from arbitrary git URLs.
353+
* `allow_pip_install`: Allows installing arbitrary pip packages via the Manager.
354+
355+
* Both flags default to `False` — secure by default. A missing or invalid value (anything other than `true`, case-insensitive) is read as `False`.
356+
* These flags fully **replace** `security_level` for these two features. `security_level` no longer affects them in either direction: a strict security level cannot deny them when the flag is `true`, and a weak security level cannot allow them when the flag is `false`.
357+
* The network-position rule still applies independently: even with a flag enabled, the feature is denied when the server listener is **non-local**, unless `network_mode = personal_cloud`.
358+
* Batch installs of git URLs not registered in the `default channel` are also gated by `allow_git_url_install`, and additionally require the normal batch-install preconditions (the `middle+` rules in the tables above). Unknown pip packages in batch installs remain blocked unconditionally — the flags do not open them.
359+
* Changes to these flags require a restart of ComfyUI to take effect.
360+
* Migration note: if you previously relied on `security_level = weak` or `normal-` to use these features, you must now opt in explicitly by setting the flags in the `[default]` section of `config.ini`. The flags are not auto-seeded from your `security_level`.
361+
344362
345363
# Disclaimer
346364

comfyui_manager/common/manager_security.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,25 @@ def is_loopback(address):
8383
return False
8484

8585

86+
def is_dedicated_install_allowed(flag_value: bool, listen_address: str, network_mode: str) -> bool:
87+
"""P-direct predicate for the dedicated install flags (goal265-spec.md §1.2).
88+
89+
allowed iff flag AND (loopback listener OR network_mode == 'personal_cloud').
90+
91+
Gates the git-URL / standalone-pip install surfaces via the dedicated
92+
``allow_git_url_install`` / ``allow_pip_install`` config flags, fully
93+
decoupled from ``security_level`` (REPLACE, not AND — spec §1.1 inv. 1).
94+
The network-position term retains today's invariant that a public
95+
(non-loopback, non-personal_cloud) listener stays denied regardless of
96+
the flags (spec §1.1 inv. 2).
97+
98+
Pure function — NO config access; callers resolve ``flag_value`` and
99+
``network_mode`` through their own config reader and pass values in
100+
(preserves common/ layering: this module must stay config-import-free).
101+
"""
102+
return bool(flag_value) and (is_loopback(listen_address) or network_mode.lower() == 'personal_cloud')
103+
104+
86105
def do_nothing():
87106
pass
88107

comfyui_manager/glob/manager_core.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,6 +1706,8 @@ def write_config():
17061706
'network_mode': get_config()['network_mode'],
17071707
'db_mode': get_config()['db_mode'],
17081708
'verbose': get_config()['verbose'],
1709+
'allow_git_url_install': get_config()['allow_git_url_install'],
1710+
'allow_pip_install': get_config()['allow_pip_install'],
17091711
}
17101712

17111713
# Sanitize all string values to prevent CRLF injection attacks
@@ -1755,6 +1757,8 @@ def get_bool(key, default_value):
17551757
'security_level': default_conf.get('security_level', SecurityLevel.NORMAL.value).lower(),
17561758
'db_mode': default_conf.get('db_mode', DBMode.CACHE.value).lower(),
17571759
'verbose': get_bool('verbose', False),
1760+
'allow_git_url_install': get_bool('allow_git_url_install', False),
1761+
'allow_pip_install': get_bool('allow_pip_install', False),
17581762
}
17591763

17601764
except Exception:
@@ -1783,6 +1787,8 @@ def get_bool(key, default_value):
17831787
'security_level': SecurityLevel.NORMAL.value,
17841788
'db_mode': DBMode.CACHE.value,
17851789
'verbose': False,
1790+
'allow_git_url_install': False,
1791+
'allow_pip_install': False,
17861792
}
17871793

17881794

comfyui_manager/js/common.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ export async function install_pip(packages) {
216216
});
217217

218218
if(res.status == 403) {
219-
show_message('This action is not allowed with this security level configuration.');
219+
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.<BR>Note: if the ComfyUI listener is not local, <code>network_mode = personal_cloud</code> is also required.");
220220
return;
221221
}
222222

@@ -251,7 +251,7 @@ export async function install_via_git_url(url, manager_dialog) {
251251
});
252252

253253
if(res.status == 403) {
254-
show_message('This action is not allowed with this security level configuration.');
254+
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.<BR>Note: if the ComfyUI listener is not local, <code>network_mode = personal_cloud</code> is also required.");
255255
return;
256256
}
257257

comfyui_manager/legacy/manager_core.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,6 +1690,8 @@ def write_config():
16901690
'always_lazy_install': get_config()['always_lazy_install'],
16911691
'network_mode': get_config()['network_mode'],
16921692
'db_mode': get_config()['db_mode'],
1693+
'allow_git_url_install': get_config()['allow_git_url_install'],
1694+
'allow_pip_install': get_config()['allow_pip_install'],
16931695
}
16941696

16951697
# Sanitize all string values to prevent CRLF injection attacks
@@ -1734,6 +1736,8 @@ def get_bool(key, default_value):
17341736
'network_mode': default_conf.get('network_mode', NetworkMode.PUBLIC.value).lower(),
17351737
'security_level': default_conf.get('security_level', SecurityLevel.NORMAL.value).lower(),
17361738
'db_mode': default_conf.get('db_mode', DBMode.CACHE.value).lower(),
1739+
'allow_git_url_install': get_bool('allow_git_url_install', False),
1740+
'allow_pip_install': get_bool('allow_pip_install', False),
17371741
}
17381742

17391743
except Exception:
@@ -1757,6 +1761,8 @@ def get_bool(key, default_value):
17571761
'network_mode': NetworkMode.PUBLIC.value,
17581762
'security_level': SecurityLevel.NORMAL.value,
17591763
'db_mode': DBMode.CACHE.value,
1764+
'allow_git_url_install': False,
1765+
'allow_pip_install': False,
17601766
}
17611767

17621768

comfyui_manager/legacy/manager_server.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
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"
4444
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"
4545
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."
46+
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"
47+
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"
4648

4749
routes = PromptServer.instance.routes
4850

@@ -122,6 +124,18 @@ def is_allowed_security_level(level):
122124
return True
123125

124126

127+
def _dedicated_install_allowed(flag_key: str) -> bool:
128+
"""goal265: P-direct gate for the dedicated install flags (spec §1.2).
129+
130+
allowed iff config[flag_key] AND (loopback listener OR
131+
network_mode == 'personal_cloud') — fully decoupled from security_level.
132+
Resolves config through the LEGACY reader and delegates the pure
133+
predicate to common/manager_security (which stays config-import-free).
134+
"""
135+
return manager_security.is_dedicated_install_allowed(
136+
core.get_config()[flag_key], args.listen, core.get_config()['network_mode'])
137+
138+
125139
async def get_risky_level(files, pip_packages):
126140
json_data1 = await core.get_data_by_mode('local', 'custom-node-list.json')
127141
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):
14731487
else:
14741488
return web.Response(status=404, text=f"Following node pack doesn't provide `nightly` version: ${git_url}")
14751489

1476-
if not is_allowed_security_level(risky_level):
1490+
# goal265 S-C (middle+ entry gate above UNCHANGED): unknown git URL ('high+')
1491+
# -> dedicated-flag full predicate replaces the security_level check (spec §1.2);
1492+
# unknown pip ('block') -> unconditional deny via is_allowed_security_level (Q1).
1493+
# Flag-deny PRESERVES today's 404 response shape at this position (R1).
1494+
if risky_level == 'high+':
1495+
if not _dedicated_install_allowed('allow_git_url_install'):
1496+
logging.error(SECURITY_MESSAGE_FLAG_GIT_URL)
1497+
return web.Response(status=404, text="A security error has occurred. Please check the terminal logs")
1498+
elif not is_allowed_security_level(risky_level):
14771499
logging.error(SECURITY_MESSAGE_GENERAL)
14781500
return web.Response(status=404, text="A security error has occurred. Please check the terminal logs")
14791501

@@ -1527,8 +1549,9 @@ async def _fix_custom_node(json_data):
15271549

15281550
@routes.post("/v2/customnode/install/git_url")
15291551
async def install_custom_node_git_url(request):
1530-
if not is_allowed_security_level('high+'):
1531-
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
1552+
# goal265 S-A: dedicated-flag gate, decoupled from security_level (spec §1.2).
1553+
if not _dedicated_install_allowed('allow_git_url_install'):
1554+
logging.error(SECURITY_MESSAGE_FLAG_GIT_URL)
15321555
return web.Response(status=403)
15331556

15341557
url = await request.text()
@@ -1547,8 +1570,9 @@ async def install_custom_node_git_url(request):
15471570

15481571
@routes.post("/v2/customnode/install/pip")
15491572
async def install_custom_node_pip(request):
1550-
if not is_allowed_security_level('high+'):
1551-
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
1573+
# goal265 S-B: dedicated-flag gate, decoupled from security_level (spec §1.2).
1574+
if not _dedicated_install_allowed('allow_pip_install'):
1575+
logging.error(SECURITY_MESSAGE_FLAG_PIP)
15521576
return web.Response(status=403)
15531577

15541578
packages = await request.text()

0 commit comments

Comments
 (0)