Back to skill

Security audit

image

Security checks for vulnerabilities and agentic risk

Overview

This ComfyUI skill has a real image-workflow purpose, but it also gives the agent broad download and local-write authority, including automatic installation and execution of an unverified external downloader.

Review before installing. Use this only if you are comfortable letting an agent modify workflows, write into ~/ComfyUI, contact arbitrary model URLs, and possibly install pget into ~/.local/bin. Prefer running downloads with --no-pget, only use trusted HTTPS model sources, avoid --overwrite, and inspect or remove bundled workflow assets with unexpected sensitive prompts.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/download_weights.py:96
Finding

Encoded Path Traversal Allows Arbitrary File Writes

Content
View full analysis

Vulnerability Details

File Location: scripts/download_weights.py, lines 96–121 and 137–149
Vulnerability Type: Path traversal and unrestricted 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)
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:

...[truncated 2343 chars]
Remediation
View remediation

Remediation Suggestions

  • Decode the URL path before extracting the final filename, then reduce it to a single safe basename.
  • Reject filenames containing /, \, NUL characters, . or .. path components, control characters, or platform-specific separators.
  • Resolve the model directory and proposed destination with pathlib.Path.resolve().
  • Verify confinement with destination.is_relative_to(model_directory) on supported Python versions, or use a reliable os.path.commonpath() comparison.
  • Generate a safe local filename when the remote filename is missing or invalid.
  • Create downloads using exclusive file creation where practical and require explicit confirmation before replacing existing files.
  • Write to a secure temporary file inside the validated destination directory, verify the result, and atomically rename it into place.
  • Apply the same validated path to both the built-in downloader and the pget manifest.
  • Add regression tests covering encoded / and \ separators, encoded .., absolute paths, mixed encoding, and platform-specific traversal forms.

T03 · Remote Payload Retrieval and Execution

Warning
Location
scripts/download_weights.py:18
Finding

Unpinned Remote Binary Is Automatically Downloaded and Executed

Content
View full analysis

Vulnerability Details

File Location: scripts/download_weights.py, lines 18 and 60–90, with execution at lines 126–130
Vulnerability Type: Unverified remote payload retrieval and execution
Risk Level: Medium

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
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 not already available, the script downloads a ...[truncated 1918 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not automatically download and execute a binary as an implicit part of a model download.
  • Prefer the built-in downloader by default and require explicit user approval before installing an external executable.
  • Pin pget to a specific audited version rather than using a mutable latest URL.
  • Maintain platform-specific SHA-256 or stronger cryptographic digests in the reviewed source code.
  • Download to a secure temporary file, verify its digest and any available publisher signature, and only then install and execute it.
  • Validate the final redirect destination and reject unexpected hosts or insecure redirects.
  • Use a package manager or trusted dependency mechanism that provides integrity and provenance verification where available.
  • Avoid installing into a general executable directory automatically. Use a skill-specific cache directory with restrictive permissions.
  • Document the exact external dependency version and provide a reproducible update process for digest changes.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_weights.py:96
Finding

Unrestricted Download URLs Permit Local File Access and Server-Side Request Forgery

Content
View full analysis

Vulnerability Details

File Location: scripts/download_weights.py, lines 96–121 and 137–149
Vulnerability Type: Unrestricted URL scheme and destination access
Risk Level: Medium

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)
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 ch
...[truncated 2548 chars]
Remediation
View remediation

Remediation Suggestions

  • Permit only https URLs by default and reject file, ftp, data, and other schemes.
  • Reject embedded credentials and unexpected ports unless the user explicitly authorizes them.
  • Resolve hostnames before connecting and block loopback, private, link-local, multicast, reserved, and unspecified IP ranges.
  • Account for IPv4, IPv6, alternative textual representations, and DNS rebinding.
  • Disable redirects or validate the scheme, hostname, port, and resolved address again after every redirect.
  • Optionally maintain an allowlist of trusted model-hosting domains.
  • Separate an explicit local-file import feature from remote downloading if local imports are legitimately required.
  • Apply equivalent restrictions when using pget; otherwise, use only the hardened built-in client.
  • Add tests for file://, localhost, private IPv4 ranges, IPv6 loopback and link-local addresses, redirects to blocked destinations, and hostname resolution changes.
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 (13)

