Back to skill

Security audit

GLM Image Generator

Security checks for vulnerabilities and agentic risk

Overview

This image skill mostly does what it claims, but it looks through too many local config and .env files for API keys, including another agent config file, so it should be reviewed before installation.

Review this before installing. Use explicit GLM_API_KEY or OPENROUTER_API_KEY environment variables, force the intended provider with --provider when possible, avoid running it in directories containing unrelated .env or config.json files, and do not put confidential information in prompts sent to GLM or OpenRouter.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:43
Finding

Overbroad Credential Discovery Can Transmit an Unrelated Secret to the GLM API

Content
View full analysis
str | None: """Try env var → config files → .env files. Returns None if not found.""" value = os.environ.get(env_var) if value: return value config_paths = [ "config.json", os.path.expanduser("~/.openclaw/config.json"), os.path.expanduser("~/.claude/config.json"), ] for path in config_paths: if os.path.exists(path): try: with open(path) as f: data = json.load(f) if data.get(config_key): return data[config_key] except Exception: pass script_dir = os.path.dirname(os.path.abspath(__file__)) skill_dir = os.path.dirname(script_dir) env_paths = [ os.path.join(skill_dir, ".env"), ".env", os.path.expanduser("~/.env"), ] for path in env_paths: if os.path.exists(path): try: with open(path) as f: for line in f: line = line.strip() if line.startswith(f"{env_var}="): val = line.split("=", 1)[1].strip().strip('"') if val: return val except Exception: pass return None ``` The GLM loader supplies the generic configuration property name `api_key`: ```python def load_glm_key() -> str: key = _load_key_from_env_and_configs("GLM_API_KEY", "api_key") ``` The selected value is subsequently transmitted as an authorization credential: ```python def generate_image_glm( prompt: str, size: str = "10 ...[truncated 3583 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (31)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The metadata and description present the skill as a GLM-Image API generator, but the body also supports OpenRouter, auto-selects between providers, and reads credentials from multiple local sources. This is security-relevant behavior expansion because user prompts may be transmitted to a different third-party service than expected, and local credential discovery reaches beyond the narrowly described purpose.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · CHANGELOG.md (reported line 37)May include surrounding context.

bash
export GLM_API_KEY=your-key
# or add to ~/.openclaw/config.json: { "api_key": "your-key" }
# or add GLM_API_KEY=your-key to .env

Option B — OpenRouter

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

bash
export GLM_API_KEY=your-key
# or add to ~/.openclaw/config.json: { "api_key": "your-key" }
# or add GLM_API_KEY=your-key to .env

Option B — OpenRouter

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

bash
export GLM_API_KEY=your-key
# or add to ~/.openclaw/config.json: { "api_key": "your-key" }
# or add GLM_API_KEY=your-key to .env

Option B — OpenRouter

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
96% confidence
Finding

The Step 0 command reads from ~/.claude/config.json and ~/.openclaw/config.json to discover credentials. Accessing a general agent config directory is sensitive because those files may contain unrelated secrets or settings, and the skill expands its reach into broader local configuration data without strict minimization.

Content

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

md
glm = bool(os.environ.get('GLM_API_KEY'))
orouter = bool(os.environ.get('OPENROUTER_API_KEY'))
if not glm and not orouter:
    for p in ['~/.openclaw/config.json', '~/.claude/config.json']:
        try:
            d = json.loads(pathlib.Path(p).expanduser().read_text())
            if d.get('api_key'): glm = True

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

The helper is explicitly designed to pull API keys from environment variables, config files, and .env files, including locations outside the immediate invocation context. That is credential access behavior beyond minimal need and is risky in an agent skill because it normalizes broad secret harvesting from the user's environment.

Content

Scanner excerpt · scripts/generate.py (reported line 49)May include surrounding context.

python
# ---------------------------------------------------------------------------

def _load_key_from_env_and_configs(env_var: str, config_key: str) -> str | None:
    """Try env var → config files → .env files. Returns None if not found."""
    value = os.environ.get(env_var)
    if value:
        return value

Agent Config Directory Access

High
Category
Agent Snooping
Confidence
98% confidence
Finding

Reading ~/.claude/config.json gives the skill access to the agent's broader configuration directory, which may contain unrelated credentials or sensitive settings. In a skill ecosystem, this is particularly dangerous because a seemingly narrow image-generation tool reaches into another app's config space, violating least privilege and increasing the blast radius of misuse or future code changes.

Content

Scanner excerpt · scripts/generate.py (reported line 57)May include surrounding context.

python
config_paths = [
        "config.json",
        os.path.expanduser("~/.openclaw/config.json"),
        os.path.expanduser("~/.claude/config.json"),
    ]
    for path in config_paths:
        if os.path.exists(path):

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

Including the skill directory .env as an automatic credential source can unexpectedly ingest secrets stored alongside code or packaged artifacts. In an agent environment, this encourages hidden secret discovery rather than explicit secret provision and increases the chance of unauthorized credential use.

Content

Scanner excerpt · scripts/generate.py (reported line 72)May include surrounding context.

python
script_dir = os.path.dirname(os.path.abspath(__file__))
    skill_dir = os.path.dirname(script_dir)
    env_paths = [
        os.path.join(skill_dir, ".env"),
        ".env",
        os.path.expanduser("~/.env"),
    ]

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

Reading a generic current-working-directory .env allows the skill to consume unrelated project secrets opportunistically. That violates least privilege and can lead to unintended credential use if the skill is run inside another repository or workspace containing sensitive environment files.

Content

Scanner excerpt · scripts/generate.py (reported line 73)May include surrounding context.

python
skill_dir = os.path.dirname(script_dir)
    env_paths = [
        os.path.join(skill_dir, ".env"),
        ".env",
        os.path.expanduser("~/.env"),
    ]
    for path in env_paths:

Credential Access

High
Category
Privilege Escalation
Confidence
98% confidence
Finding

Reading ~/.env is broad home-directory credential access and can expose many unrelated secrets to this skill. In context, an image-generation tool has no strong reason to trawl the user's home-level environment file, making this disproportionately risky.

Content

Scanner excerpt · scripts/generate.py (reported line 74)May include surrounding context.

python
env_paths = [
        os.path.join(skill_dir, ".env"),
        ".env",
        os.path.expanduser("~/.env"),
    ]
    for path in env_paths:
        if os.path.exists(path):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/generate.py (reported line 127)May include surrounding context.

python
"Then set it via:\n"
            "  export GLM_API_KEY=your-key\n"
            "  or add to ~/.openclaw/config.json: {\"api_key\": \"your-key\"}\n"
            "  or add GLM_API_KEY=your-key to .env"
        )
    return key

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/generate.py (reported line 141)May include surrounding context.

