Back to skill

Security audit

SMTools Image Generation Skill

Security checks for vulnerabilities and agentic risk

Overview

This image-generation skill is coherent and not deceptive, but it needs review because it sends user content to external providers and has weak controls around downloads and file output paths.

Review before installing. Avoid using this skill with confidential prompts or private images unless you are comfortable sending them to the selected provider. Prefer a dedicated output directory, do not let untrusted text choose output paths, and consider locking dependencies and adding URL/size validation before enabling the Kie provider in sensitive environments.

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

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Unpinned Dependency Installation Creates Supply-Chain Exposure## Vulnerability Details **File Location**: `requirements.txt:1`, `setup.sh:21-32` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Medium ### Vulnerable Code ```text requests>=2.28.0 ``` ```bash # Install dependencies into scripts/vendor/ (works with any system python3) VENDOR_DIR="$SKILL_DIR/scripts/vendor" echo "Installing dependencies into scripts/vendor/..." python3 -m pip install -q --target "$VENDOR_DIR" -r "$SKILL_DIR/requirements.txt" # Also install into venv as fallback if [ ! -d "$VENV_DIR" ]; then echo "Creating virtual environment..." python3 -m venv "$VENV_DIR" else echo "Virtual environment already exists." fi "$VENV_DIR/bin/pip" install -q -r "$SKILL_DIR/requirements.txt" ``` ### Technical Analysis The dependency specification permits any future version of `requests` equal to or newer than 2.28.0. Its transitive dependencies are also resolved dynamically. No lock file, exact version constraints, package hashes, or trusted package repository configuration is present. Consequently, the code installed by `setup.sh` is not the same fixed dependency set that was available during this audit. The setup process installs the mutable dependency tree twice: once into `scripts/vendor/` and once into the virtual environment. Python package installation and later imports can execute package-controlled code. This is a supply-chain hardening weakness rather than evidence that the currently named `requests` package is malicious. ### Attack Path 1. An attacker compromises a permitted future package release, a transitive dependency, or the package delivery channel. 2. The user or agent runs `bash setup.sh`. 3. Pip resolves the newest package versions satisfying `requests>=2.28.0`. 4. The compromised package is installed into `scripts/vendor/` and `.venv`. 5. Package-controlled code executes during installation or when imported by the provide ...[truncated 565 chars]
Remediation
## Remediation Suggestions 1. Replace range-based requirements with reviewed, exact versions for direct and transitive dependencies. 2. Generate a reproducible lock file using a tool such as `pip-tools`. 3. Record and enforce package hashes: ```bash pip install --require-hashes -r requirements.lock ``` 4. Configure pip to use an explicitly trusted package index. 5. Add automated dependency vulnerability and integrity scanning. 6. Avoid installing the same dependencies into two locations unless both are required. 7. Review and deliberately update the lock file rather than resolving arbitrary future releases during setup.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/providers/kie_provider.py:94
Finding
Provider-Controlled Result URL Is Downloaded Without Destination Validation## Vulnerability Details **File Location**: `scripts/providers/kie_provider.py:94-104`, `scripts/providers/kie_provider.py:126-137` **Vulnerability Type**: Server-side request forgery and unbounded response download **Risk Level**: Medium ### Vulnerable Code ```python result_url = self._poll_until_done(task_id, headers) if result_url is None: return { "status": "error", "error": f"Task {task_id} timed out after {self.max_wait}s", "provider": self.name, "model": model, } img_response = requests.get(result_url, timeout=60) img_response.raise_for_status() with open(output_path, "wb") as f: f.write(img_response.content) ``` The URL originates directly from the remote task response: ```python if state == "success": result_json = data.get("resultJson", "{}") result_urls = json.loads(result_json).get("resultUrls", []) return result_urls[0] if result_urls else None if state == "fail": return None ``` ### Technical Analysis The Kie.ai task response controls `result_urls[0]`, which is passed directly to `requests.get`. The implementation does not validate: - That the scheme is HTTPS. - That the hostname belongs to an expected Kie.ai image or CDN domain. - Whether the hostname resolves to loopback, private, link-local, or reserved addresses. - Redirect destinations; `requests` follows redirects by default. - The response content type. - The response size before loading `img_response.content` into memory. - Whether the downloaded content is actually an image. If the provider response, provider account, or upstream service is compromised, the URL can cause the local machine to make requests to destinations that are not required for image generation. Loading the complete response into memory also exposes the process to memory and disk exhaustion. ### Attack Path 1. An attacker gains the ability to influence the Kie task result, ...[truncated 1443 chars]
Remediation
## Remediation Suggestions 1. Permit only HTTPS URLs on an explicit allowlist of documented Kie.ai image/CDN hostnames. 2. Resolve the hostname and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges. 3. Disable automatic redirects or validate every redirect target using the same policy. 4. Stream downloads instead of using `img_response.content`: ```python with requests.get(url, stream=True, timeout=(10, 60), allow_redirects=False) as response: ... ``` 5. Enforce a strict maximum response size using both `Content-Length` and a running byte counter. 6. Require an expected image content type and verify the file signature before saving. 7. Abort and delete partial files when validation or size checks fail. 8. Where supported, request image bytes from a fixed provider endpoint rather than following a provider-supplied arbitrary URL.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/providers/openrouter_provider.py:88
Finding
Caller-Controlled Output Path Can Overwrite Arbitrary User-Writable Files## Vulnerability Details **File Location**: `scripts/providers/openrouter_provider.py:88-97`, `scripts/providers/kie_provider.py:84-105`, `scripts/providers/yandexart_provider.py:103-112` **Vulnerability Type**: Unrestricted file write and overwrite **Risk Level**: Low ### Vulnerable Code ```python if output_path is None: output_dir = get_output_dir(self.config) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_path = output_dir / f"img_{timestamp}.png" else: output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, "wb") as f: f.write(image_bytes) ``` Equivalent unrestricted output-path handling is present in the Kie.ai and YandexART providers. ### Technical Analysis The `--output` command-line argument flows into each provider as `output_path`. When it is supplied, the path is accepted without confinement to the configured image output directory. Absolute paths and traversal sequences are permitted, missing parent directories are created, and `open(..., "wb")` silently truncates an existing target. There are no checks for: - Whether the resolved path remains under an approved output directory. - Existing files or explicit overwrite authorization. - Symbolic-link traversal. - Sensitive file locations. - Whether the filename extension is appropriate for generated image data. A standalone CLI commonly permits explicit output paths, but this skill is intended to be invoked by an AI agent. If untrusted instructions can influence command arguments, unrestricted filesystem writes exceed the minimum privilege required to save generated images. ### Attack Path 1. An attacker supplies a request crafted to influence the agent into selecting a sensitive `--output` path. 2. The agent invokes the skill with that attacker-influenced path. 3. Image generation completes successfully. 4. The provider resolves the caller-supplied ...[truncated 902 chars]
Remediation
## Remediation Suggestions 1. Resolve output paths and require them to remain beneath a dedicated output directory: ```python base = get_output_dir(config).resolve() target = (base / requested_name).resolve() if target != base and base not in target.parents: raise ValueError("Output path escapes the approved output directory") ``` 2. Prefer accepting only a filename rather than an arbitrary path. 3. Reject absolute paths and parent-directory traversal. 4. Refuse to overwrite existing files unless the user explicitly enables an overwrite option. 5. Use exclusive creation mode where appropriate. 6. Defend against symlink races using platform-supported no-follow and directory-relative file-opening mechanisms. 7. Require explicit user confirmation before allowing output outside the dedicated image directory. 8. Apply the same centralized path-validation function to all three providers.
Vulnerability Patterns
  • 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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (43)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
