Back to skill

Security audit

Generate Image

Security checks for vulnerabilities and agentic risk

Overview

This image-generation skill has a coherent purpose, but it lets article metadata steer credential-bearing image requests to arbitrary endpoints.

Install only if you trust the article drafts being processed and the configured image endpoint. Avoid using this with untrusted Markdown frontmatter or sensitive drafts until endpoint overrides are restricted and subprocess environment variables are minimized.

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
runtime.py:139
Finding
Article Frontmatter Can Redirect Credential-Bearing Image Generation Requests<![CDATA[ ## Vulnerability Details **File Location**: `runtime.py:31-43`, `runtime.py:139-164`, and `runtime.py:184-218` **Vulnerability Type**: Unvalidated externally controlled API endpoint **Risk Level**: High ### Vulnerable Code ```python def run_json_command( args: list[str], *, cwd: Path | None = None, env_overrides: dict[str, str] | None = None, ) -> dict[str, Any]: env = os.environ.copy() if env_overrides: env.update({key: value for key, value in env_overrides.items() if str(value or "").strip()}) result = subprocess.run( args, check=False, capture_output=True, text=True, cwd=str(cwd) if cwd else None, env=env, ) ``` ```python def resolve_image_backend( *, article_path: Path, image_provider: str | None = None, image_api_base: str | None = None, image_model: str | None = None, ) -> dict[str, str]: defaults = default_image_backend() frontmatter = load_article_frontmatter(article_path) frontmatter_provider = clean_optional_value(frontmatter.get("image_provider")) frontmatter_api_base = clean_optional_value(frontmatter.get("image_api_base")) frontmatter_model = clean_optional_value(frontmatter.get("image_model")) requested_provider = clean_optional_value(image_provider) requested_api_base = clean_optional_value(image_api_base) requested_model = clean_optional_value(image_model) provider = requested_provider or frontmatter_provider or defaults["provider"] api_base = requested_api_base or frontmatter_api_base or defaults["apiBase"] model = requested_model or frontmatter_model or defaults["model"] ``` ```python image_backend = resolve_image_backend( article_path=article_path, image_provider=image_provider, image_api_base=image_api_base, image_model=image_model, ) command = [ "md2wechat", "generate_image", "--preset", preset, ...[truncated 3494 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not allow article frontmatter to select an arbitrary API base. Treat endpoint configuration as trusted administrator configuration rather than article content. 2. If per-article endpoints are required, enforce an explicit allowlist of approved HTTPS hostnames and ports. 3. Parse and canonicalize the URL before use. Reject: - Non-HTTPS schemes - Embedded credentials - Loopback, private, link-local, multicast, and unspecified addresses - Cloud metadata endpoints - Unexpected ports 4. Resolve the hostname and validate every resolved address. Repeat destination validation after redirects to reduce DNS rebinding and redirect-based bypasses. 5. Pass a minimal subprocess environment instead of `os.environ.copy()`. Include only variables required for execution. 6. Scope provider credentials to one approved provider and endpoint where possible. Avoid forwarding a credential when a non-default endpoint is selected. 7. Separate trusted runtime overrides from article-controlled metadata and record endpoint-selection decisions in security logs without logging secrets. 8. Add tests proving that malicious frontmatter values such as loopback URLs, `file:` URLs, internal IP addresses, and unapproved domains are rejected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
runtime.py:66
Finding
Binary Download Helper Permits Local File Copy and Unrestricted Network Retrieval<![CDATA[ ## Vulnerability Details **File Location**: `runtime.py:66-87` **Vulnerability Type**: Arbitrary local-file read/copy and unsafe URL retrieval primitive **Risk Level**: Medium ### Vulnerable Code ```python def download_binary(url: str, output_path: Path) -> None: ensure_parent(output_path) value = str(url or "").strip() if not value: raise RuntimeError("下载图片失败:缺少可用地址。") parsed = urlparse(value) if parsed.scheme in {"", "file"}: local_value = unquote(parsed.path) if parsed.scheme == "file" else value local_path = Path(local_value).expanduser() if local_path.exists() and local_path.is_file(): output_path.write_bytes(local_path.read_bytes()) return request = urllib.request.Request(value, headers={"User-Agent": "content-factory/1.0"}) try: with urllib.request.urlopen(request, timeout=60) as response: output_path.write_bytes(response.read()) except urllib.error.URLError as error: raise RuntimeError(f"下载图片失败:{error}") from error ``` ### Technical Analysis `download_binary` treats values with no URL scheme and `file:` URLs as local paths. It expands user-home notation and copies any readable regular file to the caller-selected output path. There is no restriction requiring the source to reside in an approved generated-assets directory. For network resources, the helper passes the supplied value to `urllib.request.urlopen` without enforcing HTTPS, restricting hosts, checking resolved addresses, validating redirects, verifying that the response is an image, or imposing a maximum response size. Calling `response.read()` without a limit loads the complete response into memory before writing it. The audited project contains no call site for this helper, so direct exploitability within the four-file artifact is contingent on another runtime component invoking it with attacker-influenced input. Nevertheless, the function itself exposes an unsa ...[truncated 1921 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove support for local paths and `file:` URLs unless it is strictly required. 2. If local assets must be supported, resolve the source with `Path.resolve()` and require it to remain inside a dedicated, trusted asset directory. 3. Restrict remote retrieval to HTTPS and an explicit allowlist of approved image hosts. 4. Resolve destination hostnames and reject loopback, private, link-local, multicast, unspecified, and cloud metadata addresses. 5. Disable redirects or validate the scheme, hostname, port, and resolved address after every redirect. 6. Stream responses in bounded chunks rather than using an unlimited `response.read()`. 7. Enforce both a maximum `Content-Length` and a hard maximum number of downloaded bytes. 8. Verify the response MIME type and inspect file signatures to ensure the result is a supported image format. 9. Resolve and constrain `output_path` to the intended artifact directory to prevent writes elsewhere. 10. Run asset retrieval with minimal filesystem permissions, restricted network egress, and no access to unrelated credentials. 11. Add tests for local path traversal, `file:` URLs, internal IP addresses, redirects to internal hosts, malformed images, and oversized responses. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Env Variable Harvesting

High
Category
Data Exfiltration
Content
cwd: Path | None = None,
    env_overrides: dict[str, str] | None = None,
) -> dict[str, Any]:
    env = os.environ.copy()
    if env_overrides:
        env.update({key: value for key, value in env_overrides.items() if str(value or "").strip()})
    result = subprocess.run(
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The README states that the skill will call an external image generation capability, but it does not clearly warn that article-derived content may be transmitted to a third-party service. In a content pipeline, drafts can contain unpublished, proprietary, or sensitive information, so missing disclosure increases the risk of inadvertent data exposure and unsafe use in regulated or confidential environments.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill declares broad operational behavior that includes reading article drafts, writing PNG outputs, invoking a shared runtime, and using a remote image API, but it does not define any explicit tool scope or permission boundaries. That makes the effective authority ambiguous and increases the risk that a runner grants unnecessary file, network, shell, or environment access beyond what image generation actually requires.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
This helper executes an external command via subprocess.run and propagates environment overrides, but the file provides no confirmation prompt, visible logging, or explanatory comment/docstring about that action. Subprocess execution is a safety-relevant operation under the rule and should have some user disclosure unless clearly covered by the skill description.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
env = os.environ.copy()
    if env_overrides:
        env.update({key: value for key, value in env_overrides.items() if str(value or "").strip()})
    result = subprocess.run(
        args,
        check=False,
        capture_output=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
`download_binary` accepts arbitrary URLs and also treats empty-scheme or `file:` values as local filesystem paths, then copies their bytes to an output path. In an agent skill, this creates a file-read and exfiltration primitive and also enables SSRF-like outbound fetching unrelated to the stated purpose of generating article companion images.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The function downloads data from a URL and writes it to the filesystem, but there is no confirmation prompt, user-facing log/print, or explanatory comment/docstring describing that behavior. For code files, file writes and network calls should have some visible disclosure unless already clearly documented elsewhere, which is not evident in this file.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
该技能说明文档完全以中文撰写,没有提供多语言选项、英文摘要,或说明该技能仅面向中文团队/场景。若组织要求避免在未说明情况下强制单一语言,这种默认中文限定可能造成可用性和语言政策问题。

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The raised error messages are hardcoded in Chinese, which imposes a specific language on users without any opt-in or locale selection mechanism visible in this file. The policy allows locale constraints only when users are given a choice or the restriction is clearly documented and justified.

Static analysis

No suspicious patterns detected.