python
"Then set it via:\n"
            "  export GLM_API_KEY=your-key\n"
            "  or add to ~/.openclaw/config.json: {\"api_key\": \"your-key\"}\n"
            "  or add GLM_API_KEY=your-key to .env"
        )
    return key

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The README clearly instructs users to send image-generation prompts to third-party providers (GLM/Zhipu AI or OpenRouter) but does not explicitly disclose that prompts and possibly related metadata are transmitted off-platform. This creates a privacy and data-handling risk because users may enter sensitive or proprietary content without realizing it will be sent to external services with their own logging, retention, and policy controls.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill invokes shell/Python commands that inspect environment variables, read local config files, and send prompts to external providers, yet it declares no explicit tool scope or permissions. This weakens least-privilege controls and makes it harder for users or the platform to understand that the skill needs env, filesystem, and network access before execution.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

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

md
---
name: glm-image
description: Generate images using GLM-Image API. Use when the user wants to generate, create, or draw an image from a text prompt. Triggers on requests like "generate an image of...", "create a picture of...", "draw...", or any image generation request.
---

# GLM-Image Generator

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest says to use the skill when the user wants to "generate, create, or draw an image" and adds "or any image generation request," which is an open-ended activation condition. While some examples are specific, the catch-all phrasing lacks clear boundaries or exclusions and can lead to unintended invocation on loosely related creative requests.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill does not prominently warn that user prompts and related parameters will be sent over the network to external image-generation providers. This creates a privacy and data-handling risk because users may disclose sensitive information under the assumption the operation is local or limited to a single provider.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The instructions require asking the user to choose from a fixed language list and explicitly prohibit using the language already present in the user's message. This imposes a rigid language/locale policy on all users rather than offering a choice or honoring the user's existing language context.

Content

No source excerpt is available for this finding.

Tainted flow: 'url' from requests.post (line 102, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/generate.py (reported line 104)May include surrounding context.

python
try:
        url = f"https://openrouter.ai/api/v1/generation?id={generation_id}"
        headers = {"Authorization": f"Bearer {api_key}"}
        resp = requests.get(url, headers=headers, timeout=15)
        resp.raise_for_status()
        return resp.json().get("data", {}).get("total_cost")
    except Exception:

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/generate.py (reported line 184)May include surrounding context.

python
"watermark_enabled": str(watermark).lower(),
    }

    response = requests.post(url, headers=headers, json=body, timeout=120)
    response.raise_for_status()
    data = response.json()

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/generate.py (reported line 242)May include surrounding context.

python
"watermark_enabled": str(watermark).lower(),
    }

    response = requests.post(url, headers=headers, json=body, timeout=120)
    response.raise_for_status()
    data = response.json()

Tainted flow: 'url' from requests.post (line 102, network input) → requests.post (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/generate.py (reported line 184)May include surrounding context.

python
"watermark_enabled": str(watermark).lower(),
    }

    response = requests.post(url, headers=headers, json=body, timeout=120)
    response.raise_for_status()
    data = response.json()

Tainted flow: 'url' from requests.post (line 102, network input) → requests.post (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/generate.py (reported line 242)May include surrounding context.

python
"watermark_enabled": str(watermark).lower(),
    }

    response = requests.post(url, headers=headers, json=body, timeout=120)
    response.raise_for_status()
    data = response.json()

Tainted flow: 'image_url' from requests.post (line 191, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
86% confidence
Finding

The script fetches image_url directly from the GLM API response and performs a second requests.get without validating the host, scheme, or content type. If the upstream service or response is compromised, this creates an SSRF-style pivot or arbitrary remote download behavior from the user's machine, which is more dangerous in an agent context because it runs with local network access and writes the response to disk.

Content

Scanner excerpt · scripts/generate.py (reported line 199)May include surrounding context.

python
filename = f"{timestamp}_{safe_prompt}.png"
    filepath = os.path.join(output_dir, filename)

    img_response = requests.get(image_url, timeout=60)
    img_response.raise_for_status()
    with open(filepath, "wb") as f:
        f.write(img_response.content)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The CLI requires --language and restricts it to a hard-coded set of seven languages. This is a natural-language policy concern because it forces a locale/language constraint rather than offering a free-form default or optional preference, and the file does not document a region-specific justification for that limitation.

Content

No source excerpt is available for this finding.