Back to skill

Security audit

GPT Image 2 API

Security checks for vulnerabilities and agentic risk

Overview

This image-generation skill is mostly coherent, but it needs Review because it can send API keys, prompts, and uploaded images to any configured API base URL and includes an unsafe Node.js integration example.

Install only if you are comfortable giving the skill shell access, output-file write access, and access to an image API key. Prefer the default OpenAI base URL, avoid setting GPT_IMAGE2_BASE_URL or config base_url to third-party relays unless you trust them with your API key and image content, and do not copy the Node.js execSync example into code that handles untrusted prompts.

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/gpt_image2.py:95
Finding

Unrestricted API Endpoint Can Expose Credentials and Sensitive Image Data

Content
View full analysis

Vulnerability Details

File Location: scripts/gpt_image2.py, lines 95–106, 170–221, 251, and 303
Vulnerability Type: Unvalidated destination URL and transmission of bearer credentials
Risk Level: High

Vulnerable Code

python
def get_base_url(cli_value: str | None = None, config: dict | None = None) -> str:
    """Priority: CLI > config.base_url > GPT_IMAGE2_BASE_URL env > default"""
    if cli_value:
        return cli_value.rstrip("/")
    if config is None:
        config = {}
    if config.get("base_url"):
        return config["base_url"].rstrip("/")
    env_val = os.environ.get("GPT_IMAGE2_BASE_URL", "")
    if env_val:
        return env_val.rstrip("/")
    return DEFAULT_BASE_URL
python
def _curl_auth_header(api_key: str) -> str:
    """Write Authorization header to a temp curl-config file. Caller must unlink."""
    fd, path = tempfile.mkstemp(suffix=".headers")
    with os.fdopen(fd, "w") as f:
        f.write(f'-H "Authorization: Bearer {api_key}"\n')
    return path
python
url = f"{base_url}/images/generations"
python
url = f"{base_url}/images/edits"

Technical Analysis

The API base URL can originate from the --base-url argument, a configuration file, or the GPT_IMAGE2_BASE_URL environment variable. The value is used without validating its scheme, hostname, port, embedded credentials, or trust level.

Every request to the resulting endpoint includes an Authorization: Bearer header containing the resolved API key. Generation requests also transmit the prompt, while edit requests can transmit the prompt, source image, and mask. Consequently, a malicious or accidentally unsafe endpoint receives both the credential and potentially private user content.

In particular, the implementation permits:

  • Plain HTTP endpoints, exposing credentials and content to network interception.
  • Arbitrary attacker-controlled HTTPS host ...[truncated 1673 chars]
Remediation
View remediation

Remediation Suggestions

  1. Parse the endpoint with urllib.parse.urlparse and reject malformed URLs.
  2. Require the https scheme. Permit plain HTTP only through an explicit, prominently warned development-only option.
  3. Use an allowlist containing the official API hostname by default.
  4. Require explicit user confirmation or a dedicated --allow-third-party-endpoint option before sending credentials to other hosts.
  5. Reject URLs containing embedded usernames or passwords, fragments, control characters, and unsupported schemes.
  6. Consider binding credentials to specific endpoints so an OpenAI credential is never automatically sent to a relay host.
  7. Document that custom endpoints receive prompts, credentials, source images, and masks.
  8. Add automated tests covering HTTP URLs, malformed URLs, embedded credentials, unexpected ports, redirects, and unapproved hosts.
  9. Consider disabling or tightly controlling redirects so a trusted endpoint cannot redirect an authenticated request to an untrusted destination.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:205
Finding

Shell Command Injection in Documented Node.js Integration

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 205–206
Vulnerability Type: Shell command injection through unsafe integration guidance
Risk Level: Medium

Vulnerable Code

javascript
const { execSync } = require('child_process');
const output = execSync(`python3 ${scriptPath} generate "${prompt}" -o ${outputPath}`);

Technical Analysis

The documented Node.js integration constructs a shell command by directly interpolating scriptPath, prompt, and outputPath into a template string passed to execSync. By default, execSync executes the string through a shell.

