Back to skill

Security audit

Run ComfyUI on Runninghub(e.g.photo-retouching)

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its RunningHub API purpose, but it also includes under-disclosed Chrome browser control and insecure API-key storage guidance that users should review before installing.

Review before installing. Use an environment variable instead of --save-key, avoid passing API keys on the command line, and only upload images you are comfortable sending to RunningHub. Remove or avoid running chrome_automation.py unless you intentionally use Chrome remote debugging and understand it can act in a logged-in browser context. Rotate any API key previously saved in a shared or backed-up workspace.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/runninghub_client.py:27
Finding

RunningHub API Key Stored in a Plaintext Configuration File

Content
View full analysis

Vulnerability Details

File Location: scripts/runninghub_client.py, lines 27-35 and 253-258
Vulnerability Type: Plaintext credential storage with permissions inherited from the environment
Risk Level: Medium

Vulnerable Code

python
def save_config(config: Dict[str, Any]):
    """Save configuration to file"""
    try:
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump(config, f, indent=2, ensure_ascii=False)
        return True
    except Exception as e:
        print(f"❌ Config file save failed: {e}")
        return False
python
if args.save_key:
    config = load_config()
    config["api_key"] = args.save_key
    if save_config(config):
        print("✅ API key saved to config file")
        print(f"📁 Config file: {CONFIG_FILE}")
    return

Technical Analysis

The --save-key operation stores the RunningHub bearer credential directly in config.json as unencrypted JSON. The application does not explicitly create the file with owner-only permissions, validate its ownership, reject symbolic links, or verify its permissions before subsequently loading it.

The effective permissions are therefore determined by the process umask or by the permissions of an existing file. In a shared, broadly readable, backed-up, or otherwise compromised workspace, another local principal or process may be able to recover the credential. Writing through an existing symbolic link may also affect an unintended file when the program runs with sufficient permissions.

The documentation recommends this storage mechanism. It also presents --api-key as an alternative, which may expose the credential in shell history and, depending on the operating system, command-line process metadata.

The key's transmission to https://www.runninghub.ai is consistent with the Skill's declared cloud workflow functionality. The vulnerability is its local storage and input handling, not the necessary authenticated API request itse ...[truncated 1430 chars]

Remediation
View remediation

