Back to skill

Security audit

Scrapbook-Style Illustration Inserter

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent image-generation helper, but it can read unrelated local credential files and may send an unintended key to an external provider.

Review before installing. Use only explicit GLM_API_KEY or OPENROUTER_API_KEY environment variables, avoid running it from projects with unrelated .env/config secrets, and consider removing ~/.claude/config.json, ~/.env, current-directory .env, and generic api_key lookup before use. Do not run it on confidential drafts unless you are comfortable sending article-derived prompts to GLM or OpenRouter.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:42
Finding

Overbroad Credential Discovery Can Transmit an Unrelated API Key

Content
View full analysis

Vulnerability Details

File Location: scripts/generate.py, lines 42–83 and 116–117
Vulnerability Type: Overbroad credential access and unintended credential disclosure
Risk Level: Medium

Vulnerable Code

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

    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
python
def load_glm_key() -> str:
    key = _load_key_from_env_and_configs("GLM_API_KEY", "api_key")

The selected value is subsequently transmitted to the GLM API:

python
headers = {
    "Content-Type": "application/json",
    "Authorization": f"
...[truncated 2527 chars]
Remediation
View remediation

Remediation Suggestions

  1. Use only provider-specific configuration names:
    • GLM_API_KEY or glm_api_key
    • OPENROUTER_API_KEY or openrouter_api_key
  2. Replace the GLM lookup with:
    python
    key = _load_key_from_env_and_configs("GLM_API_KEY", "glm_api_key")
    
  3. Remove generic api_key fallback behavior.
  4. Stop searching unrelated user-wide locations such as ~/.claude/config.json and ~/.env.
  5. Restrict file-based discovery to the documented Skill .env and ~/.openclaw/config.json, or require an explicit configuration path.
  6. Make provider selection depend on an explicitly named provider credential rather than the existence of a generic field.
  7. Validate that the documentation and implementation use identical configuration names.
  8. Consider requiring user confirmation before using file-discovered credentials, particularly when auto-selecting a provider.
  9. Replace broad exception suppression with narrow error handling and non-sensitive diagnostics so configuration errors are visible without printing secret values.

T08 · Insecure Dependencies

Note
Location
README.md:26
Finding

Unpinned Runtime Dependency Installation

Content
View full analysis

Vulnerability Details

File Location: README.md, line 26
Vulnerability Type: Unpinned third-party dependency
Risk Level: Low

Vulnerable Code

markdown
- Python 3 + `requests` library: `pip install requests`

Technical Analysis

The documented installation command retrieves the latest available version of requests and its transitive dependencies without a version constraint, lock file, or integrity hashes. Consequently, installations are not reproducible, and the code that executes can differ over time despite the Skill package remaining unchanged.

The package name is the legitimate requests project, and the audit found no dependency confusion name, typosquatting, or explicitly unsafe package source. The risk arises from mutable dependency resolution rather than evidence of a currently malicious dependency.

Because requests runs inside the image-generation process, dependency code executes with the same operating-system privileges as the user. It can access the prompts and provider credentials handled by the process.

Attack Path

  1. A user follows the documented pip install requests command.
  2. The package installer resolves whatever versions are current at installation time.
  3. A compromised, malicious, or unexpectedly incompatible future release is downloaded because no reviewed version or hash is enforced.
  4. The dependency executes when scripts/generate.py imports and uses requests.
  5. Compromised dependency code would run with the user's privileges and could access in-process API credentials, prompts, local files available to the user, and network resources.

Impact Assessment

A compromised dependency could execute arbitrary Python code with the privileges of the user running the Skill. This could expose GLM or OpenRouter credentials, article content, generated images, and other files readable by that user.

No currently compromised package was identified, so this ...[truncated 88 chars]

Remediation
View remediation

Remediation Suggestions

  1. Add a reviewed, version-pinned dependency file, for example:
    text
    requests==<reviewed-version>
    
  2. Prefer a hash-locked requirements file generated by a dependency-management tool:
    text
    requests==<reviewed-version> \
        --hash=sha256:<verified-hash>
    
  3. Pin and review transitive dependencies rather than constraining only the top-level package.
  4. Document installation into an isolated virtual environment.
  5. Use a trusted package index explicitly and avoid adding unreviewed third-party indexes.
  6. Establish a dependency-update process that reviews release notes, vulnerability advisories, and integrity hashes before changing pinned versions.
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
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (24)

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

The helper is explicitly designed to pull API credentials from ambient sources such as config files and .env files. In a skill execution context, broad secret discovery is dangerous because it lets the skill consume credentials the user did not explicitly pass for this run, undermining least privilege and increasing the chance of secret misuse or accidental disclosure.

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
97% confidence
Finding

Reading ~/.claude/config.json gives the skill access to agent configuration material outside its narrow image-generation purpose. In an agent ecosystem, cross-skill or agent config access is dangerous because it can harvest secrets or metadata the user did not intend this skill to consume, increasing blast radius if the skill is abused or modified.

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
96% confidence
Finding

Accessing a skill-local .env file still constitutes implicit secret harvesting when the skill auto-discovers credentials rather than receiving them explicitly. This broadens the secret surface and can unintentionally bind the skill to credentials left on disk by other tools or previous sessions.

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
96% confidence
Finding

Reading the current working directory's .env file can capture unrelated application secrets simply because the script is launched from that directory. In agent workflows this is especially risky because working directories often contain project credentials unrelated to image generation.

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
97% confidence
Finding

Reading ~/.env is an especially broad form of credential access because it may contain many unrelated user secrets. A skill that auto-loads from a home-directory secret store exceeds least-privilege boundaries and creates unnecessary exposure if the skill or its logs/errors are ever mishandled.

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
95% confidence
Finding

The README states that images are generated through the GLM-Image API but does not clearly warn that user-provided article content, prompts derived from it, or related metadata may be sent to a third-party service. For a skill that processes potentially unpublished or sensitive drafts, this omission creates a real privacy and data-handling risk because users may not understand their content is leaving the local agent environment.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The invocation examples are broad enough to match ordinary writing or editing requests, which can cause the skill to activate in situations where the user did not clearly consent to image generation or external processing. In this skill's context, that increases the chance of unintended transformation of content and inadvertent transmission of article text to the third-party image service.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill instructs the agent to access environment/config secrets and make outbound network calls to third-party image providers, but it declares no explicit tool scope or permissions boundary. This can lead to overbroad execution in agent environments, making secret access and external transmission possible without clear least-privilege controls.

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: article-illustrator
description: Insert scrapbook-style illustrations into articles using image generation (GLM or OpenRouter). Use when the user wants to add images to an article, create illustrated content, or requests "illustrate this article", "add images to this article", "generate pictures for this post", "insert illustrations".
---

# Scrapbook-Style Illustration Inserter

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger phrases are broad and map to common user requests like 'create illustrated content' or 'generate pictures for this post,' which raises the chance of unintended activation. In practice, this could cause the agent to invoke a networked, credential-using workflow when the user did not explicitly intend to run this particular skill.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The prompt explicitly requires output in Chinese via phrases like '用流畅中文描述' without indicating that this should depend on the user's language or a documented product requirement. This can override user preference, reduce accessibility, and cause unexpected behavior in multilingual contexts, especially if downstream consumers expect the response language to match the article or user input.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill searches multiple local config and .env locations, including user-wide files unrelated to the immediate invocation, to obtain credentials. In an agent setting this expands the skill's access to ambient secrets beyond explicit inputs and can violate least-privilege expectations, especially because the skill also transmits data to external services.

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
88% confidence
Finding

The script fetches image_url directly from the GLM API response and performs a second requests.get() without validating the scheme, host, or content type. If the upstream service, account, or response path is compromised, this becomes an SSRF-style outbound fetch primitive that can contact arbitrary URLs and write attacker-controlled bytes 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
93% confidence
Finding

The CLI requires a --language argument and limits it to seven hard-coded languages. This enforces a locale/language constraint rather than offering an open choice or documenting a region-specific reason, which matches the policy's language/locale violation criterion.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The trigger phrases are broad enough to match ordinary user requests for adding images to content, which can cause the skill to activate unexpectedly outside a clearly bounded context. In a skill with exec permission and external image-provider integrations, overbroad activation increases the chance of unintended tool use, cost incurrence, and article content being sent to third-party services without sufficiently explicit user intent.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

Language-specific triggers like 'illustrate in english' and 'illustrate in chinese' can steer invocation based on language constraints that are not tied to explicit user preference handling in the manifest. This can create confusing or unintended activation behavior, especially for multilingual users, but the direct security impact is limited compared with code execution or data exfiltration flaws.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

Line L27 states the language parameter defaulted to zh (Chinese), which indicates the skill previously forced a specific language unless the user explicitly chose otherwise. This is a natural-language locale policy concern because it imposed a language default rather than requiring user opt-in.

Content

No source excerpt is available for this finding.