Back to skill

Security audit

ComfyUI

Security checks for vulnerabilities and agentic risk

Overview

This ComfyUI skill has a coherent image-workflow purpose, but it needs Review because it can automatically install and run unverified external software and accepts arbitrary model URLs with unsafe file and network handling.

Review this skill before installing. Use it only if you trust the ComfyUI environment and the model URLs, avoid untrusted workflow JSON or pasted URL lists, prefer --no-pget unless pget is already installed from a trusted source, and pin/verify ComfyUI and model downloads where possible.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/download_weights.py:108
Finding
Encoded Path Traversal Allows Writes Outside the ComfyUI Models Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_weights.py:108-149` **Vulnerability Type**: Path traversal and arbitrary file write **Risk Level**: High ### Vulnerable Code ```python def resolve_url_dest( raw: str, base: str, overwrite: bool, default_subfolder: str | None ) -> tuple[str | None, str | None, bool]: """Return (url, dest_path, is_skip). url None = skip/ignore.""" url = raw.strip() if not url or url.startswith("#"): return (None, None, True) subfolder = default_subfolder if default_subfolder in SUBFOLDERS else "checkpoints" if " " in url: url, sub = url.strip().split(None, 1) sub = sub.strip().lower() if sub in SUBFOLDERS: subfolder = sub elif not default_subfolder or default_subfolder not in SUBFOLDERS: subfolder = infer_subfolder(url) base = os.path.expanduser(base) model_dir = os.path.join(base, "models", subfolder) os.makedirs(model_dir, exist_ok=True) path = urlparse(url).path name = path.rstrip("/").split("/")[-1] name = unquote(name) if name else "downloaded.safetensors" out_path = os.path.join(model_dir, name) if os.path.isfile(out_path) and not overwrite: return (None, out_path, True) return (url, out_path, False) ``` The resulting path is subsequently used as a file-write destination: ```python def download_one_fallback( url: str, dest_path: str, overwrite: bool ) -> tuple[str, str]: """Download one file with urllib. Returns (status, path_or_message).""" if os.path.isfile(dest_path) and not overwrite: return ("skipped", dest_path) req = urllib.request.Request(url, headers={"User-Agent": "ComfyUI-Skill/1.0"}) try: with urllib.request.urlopen(req, timeout=600) as resp: with open(dest_path, "wb") as f: while True: chunk = resp.read(1 << 20) if not chunk: break ...[truncated 2324 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse and decode the candidate filename before validating it. 2. Reject filenames containing `/`, `\`, NUL characters, `.` or `..` path components, or absolute paths. 3. Prefer reducing the decoded value to a strict basename and enforce an allowlist of filename characters and expected model extensions. 4. Resolve both the destination and permitted model directory before writing: ```python from pathlib import Path model_root = Path(base, "models", subfolder).resolve() decoded_name = unquote(name) if ( decoded_name in {".", ".."} or "/" in decoded_name or "\\" in decoded_name or "\x00" in decoded_name ): raise ValueError("Unsafe destination filename") destination = (model_root / decoded_name).resolve() if destination.parent != model_root: raise ValueError("Destination escapes model directory") ``` 5. Perform this validation before existence checks and before adding a destination to a `pget` manifest. 6. Download to a temporary file within the validated directory and atomically rename it after successful completion. 7. Add regression tests covering encoded traversal, encoded separators, absolute paths, Windows separators, and double-encoded input. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_weights.py:108
Finding
Unrestricted Download URLs Enable SSRF and Local Resource Retrieval<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_weights.py:108-149` **Vulnerability Type**: Server-side request forgery and unsafe URL handling **Risk Level**: Medium ### Vulnerable Code ```python url = raw.strip() if not url or url.startswith("#"): return (None, None, True) ``` The URL is later passed directly to the network client: ```python req = urllib.request.Request(url, headers={"User-Agent": "ComfyUI-Skill/1.0"}) try: with urllib.request.urlopen(req, timeout=600) as resp: with open(dest_path, "wb") as f: while True: chunk = resp.read(1 << 20) if not chunk: break f.write(chunk) ``` ### Technical Analysis The downloader accepts an arbitrary user-provided URL without validating: - The URL scheme. - The destination hostname. - The resolved IP address. - Whether the destination is loopback, private, link-local, multicast, or reserved. - Redirect targets. - Whether URL credentials are embedded. - Whether the URL points to an expected public model repository. As a result, the process can be induced to make requests to services that are reachable from the Skill execution environment but not directly reachable by the attacker. Examples include local ComfyUI endpoints, other loopback services, private-network applications, and link-local cloud metadata services. Because `urllib` follows redirects, validation limited only to the initial URL would also be insufficient. Every redirect destination and resolved address must be checked. ### Attack Path 1. An attacker provides a URL targeting a loopback, private-network, link-local, or otherwise sensitive destination. 2. The Skill passes that URL directly to `urllib.request.urlopen()`. 3. The request originates from the host running the Skill and inherits its network reachability. 4. The response is written into the ComfyUI model directory or another destination if combined with the path-tr ...[truncated 910 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only explicitly required URL schemes, preferably `https`. 2. Reject URLs containing embedded usernames or passwords. 3. Resolve destination hostnames and reject every address in loopback, private, link-local, multicast, unspecified, and reserved ranges. 4. Disable automatic redirects or validate the scheme, hostname, port, and resolved address at every redirect. 5. Consider an allowlist of approved model-hosting domains if operationally practical. 6. Reject nonstandard ports unless they are explicitly required. 7. Apply connection, read, total-size, and redirect-count limits. 8. Re-resolve carefully when connecting to prevent DNS rebinding and ensure the validated address is the address actually used. 9. Log the final validated destination without exposing URL credentials or sensitive query parameters. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/download_weights.py:22
Finding
Unpinned Remote Binary Is Downloaded, Marked Executable, and Run Without Integrity Verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_weights.py:22-133` **Vulnerability Type**: Mutable remote payload execution and supply-chain compromise **Risk Level**: High ### Vulnerable Code ```python PGET_RELEASE = "https://github.com/replicate/pget/releases/latest/download" ``` ```python def get_pget_binary() -> str | None: """Return path to pget binary, or None if not found and install failed.""" pget = shutil.which("pget") if pget: return pget # Install to ~/.local/bin local_bin = os.path.expanduser("~/.local/bin") pget_path = os.path.join(local_bin, "pget") if os.path.isfile(pget_path) and os.access(pget_path, os.X_OK): return pget_path os.makedirs(local_bin, exist_ok=True) sysname = platform.system() machine = platform.machine() if machine == "aarch64": machine = "arm64" elif machine == "x86_64": machine = "x86_64" # Replicate releases: pget_Linux_x86_64, pget_Darwin_arm64, etc. asset = f"pget_{sysname}_{machine}" url = f"{PGET_RELEASE}/{asset}" try: req = urllib.request.Request(url, headers={"User-Agent": "ComfyUI-Skill/1.0"}) with urllib.request.urlopen(req, timeout=60) as resp: with open(pget_path, "wb") as f: f.write(resp.read()) os.chmod(pget_path, 0o755) return pget_path except Exception as e: print(f"Could not install pget ({e}); falling back to built-in download.", file=sys.stderr) return None ``` The downloaded file is subsequently executed: ```python def download_with_pget(manifest_path: str, pget_bin: str, overwrite: bool) -> bool: cmd = [pget_bin, "multifile", manifest_path] if overwrite: cmd.append("-f") r = subprocess.run(cmd) return r.returncode == 0 ``` ### Technical Analysis When `pget` is absent, the script downloads a native executable from a mutable `latest` release URL, writes it to `~/.local/bin/pget`, g ...[truncated 1936 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not install `pget` automatically by default. Prefer the built-in downloader or require explicit user consent. 2. Pin a specific reviewed release rather than using `/latest/`. 3. Store an expected SHA-256 digest for every supported operating-system and architecture combination. 4. Download the artifact to a securely created temporary file. 5. Verify its size and digest before setting executable permissions or moving it into `~/.local/bin`. 6. Where available, verify a cryptographic release signature or trusted provenance attestation. 7. Atomically install the verified file and use restrictive permissions. 8. Consider using a project-local tool directory instead of modifying `~/.local/bin`. 9. Document the exact pinned version and update it only through a reviewed release process. 10. Fail closed on verification failure rather than silently executing an unverified alternative. ]]>

T03 · Remote Payload Retrieval and Execution

Warning
Location
SKILL.md:60
Finding
ComfyUI Installation Instructions Clone and Install Unpinned Upstream Code<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:60-65` **Vulnerability Type**: Unpinned source and dependency installation **Risk Level**: Medium ### Vulnerable Code ```bash git clone https://github.com/comfyanonymous/ComfyUI.git ~/ComfyUI cd ~/ComfyUI python3 -m venv venv ~/ComfyUI/venv/bin/pip install -r requirements.txt ``` ### Technical Analysis The recovery instructions direct the agent to clone the current default branch of an external repository and install its dependencies without pinning a reviewed release, tag, or commit. The effective code and dependency set can change after the Skill itself has been audited. Installation of `requirements.txt` may execute package build backends or installation logic, and later startup executes the cloned `main.py`. The instructions also do not require hash-locked Python packages or verification of source provenance. ### Attack Path 1. ComfyUI is not installed when the Skill is invoked. 2. The agent follows the installation instructions. 3. It clones the then-current upstream default branch rather than a reviewed revision. 4. It installs the repository's then-current Python dependencies. 5. A compromised upstream repository, dependency, release process, or package source supplies malicious code. 6. Malicious dependency installation logic or the subsequently started ComfyUI application executes under the user's account. ### Impact Assessment A successful upstream compromise could result in arbitrary code execution with the privileges of the user performing the installation. The affected scope includes files, credentials, environment variables, and network resources accessible to that account. This is a supply-chain exposure rather than evidence that the named upstream project is currently malicious. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin ComfyUI to a reviewed release tag or immutable commit hash. 2. Verify the selected source revision against an authenticated release or documented checksum. 3. Use a lock file or constraints file with exact dependency versions. 4. Require hashes for downloaded Python packages where feasible. 5. Install in an isolated virtual environment with no unnecessary credentials or permissions. 6. Review dependency changes before updating the pinned ComfyUI revision. 7. Separate installation from normal Skill execution and require explicit user approval before cloning or installing third-party code. 8. Document the tested ComfyUI revision and supported dependency set in the Skill metadata. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (12)

Self-Modification

High
Category
Rogue Agent
Content
## Updating the Skill

- When ComfyUI’s API or workflow format changes, update SKILL.md and the run script.
- Keep the “If the server isn’t reachable” section in sync with current ComfyUI install/run instructions.
- Bump version when publishing a new release to ClawHub.
Confidence
85% confidence
Finding
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared purpose is local workflow execution, but the instructions also authorize downloading arbitrary remote files, installing an external binary into the user's home directory, and writing into model directories. This mismatch is dangerous because users or orchestration systems may invoke the skill for innocuous image generation while unintentionally triggering software installation or remote file retrieval with significant system side effects.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The script automatically downloads an external executable from GitHub and then later executes it, creating a clear supply-chain risk. If the release asset, transport, account, or upstream project is compromised, this skill can introduce and run arbitrary code on the host under the user's account.

Self-Modification

High
Category
Rogue Agent
Content
ap.add_argument(
        "--overwrite",
        action="store_true",
        help="Overwrite existing files",
    )
    ap.add_argument(
        "--no-pget",
Confidence
85% confidence
Finding
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill instructs the agent to read local files, invoke shell commands, and access the network, but it declares no explicit tool scope or permission boundary. That increases the chance the agent will use powerful capabilities implicitly and without user-visible guardrails, especially when handling user-supplied workflow paths and URLs.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The read_when conditions include generic image-generation prompts, which can cause this high-privilege skill to trigger for broad, ordinary requests. In context, that is risky because the skill can read arbitrary workflow files, make network requests, and modify local state, so over-broad matching expands the attack surface for accidental or adversarial activation.

Session Persistence

Medium
Category
Rogue Agent
Content
2. **Identify prompt-related nodes** by inspecting the graph: look for nodes that hold the main text prompt — e.g. `PrimitiveStringMultiline`, `CLIPTextEncode` (positive text), or any node with `_meta.title` or `class_type` suggesting "Prompt" / "positive" / "text". Update the corresponding input (e.g. `inputs.value`, or the text input to the encoder) to the image prompt you derived from the user (subject, style, lighting, quality). If the user didn’t ask for a custom image, you can leave the existing prompt or tweak only if needed.
3. **Optionally identify style/prefix nodes** — e.g. `StringConcatenate`, or a second string input that acts as style. Set them if the user asked for a specific style or to clear a default prefix.
4. **Optionally set a new seed** — find sampler-like nodes (e.g. `KSampler`, `BasicGuider`, or any node with a `seed` input) and set `seed` to a new random integer so each run can differ.
5. Write the modified workflow to a temp file (e.g. `skills/comfyui/assets/tmp-workflow.json`). Use `~/ComfyUI/venv/bin/python` for any inline Python; do not use bare `python`.
6. Run: `comfyui_run.py --workflow <path-to-edited-json>`.

If the workflow structure is unclear or you can’t find prompt/sampler nodes, run the file as-is and only change what you can reliably identify. Same approach for arbitrary user-supplied JSON: inspect first, edit at your best knowledge, then run.
Confidence
84% confidence
Finding
The skill tells the agent to write edited workflows to a persistent path under the skill assets directory, which can leave behind user-derived prompts, seeds, and workflow modifications across sessions. While not inherently malicious, this creates session persistence and possible cross-user data leakage or stale-state reuse if later runs consume the leftover file.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill directs the agent to clone a repository, install dependencies, download model weights from arbitrary URLs, and potentially install pget, yet it does not require an explicit warning or confirmation about network, execution, disk, and persistence side effects. That is dangerous because a user may believe they are only generating an image while the agent performs software installation and fetches untrusted content onto the host.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The script adds a capability to fetch remote model weights and modify the local ComfyUI installation, which exceeds the declared scope of a skill meant to run local ComfyUI workflows via HTTP. This scope expansion matters because it introduces network access, remote content ingestion, and filesystem writes that users and reviewers may not expect from the skill metadata.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = [pget_bin, "multifile", manifest_path]
    if overwrite:
        cmd.append("-f")
    r = subprocess.run(cmd)
    return r.returncode == 0
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The markdown states that the agent will edit workflow JSON and write a temporary workflow file, which affects files on disk. The description does not include any caution or user-facing warning about this file-writing behavior or where the modified file will be stored/overwritten.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
The workflow includes natural-language prompt content that explicitly specifies "Latina female" as the generated subject. This bakes a demographic choice into the skill behavior without offering user opt-in or indicating that the workflow is intentionally region- or audience-specific, which can violate organizational language/policy expectations around user choice.

Static analysis

No suspicious patterns detected.