Back to skill

Security audit

Comfyui Flux

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches local ComfyUI image generation, but it also handles face-reference images and can send prompts/images to any COMFYUI_URL without enough scoping or privacy warning.

Install only after reviewing the PuLID behavior. Keep COMFYUI_URL pointed at a trusted local ComfyUI instance, do not use reference face images without consent, avoid sensitive prompts or files, and be careful with custom --output paths because downloaded image bytes are written there.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (18)

Tainted flow: 'req' from os.environ.get (line 104, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
data=data,
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read().decode())
Confidence
93% confidence
Finding
The code builds outbound HTTP requests using COMFYUI_URL from an environment variable with no validation or localhost restriction. If an attacker can influence the environment, the tool will send the full workflow and user prompt to an arbitrary host and interact with that host as if it were trusted ComfyUI, creating SSRF-style behavior and prompt/data exfiltration risk.

Tainted flow: 'req' from os.environ.get (line 173, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
"""Check if ComfyUI is running."""
    try:
        req = urllib.request.Request(f"{COMFYUI_URL}/system_stats")
        with urllib.request.urlopen(req, timeout=5) as resp:
            return True
    except Exception:
        return False
Confidence
92% confidence
Finding
The health check sends a request to COMFYUI_URL/system_stats, where COMFYUI_URL is attacker-controllable via environment variables. While this call does not send the full prompt, it still enables unauthorized network access to arbitrary internal or external endpoints and can be used as a probe in SSRF scenarios.

Tainted flow: 'req' from os.environ.get (line 173, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
elapsed = int(time.time() - start)
        try:
            req = urllib.request.Request(f"{COMFYUI_URL}/history/{prompt_id}")
            with urllib.request.urlopen(req, timeout=10) as resp:
                history = json.loads(resp.read().decode())
        except Exception:
            continue
Confidence
93% confidence
Finding
The polling logic repeatedly requests COMFYUI_URL/history/{prompt_id} without validating the base URL. This creates a repeated SSRF primitive to an attacker-chosen endpoint and may expose prompt identifiers and execution metadata to an untrusted server over many requests.

Tainted flow: 'req' from os.environ.get (line 173, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
})
            url = f"{COMFYUI_URL}/view?{params}"
            req = urllib.request.Request(url)
            with urllib.request.urlopen(req, timeout=60) as resp:
                with open(output_path, "wb") as f:
                    f.write(resp.read())
            return True
Confidence
95% confidence
Finding
The download step fetches binary content from a URL derived from untrusted COMFYUI_URL and writes it directly to a user-specified output path. An attacker controlling the endpoint can cause the tool to retrieve arbitrary content, which may result in malicious file placement, misleading outputs, or data flow to and from an untrusted remote service.

Tainted flow: 'req' from os.environ.get (line 190, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        result = json.loads(resp.read().decode())

    uploaded_name = result.get("name", filename)
Confidence
91% confidence
Finding
The script builds outbound requests from COMFYUI_URL, which is taken directly from the environment without validation. If an attacker can influence that variable, the skill will upload the user's reference face image and workflow data to an arbitrary host, creating an SSRF/exfiltration path; because the data includes biometric face imagery, the privacy impact is elevated.

Tainted flow: 'req' from os.environ.get (line 190, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
data=data,
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read().decode())
Confidence
88% confidence
Finding
The workflow submission request also uses COMFYUI_URL from the environment, so a manipulated endpoint can receive user prompts and generation parameters. While this call may not include the image bytes directly, it still leaks potentially sensitive prompt content and enables remote control of where the job is executed.

Tainted flow: 'req' from os.environ.get (line 294, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
"""Check if ComfyUI is running."""
    try:
        req = urllib.request.Request(f"{COMFYUI_URL}/system_stats")
        with urllib.request.urlopen(req, timeout=5):
            return True
    except Exception:
        return False
Confidence
80% confidence
Finding
Even the health check sends a request to an environment-controlled URL, which can be abused for SSRF-like probing of internal or attacker-chosen endpoints. The direct effect is smaller than the upload path, but it still allows unintended network access and may disclose that the script is running.

Tainted flow: 'req' from os.environ.get (line 294, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
elapsed = int(time.time() - start)
        try:
            req = urllib.request.Request(f"{COMFYUI_URL}/history/{prompt_id}")
            with urllib.request.urlopen(req, timeout=10) as resp:
                history = json.loads(resp.read().decode())
        except Exception:
            continue
Confidence
85% confidence
Finding
Polling history against an unvalidated environment-supplied base URL continues the same unsafe trust boundary and can repeatedly contact an attacker-controlled service. This expands the exfiltration/SSRF surface and may leak prompt IDs and job metadata over time.

Tainted flow: 'req' from os.environ.get (line 294, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
)
            url = f"{COMFYUI_URL}/view?{params}"
            req = urllib.request.Request(url)
            with urllib.request.urlopen(req, timeout=60) as resp:
                with open(output_path, "wb") as f:
                    f.write(resp.read())
            return True
Confidence
93% confidence
Finding
Downloading the generated image from an environment-controlled URL lets an attacker supply arbitrary response bytes that the script writes to a user-chosen local path. This creates a file-write sink from untrusted network content and, combined with arbitrary output paths, can overwrite files or place unexpected content on disk.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill documentation indicates use of environment variables and network access to a local ComfyUI server, but the skill metadata does not declare those capabilities. Undeclared capabilities weaken reviewability and user consent because the skill can read runtime configuration and send data to a service without that being clearly surfaced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The manifest says the skill is for local image generation on localhost:8188 and suggests using a different skill for face-consistent selfies, but the documentation adds face-reference generation, local file upload behavior, and a different default endpoint on port 8200. This mismatch can mislead operators about what the skill actually does, increasing the chance of unintended biometric data handling or sending local images to an unexpected service.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The file broadens the skill from ordinary local image generation to face-consistent generation using a reference face, despite the metadata saying users should prefer another skill for that purpose. This scope expansion matters because face-reference workflows involve more sensitive personal data and different risk expectations than generic image generation.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The skill metadata and documentation say ComfyUI should be on localhost:8188, but the code defaults to 127.0.0.1:8200 and allows arbitrary COMFYUI_URL override. This discrepancy increases the chance that operators unknowingly point the tool at the wrong service or a non-local endpoint, which amplifies the SSRF and data exfiltration risks in this skill.

Description-Behavior Mismatch

Medium
Confidence
84% confidence
Finding
The file adds face-consistent selfie generation capability, which exceeds the manifest's stated scope and materially changes the risk profile by enabling biometric/identity-linked image generation. In this context, scope drift is security-relevant because users and reviewers may not expect handling of reference face images or identity-preserving outputs.

Intent-Code Divergence

Medium
Confidence
81% confidence
Finding
The module docstring frames face-consistent image generation as the script's core purpose, conflicting with the manifest guidance that suggests using another skill for face-consistent selfies. This mismatch can mislead operators about what the skill does and reduces the chance that biometric/privacy implications are reviewed appropriately.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill documents generating images from a reference face image without any warning about biometric sensitivity, consent, storage, or downstream transmission to the ComfyUI service. In context, this is more dangerous because the workflow explicitly uses face images, which are sensitive personal data and can be misused for impersonation or non-consensual image synthesis.

Natural-Language Policy Violations

High
Confidence
94% confidence
Finding
Advertising 'No content censorship — can generate anything locally' signals unrestricted generation without safeguards and encourages unsafe or policy-violating use. In an image-generation skill, this increases the risk of abusive outputs, including non-consensual or illegal content, and suggests the absence of moderation or usage boundaries.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script uploads a reference face image to ComfyUI over plain HTTP by default and provides no meaningful privacy warning beyond progress output. Because the data is biometric and identity-bearing, sending it over an unencrypted or potentially non-local connection materially increases exposure to interception, unintended retention, or silent exfiltration.

Static analysis

No suspicious patterns detected.