Quoting only the prompt with double quotes does not make it safe. A prompt containing a closing quote followed by shell syntax can escape the intended argument. Shell substitutions and metacharacters may also be interpreted depending on the platform. The script and output paths are entirely unquoted, creating additional injection and argument-splitting opportunities.

Prompts are likely to include user-controlled or remotely supplied text in agent integrations. An application that copies this documented example can therefore convert untrusted image-generation text into local command execution. The Python CLI itself uses argument arrays for its subprocess.run calls and does not exhibit the same shell-injection behavior; the issue is specifically the unsafe integration pattern provided in the documentation.

Attack Path

  1. A developer integrates the Skill using the documented Node.js execSync example.
  2. An attacker gains control over, or can influence, the image prompt. Alternatively, the attacker influences scriptPath or outputPath.
  3. The attacker supplies a value containing quote termination and shell command syntax.
  4. The application interpolates that value into the command string without shell-safe argument handling.
  5. execSync passes the constructed string to a shell.
  6. The shell executes the injected ...[truncated 668 chars]
Remediation
View remediation

Remediation Suggestions

Replace shell-based execution with an API that accepts an argument array and does not invoke a shell:

javascript
const { execFileSync } = require('child_process');

const output = execFileSync(
  'python3',
  [scriptPath, 'generate', prompt, '-o', outputPath],
  {
    encoding: 'utf8',
    shell: false
  }
);

Alternatively, use spawnSync with the same argument-array approach. Do not attempt to solve the issue through manual quoting or escaping, because shell behavior varies across platforms and edge cases are easy to miss.

The documentation should explicitly state that prompts and paths must be passed as separate process arguments. Add a regression test using prompts and paths containing quotes, spaces, command substitutions, semicolons, and other shell metacharacters to verify they remain literal argument data.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (15)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
72% confidence
Finding

The documentation states that the script supports configurable base_url and uses curl -K temporary config files to pass authorization data. If base_url or curl config content is not strictly validated in the underlying script, an attacker could steer requests to a malicious endpoint or abuse curl config semantics to manipulate request behavior and expose secrets; the absence of tool scoping makes this more concerning in an agent setting.

Content

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

md
2. **The edits endpoint does NOT support `response_format`** — always returns b64_json regardless.
3. **gpt-image-2 generations may time out on some relay endpoints** — use `--timeout` flag (default 600s).
4. **Prompt with special characters** — the script writes prompts to temp files internally, avoiding shell escaping issues. No need to worry about quoting.
5. **Authorization header is never passed via `-H`** — the script uses curl `-K` temp config file, deleted immediately after use. Keys never appear in `ps aux`.
6. **Config file permissions** — the script warns if config has group/other read permissions. Run `chmod 600 <config>` to fix.
7. **Zero pip dependencies** — the script only requires Python 3.8+ stdlib and `curl`. No installation step needed.
8. **Chinese text in prompts may not render correctly** — gpt-image-2's Chinese rendering is unstable; it often ignores Chinese constraints and outputs English text in images. Consider using Gemini for Chinese text rendering.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
87% confidence
Finding

This curl helper supports caller-controlled base_url and transmits the Authorization bearer token to whatever endpoint is configured. In an agent skill context, that makes the tool more dangerous because a prompt or workflow could direct it to an attacker-controlled relay, causing API key exfiltration and leakage of submitted images/prompts.

Content

Scanner excerpt · scripts/gpt_image2.py (reported line 168)May include surrounding context.

python
return saved


# ── curl helpers ──────────────────────────────────────────────────

def _curl_auth_header(api_key: str) -> str:
    """Write Authorization header to a temp curl-config file. Caller must unlink."""

Self-Modification

High
Category
Rogue Agent
Confidence
85% confidence
Finding

Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Content

Scanner excerpt · scripts/gpt_image2.py (reported line 425)May include surrounding context.

