Back to skill

Security audit

Gemini TG Image Gen

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it advertises, but its image-fetching script can follow arbitrary URLs produced by the model, creating avoidable network and data-exposure risk.

Review before installing. The OpenRouter credential use and Telegram delivery are expected for this skill, but the script should be changed to accept only trusted structured image outputs or validated inline image data, enforce content type and size limits, and delete temporary files after sending. Avoid using it with sensitive prompts or in environments that can reach internal services until that URL-fetch behavior is constrained.

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/generate_image.py:35
Finding

Unrestricted Fetching of Model-Controlled URLs Enables SSRF and Data Exfiltration

Content
View full analysis

Vulnerability Details

File Location: scripts/generate_image.py, lines 35-44, 67-75, and 131-136
Vulnerability Type: Server-Side Request Forgery through untrusted model output
Risk Level: High

Vulnerable Code

python
def _download_url(url: str, out_dir: str, idx: int) -> str:
    parsed = urlparse(url)
    ext = os.path.splitext(parsed.path)[1].lstrip(".") or "png"
    filename = f"openrouter_image_{int(time.time())}_{idx}.{ext}"
    path = os.path.join(out_dir, filename)
    r = requests.get(url, timeout=60)
    r.raise_for_status()
    with open(path, "wb") as f:
        f.write(r.content)
    return path
python
elif isinstance(content, str):
    if "data:image/" in content:
        for token in content.split():
            if token.startswith("data:image/"):
                urls.append(token)
    if "http://" in content or "https://" in content:
        for token in content.split():
            if token.startswith("http://") or token.startswith("https://"):
                urls.append(token)
python
paths = []
for i, u in enumerate(urls):
    if u.startswith("data:image/"):
        paths.append(_save_data_url(u, out_dir, i))
    else:
        paths.append(_download_url(u, out_dir, i))
return paths

Technical Analysis

The script considers arbitrary HTTP or HTTPS tokens found in model-generated text to be image URLs. It subsequently sends server-side requests to those URLs without validating:

  • The destination hostname
  • The resolved IP address
  • Whether the address belongs to a loopback, private, link-local, multicast, or reserved network
  • Redirect destinations
  • The response MIME type
  • Whether the response is actually an image

OpenRouter responses and model-generated content must be treated as untrusted input. A crafted prompt, prompt-influenced model response, or compromised upstream service can cause the script to req ...[truncated 2058 chars]

Remediation
View remediation

