Skip to content

Security: CraftopiaStudio/ComfyUI-CraftKit

Security

SECURITY.md

Security

This document exists so a reviewer does not have to reverse-engineer intent from a pattern-scanner report. It lists every construct in CraftKit that a security scanner flags, what it actually does, and why it is not reachable from attacker-controlled input.

A note on how this file is written. The registry scanner reads Markdown as if it were source, so a security document that quotes the very call syntax it is explaining adds findings to its own package. Function names below are therefore written without their call parentheses and arguments. Nothing is being hidden: every construct is named, and the line numbers point at the real code.

Please open an issue at https://github.com/CraftopiaStudio/ComfyUI-CraftKit/issues for anything this page does not cover, or if you believe any claim here is wrong.

Summary

  • No third-party dependencies. pyproject.toml declares dependencies = []. Pillow, NumPy and PyTorch ship with ComfyUI itself and are deliberately not re-declared. Nothing is installed or downloaded at runtime.
  • No outbound network traffic. The package never opens a socket, makes an HTTP request, or contacts any host. No telemetry, no update check, no analytics. There is no requests, urllib, socket, or http.client import anywhere in it.
  • No dynamic execution: no eval, no exec, no compile, no pickle, no marshal, and no dunder-import builtin anywhere in the package.
  • No HTTP routes. As of 1.1.5 the package registers nothing on PromptServer and adds no aiohttp handlers.
  • No process execution. As of 1.1.5 the package contains no process spawning call of any kind: no subprocess module use, no os-level system or popen call.
  • All filesystem access is confined to ComfyUI's own input, output and temp directories plus folders the user approved explicitly.

The one node that touches the filesystem

Four of the five nodes (SmartResize, SmartResolutionMultiplier, SmartPromptController, SmartProfileSwitch) are pure computation on tensors, numbers and strings. They open no files and take no paths.

SmartBatchResize in smart_batch_resize.py is the only node that reads and writes files. It takes an input_folder STRING widget, reads the images in that folder, and writes resized copies into a subfolder of it.

Why a free STRING path is treated as hostile

ComfyUI's /prompt endpoint is unauthenticated, so any widget value is effectively attacker-controlled: a malicious page open in the same browser can POST a crafted prompt to http://127.0.0.1:8188/prompt by CSRF. A node that reads or writes an arbitrary path is therefore an unconfined filesystem read and write, reachable without authorization. That is exactly what got versions 1.0.1 through 1.1.1 banned from the registry, and the finding was correct.

The containment, and where it runs

The guard function folder_allowed in craftkit_folder_guard.py decides whether a path may be used at all. A path is accepted only if:

  1. it resolves inside one of ComfyUI's own directories (the input, output and temp directory getters on ComfyUI's own folder_paths module), or
  2. it resolves inside a folder the user approved explicitly, stored in ComfyUI's own settings store under the key CraftKit.AllowedFolders, or
  3. it resolves inside a folder in the legacy <user dir>/craftkit/allowed_folders.json, kept readable so approvals made before 1.1.5 survive the update. Nothing in the package writes that file any more.

Containment is os.path.realpath on both sides plus os.path.commonpath against each allowed root, in the is_path_under helper. Resolving both sides means a symlink or junction pointing out of an approved folder does not slip through, and .. cannot walk upward, because the comparison happens after resolution.

The guard is called before any filesystem access, on both paths:

Call site Guards
smart_batch_resize.py:251 the node's run method, ahead of the directory listing, ahead of any image decode, and ahead of creating the output directory
smart_batch_resize.py:213 the IS_CHANGED classmethod, ahead of the listing used for cache hashing

So reads are gated, not only writes. No pixel data can be decoded out of a folder the user has not approved, which is the 1.0.1 ARBITRARY_FILE_READ finding.

The write target

The output subfolder name is validated separately, in the run method: it is rejected if it is absolute, carries a drive letter, contains .., or contains any path separator. The write target can therefore only ever be a direct child of an already-approved folder. A run that would write into the input folder itself is also refused, so originals are never overwritten.

No escape hatch

Before 1.1.5 the config honoured an "allow_any": true flag that disabled containment. It is removed. A reviewer judges what the code makes possible, not what the default is, and a flag that turns off containment is a finding on its own. A user who needs a wide scope approves a wide folder instead, deliberately.

Two smaller precautions

  • UNC paths are screened before resolution. On Windows, merely resolving or stat-ing a \\server\share path opens an SMB connection and hands over an NTLM hash before any containment check gets to run. The prescreen helper therefore judges UNC values lexically and refuses them outright unless they sit under a share root the user already approved. No filesystem call happens first.
  • The legacy approval list lives outside the package folder, under ComfyUI's user directory. The package directory is a git working tree, and a stray git add -A there would publish the user's personal folder list.

Removed in 1.1.5

Up to 1.1.4 the node had a "Browse folder" button, backed by a POST /craftkit/browse_folder route that opened the operating system's own folder dialog by launching a helper process: PowerShell's FolderBrowserDialog on Windows, osascript on macOS, zenity or kdialog on Linux.

The command lines were fixed constants with nothing interpolated into them, and the route was loopback-only with an Origin-versus-Host check. It was also the stronger design in one respect: the click-OK happened in the operating system, outside anything an HTTP request could reach.

It is gone regardless. Versions 1.1.2, 1.1.3 and 1.1.4 were banned for command injection, and 1.1.3 had already replaced the earlier COM-interop implementation with a plainer one, which was not enough. The feature is removed rather than reworked a third time. Folder approval is now a click in the ComfyUI frontend that writes CraftKit.AllowedFolders through ComfyUI's own settings route. The package only ever reads that setting.

This means the package registers no routes and starts no processes at all, which is what removes the entire class of finding rather than one instance of it.

Known scanner findings and why they are false positives

Pattern Where What it is
Pillow image decode smart_batch_resize.py Decoding an image file in a folder the user approved. The guard runs above it.
pathlib directory listing smart_batch_resize.py Listing that same approved folder. Same guard.
two read-only file opens craftkit_folder_guard.py Reading two JSON files, both under ComfyUI's user directory, both with fixed names, never written. A missing or malformed file means "nothing extra is approved", never an exception mid-run.
JSON parsing craftkit_folder_guard.py The same two files. No pickle, no YAML loader, no deserialization of user-supplied objects.

If a scan reports something not listed here, please tell us which rule and which line. We would rather remove a construct than have a version banned for it.

There aren't any published security advisories