Back to skill

Security audit

Image Generation (Ollama x/z-image-turbo · macOS only)

Security checks for vulnerabilities and agentic risk

Overview

The skill’s image generation purpose is coherent, but it combines broad activation with local command execution and WhatsApp sending without enough user control or safety bounds.

Install only if you are comfortable with a skill that can run a local Ollama command and send generated images or captions to WhatsApp. Use it with explicit recipient confirmation, avoid sensitive prompts, keep any FastAPI service bound to localhost, and add authentication, input limits, rate limits, and safe argument passing before broader use.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:10
Finding
Shell Command Injection Through Prompt Interpolation in the Documented Agent Workflow## Vulnerability Details **File Location**: `SKILL.md`, lines 10-15 **Vulnerability Type**: Shell command injection **Risk Level**: High **Vulnerable Code**: ```bash Exécuter via `exec` avec **pty=true** (obligatoire) : ```bash python3 /Users/openclaw/.openclaw/skills/ollama-x-z-image-turbo/runner.py \ --prompt "<PROMPT>" \ --width 1024 --height 1024 --steps 20 \ --out /Users/openclaw/.openclaw/workspace/tmp/ollama_image.png -v ``` ### Technical Analysis The Skill instructs the Agent to insert an untrusted, user-supplied image prompt directly into a shell command. Wrapping the placeholder in double quotes does not provide adequate shell escaping. A prompt containing a double quote can terminate the intended argument, after which shell operators or substitutions can be interpreted by the command processor. Although `runner.py` invokes Ollama safely through `subprocess.run()` with an argument list and without `shell=True`, that protection does not address the outer shell command recommended by `SKILL.md`. The injection occurs before Python starts, when the Agent's execution tool parses the constructed command. ### Attack Path 1. An attacker submits an image-generation request containing shell metacharacters and a double quote designed to terminate the `--prompt` argument. 2. The Agent replaces `<PROMPT>` with the attacker-controlled text as instructed by `SKILL.md`. 3. The Agent sends the resulting string to a shell-capable execution tool. 4. The shell interprets the injected operators or substitutions as commands rather than prompt data. 5. The injected command executes with the operating-system privileges and environment available to the Agent process. ### Impact Assessment Successful exploitation could provide arbitrary command execution under the Agent's account. The attacker could access files readable or writable by that account, modify workspace or Skill content, invoke locally availa ...[truncated 206 chars]
Remediation
## Remediation Suggestions - Do not build a shell command by interpolating the prompt into a command string. - Invoke the Python executable through a structured argument-array interface, passing the entire prompt as one argument without shell parsing. - If the execution environment cannot accept argument arrays, pass the prompt through standard input or a securely created data file. - If shell execution is unavoidable, apply a platform-appropriate escaping function to every dynamic argument. Manual wrapping with quotes is not sufficient. - Update `SKILL.md` to prescribe a structured tool invocation rather than a copyable shell template. - Run the image generator with least privilege and without access to unrelated credentials or sensitive directories.

T09 · Insecure Skill Coding Practices