A description-behavior mismatch is a real security concern because users and orchestrators may authorize the skill based on its stated purpose while it also performs environment checks and local filesystem inspection unrelated to core image generation. Even if benign, undisclosed reads of .env, config.json, .venv, or output directories can expose sensitive local context and undermine informed consent and policy enforcement.

Credential Access

High
Category
Privilege Escalation
Content
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
def _load_env_file(env_path: Path) -> None:
    """Parse a .env file and populate os.environ (stdlib only, no dotenv needed)."""
    with open(env_path) as f:
        for line in f:
            line = line.strip()
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
Priority (highest first): env vars > .env > config.json
    """
    # Load .env if present
    env_path = SKILL_ROOT / ".env"
    if env_path.exists():
        _load_env_file(env_path)
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
Priority (highest first): env vars > .env > config.json
    """
    # Load .env if present
    env_path = SKILL_ROOT / ".env"
    if env_path.exists():
        _load_env_file(env_path)
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
Priority (highest first): env vars > .env > config.json
    """
    # Load .env if present
    env_path = SKILL_ROOT / ".env"
    if env_path.exists():
        _load_env_file(env_path)
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
Priority (highest first): env vars > .env > config.json
    """
    # Load .env if present
    env_path = SKILL_ROOT / ".env"
    if env_path.exists():
        _load_env_file(env_path)
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
Priority (highest first): env vars > .env > config.json
    """
    # Load .env if present
    env_path = SKILL_ROOT / ".env"
    if env_path.exists():
        _load_env_file(env_path)
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
Priority (highest first): env vars > .env > config.json
    """
    # Load .env if present
    env_path = SKILL_ROOT / ".env"
    if env_path.exists():
        _load_env_file(env_path)
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
Priority (highest first): env vars > .env > config.json
    """
    # Load .env if present
    env_path = SKILL_ROOT / ".env"
    if env_path.exists():
        _load_env_file(env_path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README describes image generation through OpenRouter, Kie.ai, and YandexART but does not clearly warn that prompts and possibly uploaded images are sent to third-party services. This creates a privacy and data-handling risk because users may disclose sensitive text or files without informed consent.

Session Persistence

Medium
Category
Rogue Agent
Content
### OpenRouter (default provider)

1. Create an account at [openrouter.ai](https://openrouter.ai)
2. Go to **Keys** → **Create key**
3. Copy the key and set it in your environment:
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.

Session Persistence

Medium
Category
Rogue Agent
Content
3. Copy the key and set it in your environment:

```bash
# Add to ~/.zshrc or ~/.bashrc so it persists across sessions
export OPENROUTER_API_KEY="sk-or-..."
```
Confidence
90% 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.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The README says the skill activates automatically when users ask to generate, create, draw, or illustrate an image. That broad trigger language can cause unintended invocation and accidental transmission of user prompts or images to external providers, especially in normal conversation where the user may not realize a networked skill is being used.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill declares capabilities that involve environment access, filesystem reads, and network use, but it does not define an explicit tool scope such as permissions or allowed-tools. In an agent setting, that increases the chance the skill can access more resources than are necessary for image generation, which weakens least-privilege controls and broadens the blast radius if the skill is misused or compromised.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill does not clearly warn users that their prompts and input images may be transmitted to external providers such as OpenRouter, Kie.ai, or YandexART. This is a meaningful privacy and data-governance issue because users may unknowingly send sensitive text or local images to third parties, especially during image editing workflows.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The activation criteria are broad enough to match common requests like 'make a picture' or 'edit this image,' which can cause the skill to trigger in contexts where the user did not intend external processing. In this skill's context, overbroad activation is more dangerous because prompts and possibly input images may be sent to third-party providers, creating privacy and data-handling risks from accidental invocation.

Static analysis

No suspicious patterns detected.