Back to skill

Security audit

ChatGPT Image Tailnet

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent for remote ChatGPT image generation, but it controls a remote browser session through a broadly trusted tailnet HTTP API without clear endpoint-level safeguards.

Install only if you own and trust the tailnet browser host, its ChatGPT session, and the network controls around port 9377. Treat prompts and generated images as sent through that remote browser and saved to disk; restrict tailnet ACLs/firewall rules and avoid sensitive prompts unless endpoint authentication and session isolation are added.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/chatgpt_image_tailnet.py:13
Finding
Unauthenticated Remote Browser Control over Plain HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/chatgpt_image_tailnet.py`, lines 13-27 **Vulnerability Type**: Remote browser API lacking application-level authentication and TLS **Risk Level**: High ### Complete Code Snippet ```python DEFAULT_BASE = "http://100.89.48.48:9377" CHATGPT_URL = "https://chatgpt.com/" def request(base, method, path, params=None, body=None, timeout=60): url = base.rstrip("/") + path if params: url += "?" + urllib.parse.urlencode(params) data = None headers = {"Content-Type": "application/json"} if body is not None: data = json.dumps(body).encode() req = urllib.request.Request(url, data=data, headers=headers, method=method) with urllib.request.urlopen(req, timeout=timeout) as resp: raw = resp.read().decode() return json.loads(raw) if raw else {"ok": True} ``` ### Technical Analysis The default browser-control endpoint uses plain HTTP, and requests contain no authorization token, client certificate, request signature, or other application-level authentication mechanism. The API is used for sensitive operations including creating browser tabs, reading page snapshots, entering user prompts, executing JavaScript in an authenticated page, and retrieving downloaded files. The endpoint is a private Tailscale address, which limits ordinary Internet exposure and normally provides encrypted transport between authorized tailnet nodes. However, tailnet membership alone does not create endpoint-level authorization. A compromised or excessively privileged tailnet peer could potentially connect directly to the service if Tailscale ACLs or host firewall rules do not restrict it. Plain HTTP also leaves the application without independent TLS identity verification or protection if the endpoint is accessed outside the expected protected tunnel. The `--base` option can redirect all browser-control traffic to an arbitrary HTTP endpoint. Because server responses are trusted ...[truncated 1905 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Serve the browser API over HTTPS and validate the server certificate. For private infrastructure, use a private CA or mutually authenticated TLS. 2. Require a scoped API credential for every request. Avoid relying exclusively on possession of tailnet access. 3. Limit the credential to the required operations and browser profile. In particular, tightly restrict the general-purpose JavaScript evaluation endpoint. 4. Configure Tailscale ACLs and the remote host firewall so only the specific automation host can reach port `9377`. 5. Bind the service to the narrowest appropriate network interface and avoid exposing it on public or unrelated private interfaces. 6. Validate `--base` against an explicit allowlist of trusted HTTPS origins, or require a deliberate override before connecting to a non-allowlisted endpoint. 7. Do not place credentials in command-line arguments. Load them from a protected secret store or environment variable and send them through an authorization header. 8. Add server-side audit logging, request rate limits, credential rotation, and alerts for unexpected clients or sensitive browser operations. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/chatgpt_image_tailnet.py:139
Finding
Predictable Shared Session Identity and Uncorrelated Download Consumption<![CDATA[ ## Vulnerability Details **File Location**: `scripts/chatgpt_image_tailnet.py`, lines 139-171 **Vulnerability Type**: Insufficient session isolation and download correlation **Risk Level**: Medium ### Complete Code Snippet ```python def save_first_download(base, user, tab, output_path): for _ in range(20): out = downloads(base, user, tab, include_data=True, consume=True) items = out.get("downloads") or [] if items: item = items[0] data = item.get("dataBase64") if not data: raise RuntimeError(f"Download captured but no inline data returned: {item}") os.makedirs(os.path.dirname(output_path), exist_ok=True) with open(output_path, "wb") as f: f.write(base64.b64decode(data)) return { "outputPath": output_path, "suggestedFilename": item.get("suggestedFilename"), "mimeType": item.get("mimeType"), "bytes": item.get("bytes"), } time.sleep(1) raise RuntimeError("No download captured from generated image") def default_output_path(): ts = datetime.now(UTC).strftime("%Y%m%d-%H%M%S") return os.path.join(os.path.dirname(__file__), "..", "generated", f"chatgpt-image-{ts}.png") def main(): ap = argparse.ArgumentParser(description="Generate and download a ChatGPT image through the remote Camoufox browser") ap.add_argument("prompt", help="Prompt to send to ChatGPT image generation") ap.add_argument("--base", default=DEFAULT_BASE) ap.add_argument("--user", default="lotfi") ap.add_argument("--session", default="chatgpt-image-helper") ``` ### Technical Analysis All default invocations use the same predictable user and session identifiers. The download retrieval logic requests downloads with `consume=True` and immediately accepts `items[0]`. It does not verify that the item was created after the current image-generation r ...[truncated 2476 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate a cryptographically random session key for each invocation unless deliberate session reuse is required for authentication state. 2. Separate the persistent authenticated browser profile from per-job session and tab identifiers. 3. Record the download queue state or a server timestamp before triggering the image download, and only accept an item created afterward. 4. Add a unique request identifier to the generated download filename and require an exact match when selecting the result. 5. Request downloads with `consume=False` first. Validate ownership and content before making a separate request to consume the confirmed item. 6. Validate the reported MIME type against an allowlist such as `image/png`, impose a maximum decoded size, and verify the decoded file signature and image structure. 7. Correlate downloads to the exact tab and job on the server side rather than selecting the first queue entry. 8. Prevent concurrent clients from controlling the same tab, or enforce a per-tab lock for the complete generate-and-download operation. 9. Use collision-resistant default output names, such as a timestamp combined with a random identifier, to prevent simultaneous invocations from selecting the same path. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Content
e": item.get("mimeType"),
                "bytes": item.get("bytes"),
            }
        time.sleep(1)
    raise RuntimeError("No download captured from generated image")


def default_output_path():
    ts = datetime.now(UTC).strftime("%Y%m%d-%H%M%S")
    return os.path.join(os.path.dirname(__file__), "..", "generated", f"chatgpt-image-{ts}.png")


def main():
    ap = argparse.ArgumentParser(description="Generate and download a ChatGPT image through the remote Camoufox browser")
    ap.add_argument("prompt", help="Prompt to send to ChatGPT image generation")
    ap.add_argument("--base", default=DEFAULT_BASE)
    ap.add_argument("--user", default="lotfi")
    ap.add_argument("--session", default="chatgpt-image-helper")
    ap.add_argument("--output", default=None)
    ap.add_argument("--timeout", type=int, default=180)
    args = ap.parse_args()

    output = os.path.abspath(args.output or default_output_path())
    try:
        tab = open_tab(args.base, args.user, args.session, C
Confidence
80% confidence
Finding
YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill instructs use of a remote browser over a tailnet IP and performs networked actions, but the manifest declares no explicit tool scope or allowed-tools restrictions. That weakens least-privilege controls and can cause the agent to invoke broader network capabilities than a user would reasonably expect, especially when interacting with a remote residential browser and external services.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill explicitly describes downloading generated images and saving or sending them, but the user-facing description does not clearly warn that files will be written to disk. This can surprise users, lead to unintended storage of potentially sensitive or policy-relevant content, and increase risk if saved files are later accessed, synced, or retained longer than expected.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script sends user-provided prompt content through remote browser-control HTTP endpoints and then to ChatGPT, but the code does not provide a visible warning, confirmation, or privacy notice about that transmission. Network transmission of user data is in scope for missing-warning checks on code files.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This code creates directories and writes a downloaded image file to the local filesystem, but there is no confirmation prompt, warning comment, docstring disclosure, or user-facing log indicating that a file will be created. For a code file, file writes are safety-relevant operations that should have some visible disclosure unless clearly disclosed elsewhere.

Static analysis

No suspicious patterns detected.