Warning
Location
generate_image.py:13
Finding
Unauthenticated and Unbounded Image Generation Enables Resource Exhaustion## Vulnerability Details **File Location**: `generate_image.py`, lines 13-57 **Vulnerability Type**: Missing authentication, input bounds, and resource controls **Risk Level**: Medium **Vulnerable Code**: ```python class ImageRequest(BaseModel): prompt: str width: int = 1024 height: int = 1024 steps: int = 20 seed: int | None = None negative: str | None = None @app.post("/generate-image/", response_class=Response) async def generate_image(request: ImageRequest): url = f"{OLLAMA_URL}/api/generate" payload = { "model": MODEL, "prompt": request.prompt, "stream": False, "options": { "width": request.width, "height": request.height, "steps": request.steps, }, } if request.seed is not None: payload["options"]["seed"] = request.seed if request.negative: payload["options"]["negative"] = request.negative r = requests.post(url, json=payload, timeout=180) if r.status_code != 200: raise HTTPException(status_code=500, detail=r.text) data = r.json() # Ollama image models return base64 in images[] if "images" in data and data["images"]: img_b64 = data["images"][0] elif "response" in data: img_b64 = data["response"] else: raise HTTPException(status_code=500, detail="No image data in response") try: img = base64.b64decode(img_b64) except Exception: raise HTTPException(status_code=500, detail="Invalid image base64") return Response(content=img, media_type="image/png") ``` ### Technical Analysis The image-generation route has no authentication, authorization, rate limiting, or concurrency control. The request model accepts arbitrary integers for `width`, `height`, and `steps`, while prompt and negative-prompt lengths are unrestricted. Attackers who can reac ...[truncated 1530 chars]
Remediation
## Remediation Suggestions - Require authentication for the endpoint and authorize users before permitting model execution. - Bind the service to loopback by default unless remote access is explicitly required. - Enforce strict Pydantic constraints for prompt length, negative-prompt length, dimensions, and step count. - Reject non-positive dimensions and step values, and define conservative maximum values supported by the deployment. - Add per-user rate limits, global concurrency limits, request queues, and resource quotas. - Replace blocking `requests` calls in asynchronous handlers with an asynchronous HTTP client, or execute blocking work in a bounded thread pool. - Enforce maximum upstream response and decoded-image sizes before retaining data in memory. - Return sanitized upstream errors rather than exposing the complete Ollama response through `detail=r.text`. - Monitor generation latency, rejected requests, concurrency, and resource consumption.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
whatsapp_integration.py:7
Finding
Unauthenticated WhatsApp Relay Permits Unauthorized Outbound Messaging## Vulnerability Details **File Location**: `whatsapp_integration.py`, lines 7-17 **Vulnerability Type**: Missing authentication and outbound-message authorization **Risk Level**: Medium **Vulnerable Code**: ```python @app.post("/send-to-whatsapp/") async def send_to_whatsapp(image_url: str, message: str, recipient: str): # Logique pour envoyer l'image à WhatsApp whatsapp_api_url = "https://api.whatsapp.com/send" payload = { "phone": recipient, "body": f"{message} \n Voici votre image : {image_url}" } response = requests.post(whatsapp_api_url, data=json.dumps(payload), headers={'Content-Type': 'application/json'}) return response.json() ``` ### Technical Analysis The endpoint accepts an arbitrary recipient, message, and image URL without authenticating the caller or verifying that the caller is authorized to contact the specified recipient. No rate limit, recipient allowlist, message-length restriction, or URL validation is applied. The handler also performs synchronous outbound I/O in an asynchronous route and does not configure a request timeout. A slow or unresponsive upstream connection may therefore block a worker indefinitely. The practical messaging behavior depends on the external WhatsApp endpoint and any session or credentials available in the deployment; the source code itself contains no hard-coded WhatsApp credentials. ### Attack Path 1. An unauthorized caller reaches `/send-to-whatsapp/`. 2. The caller supplies an arbitrary recipient, message, and image URL. 3. The server constructs a WhatsApp payload directly from those values. 4. The server submits the attacker-controlled payload to the external WhatsApp endpoint. 5. If the deployment has a usable authenticated WhatsApp context and the external API accepts the request, the attacker can cause unauthorized messaging. 6. Independently, repeated calls or deliberately slow upstream responses can occupy ...[truncated 513 chars]
Remediation
## Remediation Suggestions - Require strong authentication for the route. - Authorize each caller for the requested recipient and maintain an allowlist or verified-recipient registry where appropriate. - Use an officially supported, authenticated WhatsApp API and keep credentials in a dedicated secret manager. - Apply per-user and global rate limits, messaging quotas, and abuse monitoring. - Validate recipient syntax and enforce maximum lengths for messages and URLs. - Restrict image URLs to approved schemes and, where appropriate, approved hosts. - Configure finite connection and response timeouts for every outbound request. - Use an asynchronous HTTP client in the async route or move blocking calls to a bounded worker pool. - Handle non-JSON responses and upstream failures safely rather than returning `response.json()` unconditionally. - Record auditable sender, recipient, and request-result metadata without logging sensitive message content unnecessarily.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (12)

