Back to skill

Security audit

wechat-comic-factory

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly a real WeChat comic publishing pipeline, but it has review-worthy risks around live account publishing, token storage, and unsafe handling of files and remote image URLs.

Install only if you are comfortable giving the skill real model-provider and WeChat Official Account credentials. Use a test account first, review generated HTML before publishing, avoid publishing from untrusted task directories, protect or delete logs/wechat_access_token.json, and be aware that generated articles include default publisher branding unless changed in config.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (4)

T01 · Skill Instruction Hijacking

Warning
Location
scripts/format_article.py:123
Finding

Default Third-Party Branding Is Unconditionally Injected into Published Articles

Content
View full analysis
dict[str, str]: defaults = (config or {}).get("defaults", {}) brand_name = normalize_display_text(defaults.get("publisher_name")) or "心枢ai研习社" brand_tagline = ( normalize_display_text(defaults.get("publisher_tagline")) or "AI 内容生产、漫画创作与公众号排版" ) return { "publisher_name": brand_name, "publisher_tagline": brand_tagline, } ``` ```python def build_footer_section(topic: str, publisher_name: str, publisher_tagline: str) -> str: tag = topic or "公众号文章" return f"""
{escape(publisher_name)}
{escape(publisher_tagline)}
· {escape(tag)} ·
建议在浏览器中打开本 HTML 后全选复制,再粘贴到公众号编辑器。
""" ``` ```python article_html = "".join( [ build_banner_section(title, subtitle), build_intro_section(article_payload["intro"]), "".join(part_sections), build_ending_section(article_payload["ending"]), build_footer_section( article_payload["topic"], branding["publisher_name"], branding["publish ...[truncated 1726 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish_draft.py:91
Finding

Crafted Task Metadata Can Cause Arbitrary Local Files to Be Uploaded to WeChat

Content
View full analysis
dict[str, Any]: path = task_result_path(output_dir) if not path.exists(): raise SkillError( "task_state", f"任务状态文件不存在: {path}", hint="请先执行生成流程,确保 task_result.json 已写入。", ) payload = read_json_file(path) payload["output_dir"] = str(Path(payload.get("output_dir") or output_dir).resolve()) return payload ``` ```python def ensure_publishable_task_files(payload: dict[str, Any]) -> dict[str, Any]: output_dir = Path(str(payload.get("output_dir", ""))).resolve() required_fields = { "html_path": payload.get("html_path"), "assets_path": payload.get("assets_path"), } for field_name, field_value in required_fields.items(): if not field_value: raise SkillError( "task_state", f"最近任务缺少 {field_name}", hint="请先执行完整生成流程,确保 HTML 与发布素材已生成。", ) candidate = Path(str(field_value)) if not candidate.is_absolute(): candidate = output_dir / candidate if not candidate.exists(): raise SkillError( "task_state", f"最近任务缺少必要文件: {candidate}", hint="请重新执行生成流程,确保待发布内容已完整生成。", ) return payload ``` ```python def resolve_publish_target( *, publish_latest: bool, publish_output_dir: str | None, ) -> tuple[dict, Path, bool]: if publish_output_dir: payload = load_task_result(publish_output_dir) elif publish_latest: payload = load_latest_task() else: raise SkillError("input", "未提 ...[truncated 3557 chars]
Remediation
View remediation
Path: root = output_dir.resolve(strict=True) candidate_value = Path(supplied_path) if candidate_value.is_absolute(): raise SkillError("task_state", "Absolute task paths are not permitted") candidate = (root / candidate_value).resolve(strict=True) if not candidate.is_relative_to(root): raise SkillError("task_state", "Task path escapes the output directory") if not candidate.is_file(): raise SkillError("task_state", "Task path is not a regular file") return candidate ``` 2. Do not trust the `output_dir` stored inside `task_result.json`. Use the directory explicitly selected by the caller as the trust root. 3. Reject absolute paths and traversal components in `html_path`, `assets_path`, `md_path`, `cover_path`, and all body-image paths. 4. Require regular files and reject symbolic links where task directories may be writable by untrusted users. 5. Decode and validate image files before uploading them, and enforce an allowlist of accepted formats and maximum dimensions. 6. Apply strict file-size limits before reading or uploading content. 7. Where possible, reconstruct expected filenames from the task directory instead of accepting arbitrary path fields from JSON. 8. Add tests for absolute paths, `../` traversal, symlink escapes, and task metadata that references files outside the task root. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/common.py:531
Finding

WeChat Access Token Is Persisted in Plaintext Without Enforced File Permissions

Content
View full analysis
Path: file_path = Path(path) ensure_directory(file_path.parent) file_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") return file_path ``` ```python def read_token_cache() -> dict[str, Any]: if not TOKEN_CACHE_PATH.exists(): return {} try: return read_json_file(TOKEN_CACHE_PATH) except SkillError: return {} def write_token_cache(payload: dict[str, Any]) -> Path: ensure_directory(TOKEN_CACHE_PATH.parent) return write_json_file(TOKEN_CACHE_PATH, payload) ``` ```python cache_payload = { "access_token": access_token, "expires_in": expires_in, "expires_at": now + max(0, expires_in - 300), "updated_at": now, } write_token_cache(cache_payload) logger.info("微信 access_token 获取成功,已写入缓存") return access_token ``` ### Technical Analysis The WeChat bearer token is serialized directly into a JSON file under the project directory. `Path.write_text()` creates or replaces the file using permissions derived from the process umask and existing file metadata. The code does not: - Request owner-only permissions. - Correct insecure permissions on an existing cache. - Verify ownership of the cache directory or file. - Protect against symlink replacement. - Store the token in an operating-system credential facility. On systems with a permissive umask, shared project directory, container volume, backup process, or artifact collection job, the token may become readable by users or processes that should not possess WeChat API authority. ### Attack ...[truncated 1289 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_comic_images.py:226
Finding

Provider-Controlled Image URLs Are Downloaded Without SSRF or Resource Limits

Content
View full analysis
list[str]: candidates: list[str] = [] url_keys = {"url", "image_url", "result_url", "orig", "origin_url"} def walk(node: Any) -> None: if isinstance(node, dict): for key, value in node.items(): lower_key = str(key).lower() if ( lower_key in url_keys or lower_key.endswith("_url") ) and isinstance(value, str) and value.startswith("http"): candidates.append(value) walk(value) return if isinstance(node, list): for item in node: walk(item) walk(payload) unique_urls: list[str] = [] for url in candidates: if url not in unique_urls: unique_urls.append(url) return unique_urls ``` ```python def download_image(url: str, target_path: Path, logger) -> Path: response = request_with_retry( "GET", url, stage="image_download", timeout=120, logger=logger, ) ensure_directory(target_path.parent) target_path.write_bytes(response.content) return target_path ``` ```python image_url = extract_dashscope_image_url(task_result, str(config["image_llm"]["model"])) raw_path = download_image(image_url, raw_dir / f"{index:02d}.png", logger) composed_path, font_used = overlay_caption( raw_path, final_dir / f"{index:02d}.png", caption, config, logger, ) ``` ### Technical Analysis The image-generation service's response is treated as authoritative. Any string beginning with `http` in an expected URL field may become the image d ...[truncated 2443 chars]
Remediation
View remediation
MAX_IMAGE_BYTES: raise SkillError("image_download", "Image exceeds maximum size") output.write(chunk) ``` 7. Validate `Content-Type` against an image MIME allowlist before writing the body. 8. Decode the image in a constrained step and verify its actual format, dimensions, and pixel count. 9. Configure Pillow's decompression-bomb protections and reject images exceeding expected dimensions. 10. Avoid logging internal response bodies or sensitive internal URLs when a download fails. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description presents an end-to-end WeChat comic article generation and draft publication skill. However, this code chunk is a narrow pipeline stage for image processing. It uses local files and configuration, manipulates images, and writes asset metadata, but contains no logic for article generation, no invocation of downstream publishing scripts or APIs, and no WeChat interaction at all. While image compression could be a supporting component of a larger pipeline, this chunk by itself does not accurately match the declared overall behavior, especially the key capabilities of generating articles and publishing drafts.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The declared description presents an end-to-end skill for generating WeChat comic articles and publishing them to the WeChat Official Account draft box. The supplied code chunk is only a formatting stage: it reads markdown and asset JSON, derives title/intro/ending, builds styled HTML, optionally uploads inline article images to WeChat's image-upload API, writes final.html locally, and records upload metadata back into publish_assets.json. There is no code here for generating comic content from comic_type/topic/count, selecting the latest task, or calling any API to create/push a draft-box article. Uploading article images is a related supporting step, but the absence of any draft publication logic means the actual behavior materially underdelivers relative to the declared primary purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

This code chunk implements only the planning stage of a comic pipeline. It accepts comic_type, topic, count, renders a prompt, sends it to a DeepSeek chat completion endpoint, validates returned title/intro/items/ending, and saves the result as plan.json. There is no code for article composition beyond the plan, no image generation, no WeChat Official Account integration, no draft box upload, and no handling of a 'publish latest task' action. Therefore the declared description overstates the actual behavior of this code chunk in a material way.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The README explicitly frames the skill as capable of pushing generated content into a real WeChat Official Account draft box, but it does not clearly warn users that this action triggers external network transmission using live account credentials and can affect a production publishing workflow. In an agent-executed context, ambiguous commands like '发布' may lead users to invoke account-affecting actions without fully understanding that content will be sent to a third-party platform under their authenticated account.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill instructs the agent to execute local Python scripts that can read/write files and interact with WeChat-related network services, but it declares no explicit tool scope or permission boundaries. That creates an authorization gap: a reviewer or runtime may not understand the real capabilities, increasing the risk of unintended file access, credential use, or outbound publishing actions.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The instruction is entirely in Chinese and assigns a role for generating content for a WeChat public account, implicitly requiring Chinese-language output. The file does not offer the user any language or locale choice, nor does it document a justified region-specific constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The prompt is entirely written in Chinese and requires Chinese-specific output such as '适合中文生图模型' and a WeChat public-account style, which effectively constrains the skill to a specific language/locale. The file does not offer opt-in, language selection, or a documented justification that this is a region-specific tool exempt from the policy.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code contains natural-language strings for errors, logs, CLI descriptions, and help text in Chinese only, such as the error at L101, logger messages at L113 and L152, and argparse text at L164-L166. Under the policy rule, forcing a specific language without user opt-in is a natural-language policy violation because users are not given any language or locale choice.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The user-facing/logging strings are written in Chinese, such as the DashScope task submission message. This reflects a fixed language choice in natural-language output without any visible option for the user to select another language or locale.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The argparse description and help messages are natural-language text presented to users, and they are hard-coded in Chinese. There is no indication that users can choose their preferred language or that the tool is explicitly limited to a Chinese-only context.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file contains user-facing natural-language strings exclusively in Chinese, including the model instruction and error/help text, and the system prompt at L60-L61 explicitly constrains the assistant behavior in Chinese. There is no visible opt-in, locale selection, or documentation that this skill is intentionally region-specific, which can violate language/locale policy requiring user choice or justified constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This code presents user-facing CLI descriptions and error messages entirely in Chinese, such as the publish-target error at L061, with no indication that users may choose another language. Under the policy for natural-language violations, forcing a specific language without user opt-in is reportable.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

This manifest file includes fixed publisher name and tagline values in Chinese, which imposes a specific language/locale in the skill configuration. Under the policy rule, locale-specific language should be optional, user-selectable, or clearly justified as region-specific.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: Pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: Markdown has 2 known advisory(ies) (CVE-2025-69534 (Python-Markdown has an Uncaught Exception); CVE-2025-69534 (Python-Markdown version 3.8 contain a vulnerability where malformed HTML-like se)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

This code writes a WeChat access-token cache to disk, which is a safety-relevant file write involving authentication material. Although the function name is descriptive, there is no confirmation prompt, user-facing print/log warning, or inline comment here disclosing that credential-like data will be persisted locally.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
72% confidence
Finding

This function opens a local file and uploads it to the WeChat API, which is a network transmission of user or system data. While the function purpose is inferable from its name, this code path lacks a direct user-facing warning, confirmation, or explanatory comment about sending local file contents to an external service.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.