mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-07-26 10:07:38 +08:00
fix(security): harden CSRF with Content-Type gate and expand E2E coverage (#2818)
Defense-in-depth over GET→POST alone: reject the three CORS-safelisted simple-form Content-Types (x-www-form-urlencoded, multipart/form-data, text/plain) on 16 no-body POST handlers (glob + legacy) to block <form method=POST> CSRF that bypasses method-only gating. Move comfyui_switch_version to a JSON body so the preflight requirement applies. Split db_mode/policy/update/channel_url_list into GET(read) + POST(write). Tighten do_fix (high → high+) and gate three previously-ungated config setters at middle. Resynchronize openapi.yaml (27 paths, 30 operations, ComfyUISwitchVersionParams as a shared $ref component). Add E2E harness variants, Playwright config, CSRF/secgate suites, 39-endpoint coverage, and a CHANGELOG. Breaking: legacy per-op POST routes (install/uninstall/fix/disable/update/ reinstall/abort_current) are removed; callers already use queue/batch. Legacy /manager/notice (v1) is removed; /v2/manager/notice is retained. Reported-by: XlabAI Team of Tencent Xuanwu Lab CVSS: 8.1 (AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H)
This commit is contained in:
@@ -202,9 +202,14 @@ uv pip install \
|
||||
-r "$E2E_ROOT/comfyui/requirements.txt" \
|
||||
--extra-index-url "$PYTORCH_CPU_INDEX"
|
||||
|
||||
# Step 4: Install ComfyUI-Manager (non-editable, production-like)
|
||||
log "Step 4/8: Installing ComfyUI-Manager..."
|
||||
uv pip install --python "$VENV_PY" "$MANAGER_ROOT"
|
||||
# Step 4: Install ComfyUI-Manager (editable — venv tracks workspace edits)
|
||||
# Editable install prevents silent drift between the workspace source and the
|
||||
# installed package: any change to comfyui_manager/** is visible to E2E
|
||||
# immediately without re-running this script. The 2026-04-18 junk_value-rejection
|
||||
# regression (surfaced in WI-E/WI-G, root-caused in WI-I) was masked for weeks by
|
||||
# a non-editable snapshot — this flag closes that failure mode.
|
||||
log "Step 4/8: Installing ComfyUI-Manager (editable)..."
|
||||
uv pip install --python "$VENV_PY" -e "$MANAGER_ROOT"
|
||||
|
||||
# Step 5: Create symlink for custom_nodes discovery
|
||||
log "Step 5/8: Creating custom_nodes symlink..."
|
||||
|
||||
@@ -6,9 +6,15 @@
|
||||
# Claude's Bash tool — the call returns only when ComfyUI is accepting requests.
|
||||
#
|
||||
# Input env vars:
|
||||
# E2E_ROOT — (required) path to E2E environment from setup_e2e_env.sh
|
||||
# PORT — ComfyUI listen port (default: 8199)
|
||||
# TIMEOUT — max seconds to wait for readiness (default: 120)
|
||||
# E2E_ROOT — (required) path to E2E environment from setup_e2e_env.sh
|
||||
# PORT — ComfyUI listen port (default: 8199)
|
||||
# TIMEOUT — max seconds to wait for readiness (default: 120)
|
||||
# ENABLE_LEGACY_UI — if set to "1"/"true"/"yes", add --enable-manager-legacy-ui
|
||||
# (for Playwright legacy-UI tests; pytest suites should
|
||||
# leave this unset because glob and legacy manager_server
|
||||
# modules are mutex-loaded and several pytest suites hit
|
||||
# glob-only v2 endpoints such as /v2/manager/queue/task).
|
||||
# The convenience wrapper start_comfyui_legacy.sh sets it.
|
||||
#
|
||||
# Output (last line on success):
|
||||
# COMFYUI_PID=<pid> PORT=<port>
|
||||
@@ -36,7 +42,11 @@ PY="$E2E_ROOT/venv/bin/python"
|
||||
COMFY_DIR="$E2E_ROOT/comfyui"
|
||||
LOG_DIR="$E2E_ROOT/logs"
|
||||
LOG_FILE="$LOG_DIR/comfyui.log"
|
||||
PID_FILE="$LOG_DIR/comfyui.pid"
|
||||
# Port-namespaced PID file — prevents concurrent tests on different ports
|
||||
# (e.g., teammate running pytest on 8199 while Playwright runs on 8200)
|
||||
# from overwriting each other's PID, which would cause stop_comfyui.sh to
|
||||
# kill the wrong process (observed in WI-CC: 8200 stop killed 8199 PID 2979469).
|
||||
PID_FILE="$LOG_DIR/comfyui.${PORT}.pid"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
@@ -69,12 +79,30 @@ log "Starting ComfyUI on port $PORT..."
|
||||
# Create empty log file (ensures tail -f works from the start)
|
||||
: > "$LOG_FILE"
|
||||
|
||||
# Launch with unbuffered Python output so log lines appear immediately
|
||||
# Assemble manager flags. ENABLE_LEGACY_UI toggles --enable-manager-legacy-ui
|
||||
# without forcing every caller to care — pytest leaves it unset (glob mode),
|
||||
# start_comfyui_legacy.sh sets it (legacy UI mode).
|
||||
MANAGER_FLAGS=(--enable-manager)
|
||||
case "${ENABLE_LEGACY_UI:-}" in
|
||||
1|true|TRUE|yes|YES)
|
||||
MANAGER_FLAGS+=(--enable-manager-legacy-ui)
|
||||
log "Legacy UI enabled via ENABLE_LEGACY_UI=${ENABLE_LEGACY_UI}"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Launch with unbuffered Python output so log lines appear immediately.
|
||||
# COMFYUI_MANAGER_SKIP_MANAGER_REQUIREMENTS=1 is the WI-WW safety belt:
|
||||
# any install/update/reinstall path that would normally run
|
||||
# `pip install -r manager_requirements.txt` becomes a no-op log line.
|
||||
# Essential for WI-YY real-E2E tests that trigger install/update flows
|
||||
# — without it, a real update_comfyui task could run unbounded pip
|
||||
# installs on the test venv.
|
||||
PYTHONUNBUFFERED=1 \
|
||||
HOME="$E2E_ROOT/home" \
|
||||
COMFYUI_MANAGER_SKIP_MANAGER_REQUIREMENTS=1 \
|
||||
nohup "$PY" "$COMFY_DIR/main.py" \
|
||||
--cpu \
|
||||
--enable-manager \
|
||||
"${MANAGER_FLAGS[@]}" \
|
||||
--port "$PORT" \
|
||||
> "$LOG_FILE" 2>&1 &
|
||||
COMFYUI_PID=$!
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# start_comfyui_legacy.sh — Thin wrapper that launches ComfyUI in LEGACY UI mode.
|
||||
#
|
||||
# Delegates to start_comfyui.sh with ENABLE_LEGACY_UI=1. The underlying script
|
||||
# translates that into --enable-manager-legacy-ui on main.py, which registers
|
||||
# the legacy Manager dialog frontend and routes POST /v2/manager/queue/* to
|
||||
# the legacy handler module (legacy/manager_server.py).
|
||||
#
|
||||
# Use this wrapper for Playwright legacy-UI tests (tests/playwright/legacy-ui-*).
|
||||
# Do NOT use for pytest suites that hit glob-only v2 endpoints (e.g.
|
||||
# /v2/manager/queue/task), because glob/manager_server and legacy/manager_server
|
||||
# are mutex-loaded — see comfyui_manager/__init__.py::start().
|
||||
#
|
||||
# Input env vars (forwarded to start_comfyui.sh):
|
||||
# E2E_ROOT — required
|
||||
# PORT — default 8199
|
||||
# TIMEOUT — default 120
|
||||
#
|
||||
# Output (last line on success, inherited from start_comfyui.sh):
|
||||
# COMFYUI_PID=<pid> PORT=<port>
|
||||
#
|
||||
# Exit: 0=ready, 1=timeout/failure
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
exec env ENABLE_LEGACY_UI=1 bash "$SCRIPT_DIR/start_comfyui.sh" "$@"
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# start_comfyui_permissive.sh — Launch ComfyUI in PERMISSIVE security mode
|
||||
# for WI-YY real-E2E tests of `high+` gated endpoints.
|
||||
#
|
||||
# Patches `security_level = normal-` into the manager config.ini before
|
||||
# launching (with backup of original value), then delegates to
|
||||
# start_comfyui.sh with ENABLE_LEGACY_UI=1 (wi-037 and wi-038 are
|
||||
# legacy-only routes). The corresponding stop_comfyui.sh teardown should
|
||||
# be paired with restore_config() inside the pytest fixture — this
|
||||
# script does NOT restore on its own, so fixture teardown MUST cleanup.
|
||||
#
|
||||
# Why permissive mode is needed:
|
||||
# Three endpoints check is_allowed_security_level('high+')
|
||||
# (security_utils.py:20-26): at is_local_mode=True (127.0.0.1 listen)
|
||||
# the gate requires security_level ∈ {weak, normal-}. Default
|
||||
# `security_level = normal` fails, so the POST returns 403.
|
||||
# - wi-014 POST /v2/comfyui_manager/comfyui_switch_version
|
||||
# - wi-037 POST /v2/customnode/install/git_url
|
||||
# - wi-038 POST /v2/customnode/install/pip
|
||||
# Setting security_level = normal- allows real E2E execution of these
|
||||
# endpoints with fixed, trusted inputs (never test-input-derived URLs).
|
||||
#
|
||||
# SECURITY NOTE:
|
||||
# The endpoints are gated at high+ because they execute arbitrary remote
|
||||
# code (git clone / pip install / version switch). This harness opens
|
||||
# the gate ONLY in the E2E sandbox with HARDCODED trusted inputs
|
||||
# (ComfyUI_examples repo; text-unidecode package). Never use with
|
||||
# user-input-derived inputs — the 403 contract at default security is
|
||||
# the positive-path security behavior we want to preserve in production.
|
||||
#
|
||||
# Input env vars (forwarded to start_comfyui.sh):
|
||||
# E2E_ROOT — required
|
||||
# PORT — default 8199
|
||||
# TIMEOUT — default 120
|
||||
#
|
||||
# Output (last line on success, inherited from start_comfyui.sh):
|
||||
# COMFYUI_PID=<pid> PORT=<port>
|
||||
#
|
||||
# Exit: 0=ready, 1=timeout/failure
|
||||
#
|
||||
# Side effect: $E2E_ROOT/comfyui/user/__manager/config.ini gets
|
||||
# `security_level = normal-`. The original value is preserved at
|
||||
# config.ini.before-permissive for the fixture to restore on teardown.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
[[ -n "${E2E_ROOT:-}" ]] || { echo "[start_comfyui_permissive] ERROR: E2E_ROOT is not set" >&2; exit 1; }
|
||||
|
||||
CONFIG="$E2E_ROOT/comfyui/user/__manager/config.ini"
|
||||
BACKUP="$CONFIG.before-permissive"
|
||||
|
||||
[[ -f "$CONFIG" ]] || { echo "[start_comfyui_permissive] ERROR: config not found at $CONFIG" >&2; exit 1; }
|
||||
|
||||
# Preserve original config so the fixture can restore it on teardown.
|
||||
# If a previous run left a backup, do NOT overwrite.
|
||||
if [[ ! -f "$BACKUP" ]]; then
|
||||
cp "$CONFIG" "$BACKUP"
|
||||
echo "[start_comfyui_permissive] Backed up original config to $BACKUP"
|
||||
fi
|
||||
|
||||
# Patch security_level to normal- (idempotent).
|
||||
if grep -qE '^security_level\s*=' "$CONFIG"; then
|
||||
sed -i -E 's/^security_level\s*=.*/security_level = normal-/' "$CONFIG"
|
||||
else
|
||||
sed -i -E '/^\[default\]/a security_level = normal-' "$CONFIG"
|
||||
fi
|
||||
echo "[start_comfyui_permissive] Patched security_level = normal- in $CONFIG"
|
||||
|
||||
exec env ENABLE_LEGACY_UI=1 bash "$SCRIPT_DIR/start_comfyui.sh" "$@"
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
# start_comfyui_strict.sh — Launch ComfyUI in STRICT security mode for SECGATE tests.
|
||||
#
|
||||
# Patches `security_level = strong` into the manager config.ini before launching
|
||||
# (with backup of original value), then delegates to start_comfyui.sh. The
|
||||
# corresponding stop_comfyui.sh teardown should be paired with restore_config()
|
||||
# inside the pytest fixture (this script does NOT restore on its own — restore
|
||||
# happens at fixture teardown to keep this wrapper symmetric with
|
||||
# start_comfyui_legacy.sh).
|
||||
#
|
||||
# Why strict mode is needed:
|
||||
# Several state-changing endpoints (snapshot/remove [middle], snapshot/restore
|
||||
# [middle+], reboot [middle], queue/update_all [middle+]) check
|
||||
# is_allowed_security_level(<gate>). At the default `security_level = normal`
|
||||
# (and is_local_mode = True since we listen on 127.0.0.1), middle and middle+
|
||||
# operations are ALLOWED — so the 403 path is unreachable. Setting
|
||||
# security_level = strong puts NORMAL out of the allowed sets and makes the
|
||||
# 403 contract observable.
|
||||
#
|
||||
# At-or-below `normal` configurations cannot test the 403 path for these gates;
|
||||
# `strong` is required.
|
||||
#
|
||||
# Input env vars (forwarded to start_comfyui.sh):
|
||||
# E2E_ROOT — required
|
||||
# PORT — default 8199
|
||||
# TIMEOUT — default 120
|
||||
#
|
||||
# Output (last line on success, inherited from start_comfyui.sh):
|
||||
# COMFYUI_PID=<pid> PORT=<port>
|
||||
#
|
||||
# Exit: 0=ready, 1=timeout/failure
|
||||
#
|
||||
# Side effect: $E2E_ROOT/comfyui/user/__manager/config.ini gets
|
||||
# `security_level = strong`. The original value is preserved at
|
||||
# config.ini.before-strict for the fixture to restore on teardown.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
[[ -n "${E2E_ROOT:-}" ]] || { echo "[start_comfyui_strict] ERROR: E2E_ROOT is not set" >&2; exit 1; }
|
||||
|
||||
CONFIG="$E2E_ROOT/comfyui/user/__manager/config.ini"
|
||||
BACKUP="$CONFIG.before-strict"
|
||||
|
||||
[[ -f "$CONFIG" ]] || { echo "[start_comfyui_strict] ERROR: config not found at $CONFIG" >&2; exit 1; }
|
||||
|
||||
# Preserve original config so the fixture can restore it on teardown.
|
||||
# If a previous run left a backup, do NOT overwrite (preserves the *true*
|
||||
# pre-strict baseline across crashed test runs).
|
||||
if [[ ! -f "$BACKUP" ]]; then
|
||||
cp "$CONFIG" "$BACKUP"
|
||||
echo "[start_comfyui_strict] Backed up original config to $BACKUP"
|
||||
fi
|
||||
|
||||
# Patch security_level to strong (idempotent — works whether the line is
|
||||
# already `strong`, `normal`, or any other value).
|
||||
if grep -qE '^security_level\s*=' "$CONFIG"; then
|
||||
sed -i -E 's/^security_level\s*=.*/security_level = strong/' "$CONFIG"
|
||||
else
|
||||
# security_level missing entirely (unusual) — append under [default]
|
||||
sed -i -E '/^\[default\]/a security_level = strong' "$CONFIG"
|
||||
fi
|
||||
echo "[start_comfyui_strict] Patched security_level = strong in $CONFIG"
|
||||
|
||||
exec bash "$SCRIPT_DIR/start_comfyui.sh" "$@"
|
||||
@@ -23,7 +23,15 @@ die() { err "$@"; exit 1; }
|
||||
# --- Validate ---
|
||||
[[ -n "${E2E_ROOT:-}" ]] || die "E2E_ROOT is not set"
|
||||
|
||||
PID_FILE="$E2E_ROOT/logs/comfyui.pid"
|
||||
PID_FILE="$E2E_ROOT/logs/comfyui.${PORT}.pid"
|
||||
# Legacy single-port path — warn if encountered so concurrent tests on
|
||||
# different ports don't overwrite each other's PID file (observed during
|
||||
# WI-CC: stop_comfyui.sh on port 8200 accidentally killed another teammate's
|
||||
# PID 2979469 running on port 8199 because both shared $E2E_ROOT/logs/comfyui.pid).
|
||||
LEGACY_PID_FILE="$E2E_ROOT/logs/comfyui.pid"
|
||||
if [[ -f "$LEGACY_PID_FILE" ]] && [[ ! -f "$PID_FILE" ]]; then
|
||||
log "WARN: found legacy unported PID file $LEGACY_PID_FILE but no ${PID_FILE}. Cross-port risk — ignoring legacy file."
|
||||
fi
|
||||
|
||||
# --- Read PID ---
|
||||
COMFYUI_PID=""
|
||||
|
||||
Reference in New Issue
Block a user