Tainted flow: 'url' from os.getenv (line 23, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
if request.negative:
        payload["options"]["negative"] = request.negative

    r = requests.post(url, json=payload, timeout=180)
    if r.status_code != 200:
        raise HTTPException(status_code=500, detail=r.text)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
La description indique que l’image générée est envoyée sur WhatsApp sans avertissement clair sur la transmission de données à un service externe. Si le prompt ou l’image contient des informations sensibles, l’utilisateur peut ne pas comprendre qu’un contenu dérivé de sa demande sera partagé hors de l’environnement local.

Vague Triggers

Medium
Confidence
94% confidence
Finding
Le déclenchement repose sur des formulations très générales autour de la génération d’image, ce qui peut activer la skill lors de demandes ambiguës ou conversationnelles. Comme la skill exécute ensuite une commande locale puis envoie automatiquement le résultat sur WhatsApp, une activation involontaire peut entraîner une action externe non désirée et une fuite de contenu vers un tiers.

Vague Triggers

Medium
Confidence
92% confidence
Finding
L’exemple « Fais une image de … » est suffisamment vague pour recouvrir du langage courant, ce qui augmente le risque de déclenchement involontaire. Dans ce contexte, ce risque est aggravé par le fait que la skill ne se limite pas à générer un fichier local mais prévoit aussi sa transmission via un canal externe.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
With no manifest available, the skill's purpose is unknown, so invoking the external `ollama` CLI as a subprocess is an unjustified capability under the provided policy. The code is not merely formatting or parsing data; it launches another program and depends on its side effects to generate files.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
t0 = time.time()
    try:
        res = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, cwd=cwd)
    except subprocess.TimeoutExpired:
        elapsed = time.time() - t0
        log.error("Timeout after %.1fs", elapsed)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

External Transmission

Medium
Category
Data Exfiltration
Content
payload = {"model": "x/z-image-turbo", "prompt": "a cartoon cat astronaut on the moon, with big eyes and colorful fur"}

# Appel de l'API
response = requests.post(url, json=payload)

# Affichage du résultat
if response.status_code == 200:
Confidence
80% 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
payload = {"model": "x/z-image-turbo", "prompt": "a cartoon cat astronaut on the moon, with big eyes and colorful fur"}

# Appel de l'API
response = requests.post(url, json=payload)

# Affichage du résultat
if response.status_code == 200:
Confidence
80% 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
@app.post("/send-to-whatsapp/")
async def send_to_whatsapp(image_url: str, message: str, recipient: str):
    # Logique pour envoyer l'image à WhatsApp
    whatsapp_api_url = "https://api.whatsapp.com/send"
    payload = {
        "phone": recipient,
        "body": f"{message} \n Voici votre image : {image_url}"
Confidence
86% confidence
Finding
The code performs an external network transmission to WhatsApp using data derived directly from request parameters. External transmission is expected for this feature, but without safeguards such as consent, access control, validation, and abuse prevention, it creates data exfiltration and unauthorized messaging risk.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This endpoint transmits user-supplied recipient, message, and image URL to an external WhatsApp service without any visible authorization, confirmation, validation, or abuse controls. In a skill context, this can enable unintended third-party messaging, privacy leakage, and spam/abuse if an upstream agent or user can invoke the endpoint freely.

Missing User Warnings

Low
Confidence
84% confidence
Finding
This code sends the prompt payload to a local HTTP API using requests.post, but there is no confirmation prompt, warning comment about data transmission, or other user disclosure beyond a generic example label. Because network calls can transmit user-provided content, the file should make that behavior explicit.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The natural-language strings in the skill are written in French, including the inline comment and the user-visible message fragment 'Voici votre image'. This imposes a specific language on the interaction without any mechanism for user choice or documented locale constraint.

Static analysis

No suspicious patterns detected.