Back to skill

Security audit

GPT Image 2 — Image Generation via Your ChatGPT Subscription

Security checks for vulnerabilities and agentic risk

Overview

This image-generation skill is mostly coherent, but it should be reviewed because it relies on saved Codex session logs and has weak isolation around what local session data may be read.

Install only if you are comfortable letting the skill use your logged-in Codex/ChatGPT session, persist the image request in Codex session history, and read newly created Codex rollout files under ~/.codex/sessions. Avoid running it alongside other Codex jobs that may create session rollouts, and do not use it for prompts or references that should not be retained locally in Codex logs.

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

Warning
Location
scripts/gen.sh:61
Finding
Session Rollout Race Can Expose Unrelated Codex Conversations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.sh:61-62, 104-116`; `scripts/extract_image.py:27-47, 99-103` **Vulnerability Type**: Race condition and insufficient session-file isolation **Risk Level**: Medium ### Vulnerable Code From `scripts/gen.sh`: ```bash find "$SESSIONS_ROOT" -type f -name 'rollout-*.jsonl' -print 2>/dev/null | sort > "$before" || true ``` ```bash find "$SESSIONS_ROOT" -type f -name 'rollout-*.jsonl' -print 2>/dev/null | sort > "$after" || true # Collect ALL new session files. A single `codex exec` call can spawn more # than one session rollout (e.g. when the imagegen tool runs in a sub-turn), # so we must scan every new one rather than blindly picking the last. new_sessions_file="$(mktemp)" trap 'rm -f "$before" "$after" "$stdout_log" "$stderr_log" "$new_sessions_file"' EXIT comm -13 "$before" "$after" > "$new_sessions_file" || true if [[ ! -s "$new_sessions_file" ]]; then echo "No new session rollout file under $SESSIONS_ROOT" >&2 tail -n 40 "$stderr_log" >&2 || true exit 6 fi ``` From `scripts/extract_image.py`: ```python def find_best_image_blob(session_paths: list[pathlib.Path]) -> tuple[str, str] | None: """Return the largest (base64, ext) image payload found across given files.""" best: tuple[str, str, int] | None = None for session_path in session_paths: try: text = session_path.read_text(errors="replace") except OSError: continue for line in text.splitlines(): try: obj = json.loads(line) except ValueError: continue flat = json.dumps(obj) for match in BASE64_BLOB_PATTERN.finditer(flat): blob = match.group(1) for magic, ext in IMAGE_MAGIC_PREFIXES.items(): if blob.startswith(magic): if best is None or len(blob) > best[2]: best = (blob, ext, len(blob)) ...[truncated 2314 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Run Codex with an isolated temporary session home or dedicated session directory so the invocation cannot see unrelated rollouts. 2. Prefer obtaining the exact session identifier or rollout path directly from the spawned Codex process rather than inferring ownership through directory differences. 3. Validate that every selected rollout belongs to the child process using a cryptographically random invocation identifier or trusted session metadata. 4. Add an exclusive filesystem lock around snapshot creation, Codex execution, and extraction if shared global session storage is unavoidable. 5. Reject rollout paths outside the expected canonical session root and verify file ownership before reading. 6. Update the documentation so its isolation and concurrency claims accurately reflect the implemented guarantees. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gen.sh:70
Finding
Untrusted Prompt Is Passed to a General-Purpose Agent with Broad Read Access<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.sh:70-99` **Vulnerability Type**: Excessive filesystem exposure and nested-agent prompt injection **Risk Level**: Medium ### Vulnerable Code ```bash # Intentionally NOT using --ephemeral: we need the session rollout on disk. args=(exec --skip-git-repo-check --sandbox read-only --color never --enable image_generation) if [[ ${#REF_IMAGES[@]} -gt 0 ]]; then for img in "${REF_IMAGES[@]}"; do [[ -f "$img" ]] || { echo "Reference image not found: $img" >&2; exit 4; } args+=(-i "$img") done fi instruction="Use the imagegen tool to generate the image for the following request." if [[ ${#REF_IMAGES[@]} -gt 0 ]]; then instruction+=" Use the attached image(s) as visual reference / input for image-to-image." fi instruction+=$'\nRequirements: generate the image directly, return only the image, no explanation.\n\nRequest:\n'"$PROMPT" # `-i` is a variadic flag (<FILE>...), so passing the prompt as the trailing # positional would be consumed as another image file. Feed the prompt via # stdin instead (codex exec reads from stdin when no prompt positional is # given). TO="" if command -v timeout >/dev/null 2>&1; then TO="timeout" elif command -v gtimeout >/dev/null 2>&1; then TO="gtimeout" fi set +e if [[ -n "$TO" ]]; then printf '%s' "$instruction" | "$TO" "$TIMEOUT_SEC" codex "${args[@]}" >"$stdout_log" 2>"$stderr_log" else printf '%s' "$instruction" | codex "${args[@]}" >"$stdout_log" 2>"$stderr_log" fi ``` ### Technical Analysis The caller-controlled prompt is concatenated directly into instructions supplied to a nested, general-purpose Codex agent. Although the process uses `--sandbox read-only`, this setting primarily limits writes; it does not confine reads to the supplied reference images or a dedicated workspace. The trusted instruction and untrusted request are passed through the same natural-language channel. A malicious request can therefore attempt to override the image- ...[truncated 1629 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a dedicated image-generation API or narrowly scoped image tool instead of invoking a general-purpose agent. 2. If Codex must be used, launch it inside an isolated temporary workspace containing only explicitly approved reference images. 3. Deny access to the user's home directory, project secrets, credential stores, and unrelated filesystem paths through operating-system sandboxing or containerization. 4. Pass user content through a structured parameter intended specifically for image prompts rather than combining trusted instructions and untrusted content in one natural-language instruction stream. 5. Apply an allowlist to reference-image paths and copy approved inputs into the isolated workspace before execution. 6. Minimize or disable session persistence where possible. If persistence is required for extraction, securely delete the invocation's rollout after extracting the image. 7. Document that read-only mode does not necessarily mean access is limited to reference files. ]]>
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (3)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description claims a full image-generation and editing capability through GPT Image 2 inside Claude Code. However, this code chunk does not perform any generation, editing, or model interaction at all. Its sole purpose is post-processing: reading session files, locating embedded base64 image data, and exporting that data to an output image file. While this could be a supporting helper within a larger image-generation skill, the supplied chunk by itself materially differs from the declared primary purpose and exposes a distinct capability—session-log image extraction—that is not described.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill explicitly describes file reads from ~/.codex/sessions and file writes to an arbitrary --out path, but it declares no tool scope or allowed-tools restrictions. That creates an avoidable over-privilege gap: an agent invoking this skill may gain broader read/write capability than users or reviewers expect, increasing the chance of unintended access to local files or misuse of output paths.

Session Persistence

Medium
Category
Rogue Agent
Content
# ChatGPT subscription session. Supports text-to-image and image-to-image.
#
# Implementation note: on codex-cli 0.111.0 the `imagegen` tool does NOT
# write a PNG file to disk. The generated image is embedded as base64 inside
# the session rollout jsonl under ~/.codex/sessions/YYYY/MM/DD/. This script
# captures the new session file created by the run and decodes the image
# out of it. Flags: `--enable image_generation` turns the under-development
Confidence
95% confidence
Finding
The script deliberately disables ephemeral execution so Codex session rollouts are persisted under ~/.codex/sessions, then reads those files back to extract base64 image data. Those persisted session files can contain the user's prompts, reference-image metadata, generated content, and potentially other session context, creating unnecessary local retention of sensitive data beyond the requested output image.

Static analysis

No suspicious patterns detected.