Back to skill

Security audit

Generate/edit images via Tuzi API (default), Google Gemini, OpenAI, DashScope, Replicate. Text-to-image + image-to-image editing; 1K/2K/4K resolution. Use for image create/modify/edit requests incl. --input-image.

Security checks for vulnerabilities and agentic risk

Overview

This image-generation skill is mostly purpose-aligned, but it contains unsafe shell execution paths that could let crafted inputs run local commands or expose API credentials.

Install only if you are comfortable reviewing and constraining it first. Run it in a restricted environment, avoid sensitive images/prompts, use narrowly scoped API keys, avoid proxy/custom-base-url settings unless trusted, and prefer a pinned/preinstalled Bun runtime. The unsafe shell paths should be fixed before using it with untrusted filenames, environment variables, or API-key inputs.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/main.ts:113
Finding
OS Command Injection Through the Input Image Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.ts:113-120` **Vulnerability Type**: OS command injection caused by unsafe shell interpolation **Risk Level**: High ### Complete Code Snippet ```typescript async function autoDetectResolution(inputImagePath: string, explicitResolution: "1K" | "2K" | "4K"): Promise<"1K" | "2K" | "4K"> { if (explicitResolution !== "1K") return explicitResolution; try { const { execSync } = await import("node:child_process"); const result = execSync(`identify -format "%w %h" "${inputImagePath}" 2>/dev/null`, { encoding: "utf8" }).trim(); const [w, h] = result.split(" ").map(Number); ``` ### Technical Analysis The user-controlled `inputImagePath` is interpolated directly into a command executed through `execSync`. Because `execSync` receives a string, Node.js invokes a shell to interpret it. Wrapping the path in double quotes does not prevent injection. A filename containing a double quote can terminate the quoted argument and introduce shell operators or additional commands. The earlier `access(args.inputImage)` check does not make the value safe; an attacker can create a file whose name contains shell metacharacters and then supply that exact path. This shell invocation is not necessary for the declared image-generation functionality. Image dimensions should be obtained through a library or by invoking `identify` without a shell. ### Attack Path 1. The attacker creates an accessible image file with a crafted filename containing a quote, shell separator, command, and comment marker. 2. The attacker supplies that path using `--input-image`. 3. The `access()` check succeeds because the crafted file exists. 4. With the default `1K` resolution, `autoDetectResolution()` is called. 5. The crafted path terminates the quoted shell argument. 6. The shell executes the injected command with the same operating-system privileges as the Skill process. ### Impact Assessment Successful exploitation provi ...[truncated 370 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace `execSync` with `execFile` or `spawn` and pass arguments as an array with shell processing disabled: ```typescript import { execFile } from "node:child_process"; import { promisify } from "node:util"; const execFileAsync = promisify(execFile); const { stdout } = await execFileAsync( "identify", ["-format", "%w %h", inputImagePath], { timeout: 30_000, maxBuffer: 1024 * 1024 } ); ``` - Prefer a maintained image-metadata library that does not invoke an external process. - Validate that the input is a regular file and impose a reasonable file-size limit before processing. - Do not attempt to make shell interpolation safe through manual escaping; eliminate the shell boundary entirely. - Run the Skill with minimal filesystem permissions and without access to unrelated credentials. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/providers/google.ts:32
Finding
Command Injection and API-Key Exposure in Google Proxy Mode<![CDATA[ ## Vulnerability Details **File Location**: `scripts/providers/google.ts:32-48` **Vulnerability Type**: OS command injection and sensitive credential exposure **Risk Level**: High ### Complete Code Snippet ```typescript async function postJson<T>(pathname: string, body: unknown): Promise<T> { const apiKey = getApiKey(); if (!apiKey) throw new Error("GOOGLE_API_KEY or GEMINI_API_KEY is required"); const url = buildUrl(pathname); const proxy = getHttpProxy(); if (proxy) { const bodyStr = JSON.stringify(body); const proxyArgs = `-x "${proxy}"`; const result = execSync( `curl -s --connect-timeout 30 --max-time 300 ${proxyArgs} "${url}" -H "Content-Type: application/json" -H "x-goog-api-key: ${apiKey}" -d @-`, { input: bodyStr, maxBuffer: 100 * 1024 * 1024, timeout: 310000 }, ); const parsed = JSON.parse(result.toString()) as any; if (parsed.error) throw new Error(`Google API error (${parsed.error.code}): ${parsed.error.message}`); return parsed as T; ``` ### Technical Analysis When a proxy environment variable is present, the provider constructs a shell command containing the proxy URL, Google base URL, model-derived pathname, and API key. These values are interpolated without shell-safe argument separation. The API key is directly user-controllable through `--api-key`; `main.ts` copies that value into `GEMINI_API_KEY`. A value containing quotes and shell operators can escape the `x-goog-api-key` header argument and execute another command. Proxy and base URL environment variables provide additional injection surfaces. The Google API key is also embedded in the command-line string passed to `curl`. Depending on the platform and timing, local users or monitoring tools may be able to observe it through process listings or process inspection interfaces. ### Attack Path 1. Proxy mode is enabled by setting one of `https_proxy`, `HTTPS_PROXY`, `http_proxy`, `HTTP_PROXY`, or `ALL_PROXY`. 2. The attacke ...[truncated 908 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the shell-based `curl` path. - Use `fetch` with a trusted HTTP proxy dispatcher or agent whose configuration is supplied as structured data. - If an external executable is unavoidable, invoke it with `execFile` or `spawn`, pass every argument as a separate array element, and keep `shell: false`. - Avoid putting secrets in process arguments. Supply sensitive headers through an API that does not expose them in the process command line. - Parse proxy and base URLs with `URL`, require approved schemes, and reject embedded credentials or unexpected protocols. - Consider restricting custom API origins to an explicit allowlist unless custom endpoints are essential. - Ensure error handling never logs request headers or API keys. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/providers/replicate.ts:68
Finding
Replicate Bearer Token Forwarded to a Response-Controlled Polling URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/providers/replicate.ts:68-79` **Vulnerability Type**: Credential forwarding to an unvalidated remote origin **Risk Level**: Medium ### Complete Code Snippet ```typescript if (prediction.status !== "succeeded") { if (!prediction.urls?.get) throw new Error("No poll URL returned"); const start = Date.now(); while (Date.now() - start < MAX_POLL_MS) { await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS)); const pollRes = await fetch(prediction.urls!.get!, { headers: { Authorization: `Bearer ${apiToken}` } }); if (!pollRes.ok) throw new Error(`Replicate poll error: ${await pollRes.text()}`); prediction = (await pollRes.json()) as PredictionResponse; if (prediction.status === "succeeded") break; if (prediction.status === "failed" || prediction.status === "canceled") throw new Error(`Replicate ${prediction.status}: ${prediction.error}`); } ``` ### Technical Analysis The polling URL is taken from the initial API response and used without validating its scheme or origin. The code attaches the Replicate bearer token to every polling request. Authentication is required for legitimate Replicate polling, but the credential should only be sent to a trusted Replicate API origin. The current implementation permits the remote response to choose an arbitrary destination. A compromised endpoint, malicious custom `REPLICATE_BASE_URL`, or upstream response manipulation could therefore redirect the authorization header to an attacker-controlled server. ### Attack Path 1. The Skill submits a prediction to the configured Replicate endpoint. 2. A malicious or compromised endpoint returns a non-success status and sets `urls.get` to an attacker-controlled HTTPS URL. 3. The Skill enters the polling loop. 4. It sends `Authorization: Bearer <REPLICATE_API_TOKEN>` to the unvalidated URL. 5. The attacker's server records the token. 6. The attacker reuses the token against Replicate ...[truncated 456 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Parse `prediction.urls.get` using the `URL` class before sending a request. - Require HTTPS. - Compare the polling URL's origin against the validated configured Replicate API origin. - Attach the bearer token only after the destination passes the origin check. - If Replicate legitimately returns polling URLs from multiple domains, maintain an explicit documented allowlist rather than accepting arbitrary hosts. - Reject URLs containing unexpected usernames, passwords, ports, or non-HTTP schemes. - Apply redirect restrictions so a trusted polling URL cannot redirect an authorization-bearing request to an untrusted origin. - Rotate the token immediately if disclosure is suspected and use a token with the narrowest available permissions. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:20
Finding
Unpinned Runtime Package Download and Execution<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:20-35` **Vulnerability Type**: Unpinned third-party package execution **Risk Level**: Medium ### Complete Code Snippet ```markdown **Generate new image:** ```bash npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "your image description" --filename "output.png" [--resolution 1K|2K|4K] [--provider tuzi|google|openai|dashscope|replicate] [--model MODEL_ID] ``` **Edit existing image:** ```bash npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "editing instructions" --filename "output.png" --input-image "path/to/input.png" [--resolution 1K|2K|4K] ``` ``` ### Technical Analysis The documented execution method uses `npx -y bun` without an exact version, lockfile, or integrity verification. If `bun` is not already available in the local package cache, `npx` can retrieve a currently resolved package from the configured registry and execute it automatically. The `-y` option suppresses the interactive confirmation normally associated with installing the package. Consequently, the code actually executed is not fully represented by the audited project. It can change as registry resolution changes. This creates exposure to compromised package releases, package-account takeover, malicious registry configuration, or unexpected incompatible updates. ### Attack Path 1. An Agent follows the documented command. 2. The required `bun` package is absent from the local cache or resolves to a newer version. 3. `npx -y` contacts the configured package registry and downloads the package without confirmation. 4. A malicious or compromised package version runs during invocation. 5. Package code executes with the same privileges and access to the same environment variables, files, and network resources as the Agent. ### Impact Assessment A compromised dependency can execute arbitrary code under the Agent account. It may read API keys from environment variables or `.tuzi-skills/.env`, access user files, alter generated ...[truncated 187 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Prefer requiring a preinstalled, trusted Bun runtime and invoke it directly. - If `npx` must be used, pin an exact reviewed version rather than resolving the latest available package. - Use a lockfile and package-manager integrity metadata where supported. - Configure an approved package registry and verify package provenance or signatures. - Avoid `-y` for first-time dependency acquisition in security-sensitive Agent workflows. - Document the required runtime version and provide a separate, explicit installation step so dependency installation is not silently combined with normal Skill execution. - Run third-party tooling in a sandbox with minimal filesystem, environment, and network access. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill advertises image editing, `--input-image` support, and 2K/4K behavior that the implementation reportedly does not consistently provide for at least one provider. This mismatch can cause users or agents to send sensitive images/prompts to an external provider under false assumptions about functionality, routing, or output characteristics, undermining trust and safe decision-making.

Credential Access

High
Category
Privilege Escalation
Content
}

async function loadEnv(): Promise<void> {
  const homeEnv = await loadEnvFile(path.join(homedir(), ".tuzi-skills", ".env"));
  const cwdEnv = await loadEnvFile(path.join(process.cwd(), ".tuzi-skills", ".env"));
  for (const [k, v] of Object.entries(homeEnv)) {
    if (!process.env[k]) process.env[k] = v;
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
}

async function loadEnv(): Promise<void> {
  const homeEnv = await loadEnvFile(path.join(homedir(), ".tuzi-skills", ".env"));
  const cwdEnv = await loadEnvFile(path.join(process.cwd(), ".tuzi-skills", ".env"));
  for (const [k, v] of Object.entries(homeEnv)) {
    if (!process.env[k]) process.env[k] = v;
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill invokes shell execution, accesses environment variables for API keys, and performs network calls to third-party providers, but it does not declare any explicit tool scope or permissions. This weakens reviewability and containment, making it easier for an agent or user to run the skill without understanding the breadth of its capabilities and the associated data exposure.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill description does not clearly warn that user prompts, input images, and possibly metadata will be transmitted to external third-party APIs. In an image-editing context this is especially sensitive because users may provide private images or proprietary content, leading to unintended data disclosure or policy violations.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
Using `npx -y bun` without a pinned version allows retrieval and execution of whatever package/version is current at runtime. This creates a supply-chain risk where a compromised, replaced, or incompatible upstream package could execute arbitrary code on the host during skill invocation.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
The command example again relies on unpinned `npx -y bun`, which means the runtime is downloaded/executed from the package ecosystem at invocation time. In a skill that processes user-controlled prompts and images and accesses API credentials, that supply-chain entry point materially increases compromise risk.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
This workflow example repeats an unpinned `npx -y bun` invocation, preserving the same arbitrary-code/supply-chain exposure. Repetition in the primary workflow makes exploitation more likely because these are the commands users are most likely to copy verbatim.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
The final 4K example still uses unversioned `npx -y bun`, so the same runtime supply-chain risk applies at every documented entry point. Because the skill also relies on network access and credentialed API usage, a malicious runtime could exfiltrate keys, prompts, or local file content.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The code constructs a shell command with unsanitized user-controlled inputImagePath and executes it via execSync. Wrapping the path in double quotes is not sufficient because shell metacharacters such as command substitution can still be interpreted, enabling command execution if a crafted filename is supplied.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This code accepts an API key, stores it in an environment variable, and then sends the user prompt and optional input image to an external provider via generateImage. Although it logs the provider/model in use, it does not disclose that credentials and user-supplied content may be transmitted to a third-party service.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
When a proxy is configured, the code builds a shell command string and passes untrusted environment-derived data (`proxy`) plus a secret (`apiKey`) into `execSync`. Because shell metacharacters in the proxy value can break out of the quoted argument, an attacker who can influence environment variables can achieve command injection and potentially exfiltrate the API key or run arbitrary commands.

External Transmission

Medium
Category
Data Exfiltration
Content
const bodyStr = JSON.stringify(body);
    const proxyArgs = `-x "${proxy}"`;
    const result = execSync(
      `curl -s --connect-timeout 30 --max-time 300 ${proxyArgs} "${url}" -H "Content-Type: application/json" -H "x-goog-api-key: ${apiKey}" -d @-`,
      { input: bodyStr, maxBuffer: 100 * 1024 * 1024, timeout: 310000 },
    );
    const parsed = JSON.parse(result.toString()) as any;
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code sends the prompt and any optional input image to a remote Google API via HTTP, which can expose user-provided content off-host. While there is a progress log for image generation, there is no explicit disclosure that user content is being uploaded to an external service in this file.

External Transmission

Medium
Category
Data Exfiltration
Content
model: string,
  args: CliArgs
): Promise<Uint8Array> {
  const baseURL = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
  const apiKey = process.env.OPENAI_API_KEY;
  if (!apiKey) throw new Error("OPENAI_API_KEY is required");
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
When `args.inputImage` is provided, the function reads a local image file and uploads it to the OpenAI images edit endpoint. The code performs this network transmission without any confirmation prompt, logging, or inline comment/docstring warning that local user content will be sent off-system.

External Transmission

Medium
Category
Data Exfiltration
Content
}

function getBaseUrl(): string {
  const base = process.env.TUZI_BASE_URL || "https://api.tu-zi.com/v1";
  return base.replace(/\/+$/g, "");
}
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
}

function getBaseUrl(): string {
  const base = process.env.TUZI_BASE_URL || "https://api.tu-zi.com/v1";
  return base.replace(/\/+$/g, "");
}
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
When args.inputImage is set, the code reads the local image file, converts it to a base64 data URL, and includes it in the POST request to the external Tuzi service. Although there is a generic log that image generation is starting, there is no explicit disclosure that a local file's contents will be uploaded to a third-party API.

Missing User Warnings

Low
Confidence
77% confidence
Finding
The code resolves the requested filename, creates parent directories, and writes image data directly to disk. While it logs the saved path after completion, there is no prior warning or confirmation that a file write will occur and may replace existing content at that path.

Missing User Warnings

Low
Confidence
76% confidence
Finding
The function reads sensitive API credentials from environment variables and, when a proxy is configured, invokes curl through execSync to send the request. This file lacks any comment, docstring, or explicit user-facing disclosure explaining that credentials are used and that a subprocess may be spawned for network access through a proxy.

Missing User Warnings

Low
Confidence
79% confidence
Finding
The code sends the user-supplied `prompt` to the OpenAI `/images/generations` endpoint, which is a network transmission of user data. There is no visible print/log statement, confirmation, or explanatory comment in this file to disclose that the prompt leaves the local system.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/main.ts:122

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/providers/google.ts:42

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/providers/dashscope.ts:4

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/providers/google.ts:7

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/providers/openai.ts:6

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/providers/replicate.ts:10

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/providers/tuzi.ts:8