152 lines
7.7 KiB
Markdown
152 lines
7.7 KiB
Markdown
# 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.
|