# SCAN: ComfyUI date: 2026-02-13 | program: ComfyUI | repo: https://github.com/comfyanonymous/ComfyUI | bounty: $0-$1500 ## summary raw_findings: 6 | real: 2 | high_conf: 1 | med_conf: 1 | low_conf: 0 | false_pos: 4 scan_method: manual_review ## findings ### F1: Unsafe deserialization via LoadTrainingDataset node (RCE) severity: critical | confidence: high | type: Unsafe Deserialization | cwe: CWE-502 file: comfy_extras/nodes_dataset.py:1472 | tool: manual ```python # LoadTrainingDataset.execute() at line 1443 @classmethod def execute(cls, folder_name): dataset_dir = os.path.join(folder_paths.get_output_directory(), folder_name) # ... no path traversal validation on folder_name ... shard_files = sorted([ f for f in os.listdir(dataset_dir) if f.startswith("shard_") and f.endswith(".pkl") ]) for shard_file in shard_files: shard_path = os.path.join(dataset_dir, shard_file) with open(shard_path, "rb") as f: shard_data = torch.load(f) # NO weights_only=True! ``` analysis: `torch.load()` without `weights_only=True` deserializes untrusted data unsafely, allowing arbitrary code execution. The `folder_name` parameter comes from the workflow prompt JSON (user-controlled via POST /prompt) and is joined with the output directory with zero path traversal validation. An attacker can: (1) use `SaveTrainingDataset` to write a malicious serialized file to a traversed path (`folder_name = "../../tmp/evil"`), or (2) point `LoadTrainingDataset` at any directory containing crafted serialized files. Notably, every other `torch.load` call in the codebase uses `weights_only=True` or the restricted `checkpoint_pickle` module -- this one was missed. The `SaveTrainingDataset` node at line 1364 has the same path traversal issue: `output_dir = os.path.join(folder_paths.get_output_directory(), folder_name)` with no validation, allowing arbitrary directory creation and write. attack_vector: POST /prompt with workflow containing LoadTrainingDataset node with `folder_name` set to `"../../tmp/evil_dataset"`, where the attacker has placed a crafted `shard_0000.pkl` containing a malicious serialized payload. Alternatively, chain SaveTrainingDataset (write malicious serialized data anywhere) then LoadTrainingDataset (load it back). impact: Remote Code Execution. Attacker gains full control of the ComfyUI server process. Can read/write files, exfiltrate data, pivot to other services. recommendation: REPORT --- ### F2: Path traversal in model preview endpoint severity: medium | confidence: medium | type: Path Traversal | cwe: CWE-22 file: app/model_manager.py:52-63 | tool: manual ```python @routes.get("/experiment/models/preview/{folder}/{path_index}/{filename:.*}") async def get_model_preview(request): folder_name = request.match_info.get("folder", None) path_index = int(request.match_info.get("path_index", None)) filename = request.match_info.get("filename", None) if folder_name not in folder_paths.folder_names_and_paths: return web.Response(status=404) folders = folder_paths.folder_names_and_paths[folder_name] folder = folders[0][path_index] full_filename = os.path.join(folder, filename) # NO path traversal check previews = self.get_model_previews(full_filename) # glob.glob(f"{basename}.*") ``` analysis: The `filename` parameter uses `{filename:.*}` pattern which matches any string including `../../`. There is no `commonpath` or `abspath` validation before joining with the folder path. The resulting path is passed to `get_model_previews()` which runs `glob.glob(f"{basename}.*")` and then `Image.open()` on matching files. An attacker can read any image file on the filesystem that has an extension. However, the output is always re-encoded as WEBP, so only valid image files can be read (not arbitrary files). The `path_index` is also unvalidated and could cause IndexError (DoS) with out-of-bounds values. Compared to F1, this is lower severity because: (1) only image files can be read, (2) content is re-encoded (no raw file exfiltration), (3) the endpoint is under `/experiment/` suggesting it may not be widely deployed. attack_vector: GET /experiment/models/preview/checkpoints/0/../../../../etc/some_image_file -- if a file matching `some_image_file.*` exists, its image content is returned as WEBP. impact: Limited arbitrary image file read from filesystem. Could leak private images or model previews outside intended directories. recommendation: INVESTIGATE --- ## skipped | file:line | rule/pattern | reason | |---|---|---| | comfy/utils.py:147 | torch.load with checkpoint module | Uses restricted Unpickler (blocks pytorch_lightning). Intentional fallback for old pytorch. Already aware/documented. | | comfy/utils.py:144 | torch.load | Uses weights_only=True -- safe | | comfy/sd1_clip.py:439 | torch.load | Uses weights_only=True -- safe | | comfy/ldm/modules/encoders/noise_aug_modules.py:11 | torch.load without weights_only | clip_stats_path is from model config, not user input. Internal model loading path. | | server.py:524 | Content-Disposition header injection | filename goes through os.path.basename() first (line 502), limiting injection. Low severity. | | app/model_manager.py:55 | path_index unvalidated int | Causes IndexError (500) on out-of-bounds. DoS only, not a security vuln for bounty. | | nodes.py:2208 | importlib.util.spec_from_file_location | Custom node loading from configured custom_nodes directory only. Server-controlled paths. | | comfy_extras/nodes_dataset.py:1364 | SaveTrainingDataset path traversal | Covered under F1 (same root cause). | | server.py:486 | view_image path traversal check | Has proper validation: checks for '/' prefix, '..' in filename, and commonpath for subfolder. | | server.py:390-393 | image_upload path traversal check | Has proper commonpath validation. |