Back to skill

Security audit

gemini-image-generation

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it needs review because it can send prompts, images, and the Gemini API key to a configurable endpoint and its scripts do not enforce workspace-only file paths.

Review before installing. Use only a trusted Gemini endpoint, avoid setting GEMINI_BASE_URL unless you control and trust it, provide a narrowly scoped API key, and only run the scripts on images and output paths you explicitly intend to share or overwrite. Treat source images and prompts as data that will leave the local machine.

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/gemini-image-runtime.mjs:122
Finding
Unvalidated Custom Gemini Endpoint Can Receive API Credentials and User Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gemini-image-runtime.mjs:122-138`; supporting configuration guidance in `SKILL.md:17-19, 29-33, 57-59` **Vulnerability Type**: Unvalidated external service endpoint and sensitive-data redirection **Risk Level**: High ### Vulnerable Code ```js export function createGeminiImageClientFromEnv() { const apiKey = process.env.GEMINI_API_KEY; const model = process.env.GEMINI_MODEL_ID; const baseUrl = process.env.GEMINI_BASE_URL?.replace(/\/$/, ""); if (!apiKey) { throw new Error("Missing GEMINI_API_KEY in the environment"); } if (!model) { throw new Error("Missing GEMINI_MODEL_ID in the environment"); } return { ai: new GoogleGenAI({ apiKey, httpOptions: baseUrl ? { baseUrl } : undefined, }), model, }; } ``` The Skill documentation explicitly allows an arbitrary custom endpoint: ```json { "GEMINI_API_KEY": "sk-xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "GEMINI_MODEL_ID": "gemini-3.1-flash-image-preview", "GEMINI_BASE_URL": "https://custom-endpoint.com" } ``` ### Technical Analysis The value of `GEMINI_BASE_URL` is read directly from the environment and passed to the Google GenAI client without validating its protocol, hostname, port, or trust status. The same client is configured with `GEMINI_API_KEY` and is subsequently used to send generation prompts and inline image data. Consequently, a party capable of modifying the Skill environment can redirect requests to an attacker-controlled endpoint. The implementation does not require HTTPS, restrict destinations to approved Gemini hosts, or warn at runtime that the custom endpoint will receive sensitive request data and associated authentication material. For image-editing operations, local image bytes are base64-encoded and included in the request. This increases the exposure from prompt disclosure to disclosure of source images and potentially other local files accepted by the editing interface. ### ...[truncated 1427 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use the official Gemini API endpoint by default and remove custom endpoint support unless it is operationally necessary. 2. If custom endpoints are required, enforce an explicit allowlist of trusted hostnames. 3. Require HTTPS and reject plaintext HTTP URLs. 4. Parse the URL with the platform URL parser and reject: - Embedded credentials. - Unexpected schemes. - Loopback, link-local, and private-network destinations unless explicitly approved. - Unapproved ports and IP-literal destinations. 5. Separate credentials by destination. Do not send the production Gemini API key to non-Google endpoints; require a distinct credential for each approved proxy. 6. Display an explicit warning or require affirmative configuration when a custom endpoint is active, stating that prompts and images will be transmitted to it. 7. Log only the normalized destination hostname, never the API key, prompt, or submitted file contents. 8. Add automated tests confirming that HTTP URLs and unapproved hosts are rejected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gemini-image-runtime.mjs:143
Finding
Unrestricted Input and Output Paths Permit Local File Disclosure and Arbitrary File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/edit-image.mjs:13-27`; `scripts/gemini-image-runtime.mjs:143-162, 186-211` **Vulnerability Type**: Missing workspace-boundary enforcement for file reads and writes **Risk Level**: Medium ### Vulnerable Code The editing entry point accepts input and output paths directly from command-line arguments: ```js async function main() { const args = parseArgs(process.argv.slice(2)); const prompt = requireSingleArg(args.prompt, "prompt"); const inputs = normalizeArgList(requireSingleArg(args.input, "input")); const output = requireSingleArg(args.output, "output"); const mimeTypes = normalizeArgList(args["mime-type"]); const imageConfig = readImageConfigArgs(args); const inlineDataParts = await createInlineDataParts(inputs, mimeTypes); const { ai, model } = createGeminiImageClientFromEnv(); const response = await ai.models.generateContent( createImageGenerationRequest({ model, prompt, inlineDataParts, imageConfig, }), ); ``` Input files are read without canonicalization or workspace-boundary checks: ```js export async function createInlineDataParts(inputPaths, mimeTypes) { if (inputPaths.length === 0) { throw new Error("At least one --input value is required"); } if (mimeTypes.length > 1 && mimeTypes.length !== inputPaths.length) { throw new Error("Pass either one --mime-type for all inputs or one --mime-type per --input"); } return Promise.all( inputPaths.map(async (inputPath, index) => { const imageBuffer = await fs.readFile(inputPath); const mimeType = mimeTypes[index] || mimeTypes[0] || mimeTypeForPath(inputPath); return { mimeType, data: imageBuffer.toString("base64"), }; }), ); } ``` Returned data is written to the requested path without enforcing a workspace root or protecting existing files: ```js export async function saveImageResponse(response, outputPath) { const parts ...[truncated 3803 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define an explicit workspace root and resolve every input and output path against it. 2. Canonicalize paths before access and verify that the canonical path remains inside the approved root. 3. Reject absolute paths and traversal components unless a separately authorized workflow requires them. 4. Resolve and validate parent directories to prevent symlink-based escapes. 5. For input files: - Validate file signatures rather than trusting extensions or caller-supplied MIME types. - Permit only explicitly supported image formats. - Enforce per-file and aggregate size limits before reading or encoding. - Reject symbolic links unless they resolve to approved in-workspace files. 6. For output files: - Reject paths outside a dedicated output directory. - Use exclusive creation where overwriting is unnecessary. - Require explicit user confirmation before replacing an existing file. - Write to a secure temporary file and atomically rename after validation. 7. Treat `--mime-type` as a constrained override and verify that it matches the detected file format. 8. Add tests covering absolute paths, `../` traversal, symlink escapes, non-image files with explicit MIME types, and attempts to overwrite existing files. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (10)

Ae2

Medium
Category
analysis-evasion
Confidence
90% confidence
Finding
Artifact content does not match its filename extension

Session Persistence

Medium
Category
Rogue Agent
Content
# Image Generation

Use this skill when you need to create one or more image files from a text prompt, or edit one or more existing images with Gemini.

## Requirements
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill explicitly instructs users to send prompts and potentially source images to Gemini via an external API, but it does not warn users that their content leaves the local environment for third-party processing. This creates a privacy and data-handling risk, especially if users provide sensitive images or confidential prompt content under the assumption the operation is local.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The code reads local image files, base64-encodes them, and prepares them for submission via the GoogleGenAI client, which results in user-provided file contents being sent over the network. This file does not include a confirmation prompt, user-facing notice, or explanatory comment warning that local image data will be transmitted to a third-party service.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The function creates directories and writes returned image data to the filesystem, which is a safety-relevant file write operation. In this file there is no confirmation prompt, user-facing log/print, or explanatory comment/docstring disclosing that generated content will be saved to the specified path.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code performs a network call to an external AI service using user-supplied prompt data, but the file itself does not include a confirmation prompt, warning comment, or docstring disclosing that the prompt will be transmitted off-box. The later console output only reports results after the call, so it does not function as a prior user-facing warning about data transmission.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"private": true,
  "type": "module",
  "dependencies": {
    "@google/genai": "^1.44.0"
  }
}
Confidence
95% confidence
Finding
The dependency uses a caret range (^1.44.0), which permits automatic installation of newer compatible versions. This creates a supply-chain risk because a compromised or buggy upstream release could be pulled in without deliberate review, affecting image-generation functionality that likely handles external prompts and API interactions.

Context-Inappropriate Capability

Low
Confidence
82% confidence
Finding
The runtime pulls GEMINI_API_KEY, GEMINI_MODEL_ID, and optionally GEMINI_BASE_URL from process.env to configure the client. While network access to Gemini is expected for this skill, direct environment-variable access is an additional capability not reflected in the manifest description, which only describes generating or editing images in a workflow.

Missing User Warnings

Low
Confidence
78% confidence
Finding
The code accesses GEMINI_API_KEY from the environment to authenticate outbound API calls. Although this is common, it falls under sensitive credential access and this file provides no comment, docstring, or user-facing notice explaining the dependency on environment-stored credentials.

Missing User Warnings

Low
Confidence
78% confidence
Finding
The script saves generated images to the user-specified output location, which is a file write operation. Although the save is part of the apparent purpose of the script, this file contains no prior comment, docstring, or user-facing notice that files will be created or overwritten at the provided path.

Static analysis

No suspicious patterns detected.