Back to skill

Security audit

Openai Image Gen 1.0.1

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but it silently allows an environment variable to redirect the OpenAI API key and prompts to another endpoint.

Install only if you are comfortable running a local script that sends prompts and an OpenAI API key over the network. Before use, make sure OPENAI_BASE_URL and OPENAI_API_BASE are unset or trusted, avoid sensitive prompts, and be cautious opening generated galleries made from untrusted prompt text.

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/gen.py:29
Finding
OpenAI API Credential Can Be Sent to an Arbitrary Environment-Controlled Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.py:29-40`, with credential transmission at `scripts/gen.py:102-108` and invocation at `scripts/gen.py:174-190` **Vulnerability Type**: Unrestricted credential transmission to a configurable 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" ``` The selected API key is placed in an authorization header without validating the destination: ```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", ) try: with urllib.request.urlopen(req, timeout=timeout_s) as resp: raw = resp.read() ``` The environment-derived URL and API key are then used together: ```python api_key = args.api_key or os.environ.get("OPENAI_API_KEY") if not api_key: print("missing OPENAI_API_KEY (or --api-key)", file=sys.stderr) return 2 # ... url = _api_url() items: list[dict] = [] for i, prompt in enumerate(prompts, 1): payload = { "model": args.model, "prompt": prompt, "size": args.size, "quality": args.quality, "n": 1, "response_format": "b64_json", } data = _post_json(url=url, api_key=api_key, payload=payload, timeout_s=args.timeout) ``` ### Technical Analysis The script accepts `OPENAI_BASE_URL` and `OPENAI_API_BASE` as authoritative network destinations. It does not validate that the resulting URL: - Uses HTTPS. - Resolves to ...[truncated 2097 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Default to a fixed endpoint such as `https://api.openai.com/v1/images/generations`. 2. If custom endpoints are unnecessary, remove support for `OPENAI_BASE_URL` and `OPENAI_API_BASE`. 3. If compatible endpoints are required: - Require explicit command-line opt-in rather than silently trusting ambient environment variables. - Parse the URL with `urllib.parse.urlsplit`. - Require the `https` scheme. - Maintain an explicit allowlist of approved hostnames and ports. - Reject embedded credentials, fragments, unexpected paths, loopback addresses, and private-network destinations. 4. Use a separate credential for each compatible provider instead of automatically forwarding `OPENAI_API_KEY`. 5. Display the destination hostname and request user confirmation when a non-default provider is selected. 6. Document all custom endpoint behavior and the fact that prompts and credentials will be transmitted to the selected provider. 7. Consider using the official OpenAI client with a pinned, validated base URL and established transport protections. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gen.py:135
Finding
Unescaped Prompt Content Enables Script Injection in the Generated HTML Gallery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.py:135-149` **Vulnerability Type**: HTML injection and local stored cross-site scripting **Risk Level**: Medium ### Vulnerable Code ```python def _write_index(out_dir: str, items: list[dict]) -> None: html = [ "<!doctype html>", "<meta charset='utf-8'>", "<meta name='viewport' content='width=device-width, initial-scale=1'>", "<title>openai-image-gen</title>", "<style>", "body{font-family:ui-sans-serif,system-ui;margin:24px;max-width:1060px}", ".card{display:grid;grid-template-columns:220px 1fr;gap:16px;align-items:start;margin:18px 0}", "img{width:220px;height:220px;object-fit:cover;border-radius:14px;box-shadow:0 14px 38px rgba(0,0,0,.14)}", "pre{white-space:pre-wrap;margin:0;background:#111;color:#eee;padding:12px 14px;border-radius:14px;line-height:1.35}", "</style>", "<h1>openai-image-gen</h1>", ] 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>") with open(os.path.join(out_dir, "index.html"), "w", encoding="utf-8") as f: f.write("\n".join(html)) ``` ### Technical Analysis Prompt content accepted through the repeatable `--prompt` option is stored verbatim in each item and interpolated directly into `index.html`. The code does not apply context-appropriate HTML escaping before inserting the prompt between `<pre>` tags. An attacker-controlled prompt can terminate the `<pre>` element and inject arbitrary HTML or JavaScript. For example, a value structurally equivalent to the following would introduce executable markup: ```html </pre><script>/* attacker-controlled JavaScript */</script><pre> ``` The generated gallery is intended to be opened in a browser according to `SKILL.md`, so the injection reaches an execution s ...[truncated 1719 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Import Python's standard HTML-escaping utility: ```python import html ``` 2. Escape every dynamic value according to its HTML context: ```python safe_file = html.escape(it["file"], quote=True) safe_prompt = html.escape(it["prompt"], quote=True) html_parts.append(f"<a href='{safe_file}'><img src='{safe_file}'></a>") html_parts.append(f"<pre>{safe_prompt}</pre>") ``` 3. Prefer a template engine with automatic escaping enabled rather than constructing HTML through string interpolation. 4. Continue restricting generated filenames to a conservative character set and ensure paths cannot contain directory traversal sequences. 5. Add tests using prompts containing `<`, `>`, `&`, single quotes, double quotes, closing tags, and script elements. 6. Add a restrictive Content Security Policy to the generated document as defense in depth, for example prohibiting scripts and limiting network destinations. Escaping remains mandatory because CSP alone does not safely render attacker-controlled text. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • 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 (4)

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
95% confidence
Finding
The skill invokes a Python script that uses an API key from the environment and sends data over the network, but the skill manifest declares no explicit tool scope or permissions. This creates an authorization and transparency gap: users and hosting frameworks cannot easily tell that the skill requires secret access and outbound network capability before running it.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill documentation tells users how to generate prompts and images but does not clearly warn that prompts and generated outputs are transmitted to the OpenAI Images API and that results are stored on local disk. In a prompt-generation workflow, users may supply sensitive or proprietary text, so the lack of disclosure increases the risk of unintended data exfiltration to a third party and persistent local storage of potentially sensitive content.

Static analysis

No suspicious patterns detected.