Remediation Suggestions

  1. Prefer an operating-system credential store or secret-management service instead of a plaintext project file.
  2. Retain environment-variable support and remove or discourage command-line secret arguments because command lines may be recorded in shell history or process metadata.
  3. If file-based persistence remains necessary, create the secret file atomically with owner-only mode 0600, rather than relying on the ambient umask.
  4. Before reading or replacing the file, use lstat-style checks to reject symbolic links, verify that the current user owns it, and reject group-readable or world-readable permissions.
  5. Store secrets outside the project and shared workspace, such as in a user-specific configuration directory with mode 0700.
  6. Ensure config.json is excluded from version control, packaging, diagnostics, logs, and backups where practical.
  7. Avoid printing secret values and redact authentication data from exceptions or debug output.
  8. Document credential revocation and rotation procedures. Users who previously persisted keys insecurely should rotate them after applying the fix.
  9. Use narrowly scoped and short-lived credentials if RunningHub supports them.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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 (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

This is a material description/behavior mismatch. The declared purpose emphasizes RunningHub API-based ComfyUI workflow execution and lifecycle operations. The actual code does not call a RunningHub API for workflow submission or status/result retrieval. Instead, it automates a browser page by creating a new tab through Chrome's debugging endpoint, performing a WebSocket handshake to CDP, enabling DOM/Page/Runtime domains, and running JavaScript to look for file inputs and upload-related buttons. Its primary purpose is browser automation for workflow page interaction, specifically investigating upload behavior, not executing workflows via API. The access pattern is also inconsistent with the declaration because it relies on local localhost Chrome debugging resources rather than just the RunningHub cloud API.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The code is clearly related to RunningHub workflow automation and does use the API to submit tasks, query status, and retrieve results, so it partially matches the declared purpose. However, the description overstates the capability. The script is not a general-purpose executor for RunningHub ComfyUI workflows; it is a narrow utility for one specific hard-coded workflow and a hybrid process where some workflow interaction may need to happen manually through the web UI. It also does not expose meaningful workflow configuration, since submission uses a fixed workflow ID and an empty nodeInfoList. Therefore, the description does not accurately represent the actual behavior closely enough and should be flagged as a mismatch.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

python
from runninghub_client import RunningHubClient, load_config, get_api_key

# Get API key from config
api_key = get_api_key()

# Initialize client

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding

The skill documentation describes capabilities involving environment variables, local file access, file writes, and outbound network use, but it does not declare any tool scope or permission boundaries. In an agent setting, missing explicit scope increases the chance the skill will be invoked with broader-than-necessary access, making credential leakage, unintended file access, or unauthorized network transmission harder to constrain.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill instructs users to upload local image files and send API credentials to a third-party cloud service, but it does not prominently warn that local content will leave the host and may be stored, logged, or retained externally. In an agent context, this can lead to accidental disclosure of sensitive images, embedded metadata, or secrets to an external platform.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The module documentation says it performs automated image upload and workflow execution, but the code explicitly admits it cannot actually upload local files and does not execute the full workflow. Misleading security-relevant documentation can cause operators to approve or rely on automation they do not understand, masking the real behavior of local browser control.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This skill claims API-based RunningHub workflow execution, but the script instead opens and controls a local Chrome DevTools session to navigate and inspect a browser page. That expands capability beyond the declared scope and gives the skill indirect control over the user's browser context, which can expose authenticated sessions and enable unintended actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The script opens a new browser page through Chrome DevTools and takes control actions without any user confirmation, interactive prompt, or explicit disclosure at the moment of execution. In the context of an agent skill, silent browser control is dangerous because it can act within an existing logged-in session and perform unintended operations on behalf of the user.

Content

No source excerpt is available for this finding.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
91% confidence
Finding

The script makes an HTTP request to 127.0.0.1:9222 to create a new Chrome DevTools page. Although localhost access is sometimes legitimate, local service interaction is security-sensitive in agent contexts because it can reach privileged debugging interfaces that are not meant to be exposed to untrusted automation.

Content

Scanner excerpt · scripts/chrome_automation.py (reported line 86)May include surrounding context.

python
print("[1/5] Creating workflow page...")
    import requests

    resp = requests.put(f"http://127.0.0.1:9222/json/new?{WORKFLOW_URL}")
    page_info = resp.json()
    page_id = page_info.get("id")
    ws_url = page_info.get("webSocketDebuggerUrl")

Tainted flow: 'handshake' from requests.put (line 109, network input) → socket.socket.send (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/chrome_automation.py (reported line 110)May include surrounding context.

python
key = base64.b64encode(bytes([0x41] * 16)).decode()
    handshake = f"GET {path} HTTP/1.1\r\nHost: {host}:{port}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n"
    sock.send(handshake.encode())

    response = sock.recv(1024)
    if b"101" not in response:

Tainted flow: 'ws_url' from requests.put (line 89, network input) → socket.socket.send (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/chrome_automation.py (reported line 119)May include surrounding context.

python
print("✅ WebSocket connected successfully")

    print("\n[4/5] Enabling Chrome DevTools domains...")
    sock.send(send_ws_command(ws_url, "Runtime.enable", {}, 1))
    sock.send(send_ws_command(ws_url, "DOM.enable", {}, 2))
    sock.send(send_ws_command(ws_url, "Page.enable", {}, 3))
    time.sleep(1)

Tainted flow: 'ws_url' from requests.put (line 89, network input) → socket.socket.send (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/chrome_automation.py (reported line 120)May include surrounding context.

python
print("\n[4/5] Enabling Chrome DevTools domains...")
    sock.send(send_ws_command(ws_url, "Runtime.enable", {}, 1))
    sock.send(send_ws_command(ws_url, "DOM.enable", {}, 2))
    sock.send(send_ws_command(ws_url, "Page.enable", {}, 3))
    time.sleep(1)

Tainted flow: 'ws_url' from requests.put (line 89, network input) → socket.socket.send (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/chrome_automation.py (reported line 121)May include surrounding context.

python
print("\n[4/5] Enabling Chrome DevTools domains...")
    sock.send(send_ws_command(ws_url, "Runtime.enable", {}, 1))
    sock.send(send_ws_command(ws_url, "DOM.enable", {}, 2))
    sock.send(send_ws_command(ws_url, "Page.enable", {}, 3))
    time.sleep(1)

    while True:

Tainted flow: 'ws_url' from requests.put (line 89, network input) → socket.socket.send (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/chrome_automation.py (reported line 151)May include surrounding context.

python
})()
    """

    sock.send(
        send_ws_command(
            ws_url,
            "Runtime.evaluate",

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
89% confidence
Finding

This line performs another localhost request to the Chrome DevTools API to close the created page. While less severe than page creation and control, it still demonstrates that the skill can issue commands to an internal service, which is risky in environments where localhost services expose privileged functionality.

Content

Scanner excerpt · scripts/chrome_automation.py (reported line 205)May include surrounding context.

python
sock.close()

    requests.get(f"http://127.0.0.1:9222/json/close/{page_id}")

    print("\n" + "=" * 70)

Tainted flow: 'page_id' from requests.put (line 88, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/chrome_automation.py (reported line 205)May include surrounding context.

python
sock.close()

    requests.get(f"http://127.0.0.1:9222/json/close/{page_id}")

    print("\n" + "=" * 70)

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/hybrid_automation.py (reported line 26)May include surrounding context.

python
print("=" * 70)

    for attempt in range(max_attempts):
        resp = requests.post(
            query_url, headers=headers, json={"taskId": task_id}, timeout=10
        )

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/hybrid_automation.py (reported line 80)May include surrounding context.

python
print("=" * 70)

    try:
        resp = requests.post(url, headers=headers, json=payload, timeout=10)
        result = resp.json()

        if result.get("taskId"):

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The script's natural-language interface is effectively fixed to English-only output in its main user-facing messages, with no option to select another language or locale. The policy explicitly calls for flagging language or locale constraints when they are forced without user opt-in.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/runninghub_client.py (reported line 135)May include surrounding context.

python
}

        try:
            response = requests.post(
                url, headers=self.headers, json=payload, timeout=30
            )
            return response.json()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script supports persisting the API key in a local config.json file and does so without any warning, permission hardening, or guidance about the sensitivity of the credential. If the repo directory is shared, backed up, committed, or readable by other local users/processes, the key can be exposed and reused to access the user's RunningHub account.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

In run-with-image mode, a user-supplied local file is uploaded to a third-party cloud service without any privacy or data-handling warning at the point of use. This can lead to accidental disclosure of sensitive local images, especially in an agent/automation context where users may not realize the file leaves the local environment.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.