# 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 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 `/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.