Remediation Suggestions

  1. Prefer accepting only inline data:image/*;base64 results from OpenRouter, eliminating arbitrary secondary network requests.
  2. If remote downloads are required, maintain an explicit allowlist of trusted HTTPS hostnames documented by the image provider.
  3. Reject HTTP and all unsupported URL schemes.
  4. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 addresses.
  5. Disable redirects or validate every redirect destination with the same hostname and IP-address controls.
  6. Protect against DNS rebinding by ensuring that the validated address is the address used for the connection.
  7. Require an expected image MIME type and verify image magic bytes before saving or forwarding the response.
  8. Do not derive the file extension directly from an untrusted URL. Select it only after validated image-type detection.
  9. Avoid extracting URLs from arbitrary natural-language response text. Parse only documented, structured image response fields.
  10. Ensure Telegram delivery occurs only after successful image validation.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_image.py:35
Finding

Unbounded Remote Response Download Can Exhaust Memory and Disk

Content
View full analysis

Vulnerability Details

File Location: scripts/generate_image.py, lines 35-44
Vulnerability Type: Uncontrolled resource consumption
Risk Level: Medium

Vulnerable Code

python
def _download_url(url: str, out_dir: str, idx: int) -> str:
    parsed = urlparse(url)
    ext = os.path.splitext(parsed.path)[1].lstrip(".") or "png"
    filename = f"openrouter_image_{int(time.time())}_{idx}.{ext}"
    path = os.path.join(out_dir, filename)
    r = requests.get(url, timeout=60)
    r.raise_for_status()
    with open(path, "wb") as f:
        f.write(r.content)
    return path

Technical Analysis

requests.get() is invoked without streaming, causing the full response body to be buffered in process memory through r.content. The complete body is then written to disk without an upper size limit.

Because the URL originates from untrusted model output, an attacker can direct the script to a very large resource or to a server that continuously emits data. The configured timeout does not provide an adequate size control; it primarily constrains network timing and does not establish a maximum number of bytes.

The script also does not limit the number of extracted URLs. A response containing many URLs can therefore trigger multiple large downloads during one invocation.

Attack Path

  1. An attacker influences the model response so it contains one or more URLs controlled by the attacker.
  2. Each URL points to a very large response or a service that emits excessive data.
  3. _extract_image_urls() accepts the URLs.
  4. requests.get() buffers each complete response in memory.
  5. The script writes each response to /root/.openclaw/workspace/tmp without a size quota.
  6. Memory, disk capacity, or both are exhausted, causing the Skill or other processes on the host to fail.

Impact Assessment

Exploitation can cause denial of service within the privileges of the Skill process. Potentia ...[truncated 497 chars]

Remediation
View remediation

Remediation Suggestions

  1. Use requests.get(..., stream=True) rather than buffering the entire response.
  2. Enforce a strict maximum image size appropriate for Telegram and the selected image model.
  3. Reject responses whose Content-Length exceeds the limit, while still enforcing the limit during streaming because that header may be missing or false.
  4. Read the response in bounded chunks and terminate immediately once the cumulative byte limit is exceeded.
  5. Limit the number of images or URLs processed per generation request.
  6. Validate the MIME type and file signature before retaining downloaded content.
  7. Delete partial files when validation, transfer, or size checks fail.
  8. Apply filesystem quotas and periodically remove stale generated files from the temporary directory.
  9. Use separate connection and read timeouts to mitigate slow-response resource consumption.
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
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The documented purpose is narrow, but the observed behavior expands trust boundaries by writing local files and potentially downloading arbitrary URLs returned by the model response. That mismatch is dangerous because users, reviewers, and policy controls may approve the skill for simple image generation while it actually performs broader filesystem and network actions that could be abused for exfiltration, SSRF-like fetches, or unsafe content retrieval.

Content

No source excerpt is available for this finding.

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

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


name: gemini-tg-image-gen description: Generate images via OpenRouter (google/gemini-2.5-flash-image) and send to Telegram. Use when user asks for AI-generated images in TG.

Gemini TG Image Gen (OpenRouter)

Workflow

  1. Immediately notify user in Telegram: "⏳ Идёт генерация, подождите немного..."
  2. Use OpenRouter model google/gemini-2.5-flash-image.
  3. Read API key from env: OPENROUTER_API_KEY.
  4. Run script to generate and save image locally.
  5. Send the image to Telegram using the message tool (local file path).
  6. NO_REPLY.

Usage

bash
OPENROUTER_API_KEY=...

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill uses sensitive capabilities—environment access for OPENROUTER_API_KEY and external network access to OpenRouter—without declaring any explicit tool scope or permissions boundary. In an agent environment, missing scope declarations make it harder to enforce least privilege and can allow broader-than-expected execution or secret access.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The workflow requires sending a fixed Russian message to the user, and the Telegram send example repeats the same constraint. This forces a specific language without any user opt-in or explanation that the skill is intentionally limited to a Russian-speaking context.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The workflow creates local files and transmits generated content to an external service without warning the user about storage and external sharing. In contexts where prompts or generated images may contain sensitive material, silent persistence and transmission increase privacy and data-handling risk.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script downloads whatever URL appears in the model response instead of restricting fetches to trusted image origins or requiring inline image data. Because the response is model-controlled/untrusted input, this creates an SSRF-style primitive and allows unintended outbound requests to arbitrary hosts, which is more dangerous in an agent environment that may have network access beyond the intended OpenRouter call.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script sends the user-provided prompt to https://openrouter.ai/api/v1/chat/completions via an HTTP POST, but there is no user-facing warning, log statement, or explanatory comment indicating that user input will be transmitted to a third-party service. Network transmission of user data is explicitly in scope for this rule.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script saves generated image data to /root/.openclaw/workspace/tmp by writing binary files, but provides no confirmation prompt, log message, or explanatory comment/docstring disclosing that local filesystem writes will occur. For a code file, file writes are in scope for missing-warning review unless there is some visible disclosure.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
73% confidence
Finding

The manifest describes an image-generation-and-send capability, but does not mention credential access. Reading OPENROUTER_API_KEY from the environment is a sensitive capability that is not explicit in the declared purpose, even though it is commonly used for API access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code reads OPENROUTER_API_KEY from the environment, which is credential access covered by the missing-warning criteria. Although it errors if the variable is absent, it does not include any explanatory comment or user-facing notice about using a sensitive credential to authenticate to an external service.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.