python
cfg = subparsers.add_parser("config", help="Show or initialize config")
    cfg.add_argument("--init", action="store_true", help="Create template config")
    cfg.add_argument("--show", action="store_true", help="Show active config (masks keys)")
    cfg.add_argument("--force", action="store_true", help="Overwrite existing config with --init")
    cfg.add_argument("--config", help="Explicit config path (for --show)")

    args = parser.parse_args()

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill documents and encourages shell execution, file reads/writes, and environment-variable access, but it does not declare any tool scope restrictions such as permissions or allowed-tools. In an agent ecosystem, this increases the chance that an agent grants broader capabilities than necessary, enabling unintended command execution, filesystem access, or secret exposure when using the skill.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The edit command is documented earlier as python3 gpt_image2.py edit <image_path> "edit prompt", which indicates a single positional source image, yet this later workflow text states the endpoint can accept up to 4 images via repeated --image flags. That is an active contradiction in the file's own usage guidance, and could mislead an agent into invoking unsupported CLI behavior.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · SKILL.md (reported line 51)May include surrounding context.

md
from pathlib import Path
from typing import Optional

DEFAULT_BASE_URL = "https://api.openai.com/v1"

# ── Config Discovery ──────────────────────────────────────────────
# Priority: --config flag > XDG_CONFIG_HOME/gpt-image-2/config.json

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · SKILL.md (reported line 58)May include surrounding context.

md
from pathlib import Path
from typing import Optional

DEFAULT_BASE_URL = "https://api.openai.com/v1"

# ── Config Discovery ──────────────────────────────────────────────
# Priority: --config flag > XDG_CONFIG_HOME/gpt-image-2/config.json

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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/gpt_image2.py (reported line 35)May include surrounding context.

python
from pathlib import Path
from typing import Optional

DEFAULT_BASE_URL = "https://api.openai.com/v1"

# ── Config Discovery ──────────────────────────────────────────────
# Priority: --config flag > XDG_CONFIG_HOME/gpt-image-2/config.json

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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/gpt_image2.py (reported line 355)May include surrounding context.

python
from pathlib import Path
from typing import Optional

DEFAULT_BASE_URL = "https://api.openai.com/v1"

# ── Config Discovery ──────────────────────────────────────────────
# Priority: --config flag > XDG_CONFIG_HOME/gpt-image-2/config.json

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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/gpt_image2.py (reported line 391)May include surrounding context.

python
from pathlib import Path
from typing import Optional

DEFAULT_BASE_URL = "https://api.openai.com/v1"

# ── Config Discovery ──────────────────────────────────────────────
# Priority: --config flag > XDG_CONFIG_HOME/gpt-image-2/config.json

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
mode = p.stat().st_mode & 0o777
        if mode & 0o077:
            import warnings
            warnings.warn(f"Config has loose permissions ({oct(mode)}). Run: chmod 600 {p}")
    except OSError:
        pass
    try:

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/gpt_image2.py (reported line 82)May include surrounding context.

python
mode = p.stat().st_mode & 0o777
        if mode & 0o077:
            import warnings
            warnings.warn(f"Config has loose permissions ({oct(mode)}). Run: chmod 600 {p}")
    except OSError:
        pass
    try:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/gpt_image2.py (reported line 191)May include surrounding context.

python
f.write('-H "Content-Type: application/json"\n')

    try:
        result = subprocess.run(
            [
                "curl", "-s", "-X", "POST", url,
                "-K", header_path,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
80% confidence
Finding

The multipart curl command is executed safely without a shell, but untrusted field values are passed directly to curl -F. Because curl treats multipart values with special prefixes such as @ or < as file-read directives, attacker-controlled prompt text or other fields could cause unintended local file disclosure to the remote server.

Content

Scanner excerpt · scripts/gpt_image2.py (reported line 221)May include surrounding context.

python
cmd += ["-F", f"{key}={val}"]
        cmd += ["-o", resp_path, "-w", "\nHTTP_CODE:%{http_code}\nTIME:%{time_total}s"]

        result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
        return result
    except subprocess.TimeoutExpired:
        print(f"ERROR: curl timed out after {timeout}s", file=sys.stderr)

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The natural-language guidance states that Chinese text in prompts may not render correctly and recommends using another tool for Chinese text rendering. This creates a locale-specific constraint in the skill documentation, and the file does not present it as a user choice or a clearly justified region-specific policy.

Content

No source excerpt is available for this finding.