Self-Modification

High
Category
Rogue Agent
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.

Content

Scanner excerpt · CONTRIBUTING.md (reported line 48)May include surrounding context.

md
## 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 SkillBoss Hub.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared purpose is to run local ComfyUI workflows, but the skill also downloads arbitrary remote files, installs an external binary, and writes model files into the local ComfyUI tree. That mismatch can cause an agent or reviewer to underappreciate the real risk surface, enabling supply-chain exposure and unexpected system modification under a benign-sounding skill description.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

This file adds capability to fetch arbitrary URLs and write files into the local ComfyUI models tree, which materially expands the skill beyond its declared scope of invoking local ComfyUI workflows over HTTP. Scope mismatch is dangerous because it enables unreviewed network access and host filesystem modification, increasing the chance of supply-chain abuse, unauthorized model placement, or operational policy bypass.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The script downloads an executable from a remote GitHub release URL, writes it to ~/.local/bin, marks it executable, and then runs it. That creates an unnecessary remote code execution and persistence path for a skill whose stated purpose is only to run local ComfyUI workflows via HTTP API, so any compromise of the download source, network path, or release artifact could execute attacker-controlled code on the host.

Content

No source excerpt is available for this finding.

Self-Modification

High
Category
Rogue Agent
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.

Content

Scanner excerpt · scripts/download_weights.py (reported line 173)May include surrounding context.

python
ap.add_argument(
        "--overwrite",
        action="store_true",
        help="Overwrite existing files",
    )
    ap.add_argument(
        "--no-pget",

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill instructs reading files, invoking shell commands, and using network access, but it declares no explicit tool scope or permission boundaries. That increases the chance an agent can perform filesystem and network actions without clear policy constraints, especially because the skill also handles user-supplied workflow paths and URLs.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The activation conditions include generic image-generation prompts, so the skill may trigger on ordinary requests that do not explicitly ask to use local ComfyUI or modify the host environment. In this skill, over-broad activation is more dangerous because triggering it can lead to local file access, shell execution, service startup, or model downloads.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
76% confidence
Finding

The skill persists a modified workflow to a predictable temp-like path inside the skill assets directory, which can leave prompt content or user-supplied workflow modifications on disk across sessions. While not as severe as code execution, this can leak sensitive prompts or cause later runs to reuse stale state unexpectedly.

Content

Scanner excerpt · SKILL.md (reported line 25)May include surrounding context.

md
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.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill tells the agent to download model weights from arbitrary URLs and to install pget automatically if missing, but it does not require an explicit warning or confirmation before making persistent filesystem changes or introducing new executables. This creates clear supply-chain and local-environment risks, especially when the URLs may come directly from the user.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The default workflow hard-codes an ethnicity-specific prompt ('Latina female') without any user choice, context, or justification. In an image-generation skill, this can bias outputs, steer users toward demographic-specific depictions they did not request, and create fairness and representation concerns even though it is not a code-execution or system-compromise issue.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The bundled default workflow contains an explicit nude-generation prompt ('Adult woman, nude...') that is not disclosed in the skill description. This is dangerous because invoking the default asset could unexpectedly generate sexual content, creating safety, compliance, and trust issues for downstream users or systems that assume a generic image workflow.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/download_weights.py (reported line 127)May include surrounding context.

python
cmd = [pget_bin, "multifile", manifest_path]
    if overwrite:
        cmd.append("-f")
    r = subprocess.run(cmd)
    return r.returncode == 0

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

This markdown file states that the agent will edit workflow JSON and write a temp workflow file, which affects the user's local filesystem. The description does not include any caution or user-facing warning about where the file is written or that local files will be modified.

Content

No source excerpt is available for this finding.