Back to skill

Security audit

WeChat MP Plus

Security checks for vulnerabilities and agentic risk

Overview

The skill’s WeChat publishing purpose is coherent, but it has enough high-impact account access and weak safeguards that users should review it carefully before installing.

Install only if you trust the publisher and will run it in a controlled environment. Use a non-production WeChat account first, keep AppSecret values out of repositories and shell history, review Markdown image paths before publishing, avoid processing untrusted Markdown, and be careful with draft/menu delete commands because the skill does not require confirmation.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T08 · Insecure Dependencies

Error
Location
scripts/md2html.py:8
Finding
Unpinned Dependency Is Automatically Installed at Runtime<![CDATA[ ## Vulnerability Details **File Location**: `scripts/md2html.py`, lines 8–14 **Vulnerability Type**: Unsafe runtime dependency installation **Risk Level**: High ### Vulnerable Code ```python # 自动安装 markdown 库 try: import markdown except ImportError: import subprocess subprocess.check_call([sys.executable, "-m", "pip", "install", "markdown", "-q", "--break-system-packages"]) import markdown ``` ### Technical Analysis When the `markdown` module is unavailable, importing `md2html.py` automatically invokes pip and installs the latest package available under the name `markdown`. The dependency is not constrained by a version, cryptographic hash, lockfile, or explicitly trusted package index. Installation occurs during normal program execution rather than through a separate, user-approved setup process. The `--break-system-packages` option additionally permits pip to modify an externally managed Python environment, increasing the potential impact on the host. Python package installation and import can execute package-controlled code. Consequently, compromise of the configured package index, dependency account, package distribution, or local pip configuration could turn Markdown conversion into an arbitrary-code execution channel. ### Attack Path 1. The `markdown` module is absent from the active Python environment. 2. A user or Agent invokes `scripts/md2html.py` or imports it through `scripts/publish.py`. 3. The import handler executes `python -m pip install markdown -q --break-system-packages`. 4. Pip retrieves an unconstrained package version from its configured index. 5. Installation or subsequent import executes package-controlled code with the privileges of the invoking process. ### Impact Assessment Successful exploitation could execute arbitrary code with the Agent or user account's privileges. This may expose files, environment variables, WeChat credentials, cached access tokens, and other resources available to that accou ...[truncated 249 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic dependency installation from module import and normal execution paths. 2. Declare a reviewed and pinned dependency in a dedicated requirements file, for example: ```text Markdown==<reviewed-version> --hash=sha256:<verified-hash> ``` 3. Install dependencies during an explicit setup phase with: ```bash python -m pip install --require-hashes -r requirements.txt ``` 4. Use an isolated virtual environment instead of `--break-system-packages`. 5. Configure an approved package index and disable unexpected fallback indexes. 6. Generate and retain a lockfile or software bill of materials for dependency review. 7. If the dependency is missing at runtime, terminate with a clear installation instruction rather than modifying the environment automatically. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/publish.py:27
Finding
Markdown Image Paths Can Cause Unauthorized Local File Uploads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/md2html.py`, lines 60–68; `scripts/publish.py`, lines 27–38 **Vulnerability Type**: Unrestricted local path resolution and file upload **Risk Level**: High ### Vulnerable Code Image paths are accepted as local whenever they do not begin with an excluded URL scheme: ```python def find_local_images(md_text): """找出markdown中的本地图片路径""" # ![alt](path) 格式 pattern = r'!\[([^\]]*)\]\(([^)]+)\)' images = [] for alt, src in re.findall(pattern, md_text): if not src.startswith(("http://", "https://", "data:")): images.append(src) return images ``` The publisher then accepts absolute paths and unconstrained relative paths before uploading the referenced file: ```python # 2. 扫描文内图片并上传 local_images = find_local_images(md_text) url_map = {} for img_path in local_images: abs_path = img_path if os.path.isabs(img_path) else os.path.join(md_dir, img_path) if not os.path.exists(abs_path): print(f"⚠️ 图片不存在,跳过: {img_path}", file=sys.stderr) continue new_url = upload_article_image(abs_path) if new_url: url_map[img_path] = new_url if url_map: md_text = replace_image_urls(md_text, url_map) ``` ### Technical Analysis The image parser does not restrict references to an approved article asset directory. Absolute paths are explicitly accepted, while relative paths may contain `../` traversal components. No canonical-path containment check is applied before the file is opened and transmitted. The code also does not validate that the selected path is a regular image file based on its content signature. File type handling later relies on a guessed MIME type, and an unrecognized file is sent as `application/octet-stream`. Therefore, any readable file that exists at a referenced path may be submitted to the WeChat article-image endpoint. Uploading user-selected article images is necessar ...[truncated 1563 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject absolute image paths. 2. Define an explicit article asset root and resolve every candidate with `os.path.realpath()`. 3. Verify that the canonical candidate path remains under the canonical asset root: ```python asset_root = os.path.realpath(md_dir) candidate = os.path.realpath(os.path.join(asset_root, img_path)) if os.path.commonpath([asset_root, candidate]) != asset_root: raise ValueError("Image path escapes the article asset directory") ``` 4. Use `os.path.isfile()` and reject symbolic links unless explicitly required and safely resolved. 5. Validate image content using an image decoder or trusted signature inspection rather than file extensions alone. 6. Enforce an allowlist of supported image formats and a maximum upload size. 7. Display the canonical list of files to be uploaded and require confirmation when processing untrusted documents. 8. Document clearly that local image references are read and transmitted to WeChat. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wechat_mp.py:110
Finding
WeChat Access Token Is Stored in a Predictable Plaintext Temporary File<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wechat_mp.py`, lines 14 and 110–128 **Vulnerability Type**: Insecure temporary-file handling and plaintext credential storage **Risk Level**: Medium ### Vulnerable Code ```python # Token缓存文件 TOKEN_CACHE = os.path.join(tempfile.gettempdir(), "wechat_mp_token.json") ``` ```python def get_access_token(force=False): """获取access_token,带缓存(2小时有效期)""" if not force and os.path.exists(TOKEN_CACHE): try: with open(TOKEN_CACHE) as f: cache = json.load(f) if cache.get("expires_at", 0) > time.time() + 60: return cache["access_token"] except Exception: pass app_id, app_secret = _get_config() url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={app_id}&secret={app_secret}" data = _http_get(url) if _api_error(data): sys.exit(1) token = data["access_token"] with open(TOKEN_CACHE, "w") as f: json.dump({"access_token": token, "expires_at": time.time() + data.get("expires_in", 7200)}, f) return token ``` ### Technical Analysis The bearer token is stored as plaintext at a fixed filename in the operating system's shared temporary directory. The code does not explicitly create the file with owner-only permissions, verify file ownership, reject symbolic links, or use exclusive and atomic file creation. Actual readability depends partly on operating-system temporary-directory protections and the process umask. However, relying on these external defaults is unsafe for credential material. The predictable path also creates opportunities for local file replacement, symlink attacks, or cache manipulation where platform permissions permit them. The access token is required for WeChat operations, but a shared predictable temporary path is not required for the declared functionality. ### Attack Path 1. A local attacker predicts the cache path, such as `/tmp ...[truncated 967 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the cache in a per-user application state directory rather than the shared temporary directory. 2. Create the parent directory with mode `0700`. 3. Create the token file atomically with mode `0600`, using exclusive creation where practical. 4. Reject symbolic links and verify that the file is a regular file owned by the current user before reading it. 5. Write updates to a securely created temporary file in the same protected directory and atomically replace the cache. 6. Delete expired tokens and avoid retaining token data longer than necessary. 7. Prefer an operating-system credential store or keyring where available. 8. Validate cache structure and constrain token length and character format before use. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/wechat_mp.py:246
Finding
Bearer Token Material Is Disclosed Through Standard Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wechat_mp.py`, lines 246–248 **Vulnerability Type**: Sensitive credential exposure in logs and terminal output **Risk Level**: Low ### Vulnerable Code ```python if cmd == "token": t = get_access_token() print(f"access_token: {t[:20]}...{t[-10:]}") ``` ### Technical Analysis The documented connectivity-test command prints the first 20 and last 10 characters of the WeChat bearer token. Although the complete token is not displayed, 30 characters of secret token material are intentionally exposed. Standard output may be retained in Agent transcripts, CI logs, terminal recording systems, monitoring platforms, or support artifacts. Bearer-token material should not be emitted merely to confirm connectivity. Partial disclosure reduces the token's effective secrecy and may assist correlation, targeted credential recovery, or exposure analysis. ### Attack Path 1. A user or Agent runs the documented command: ```bash python3 scripts/wechat_mp.py token ``` 2. The Skill obtains or loads the access token. 3. Thirty token characters are printed to standard output. 4. Output is captured by an Agent transcript, CI system, terminal logger, or monitoring service. 5. A party with access to those records obtains substantial secret token material. ### Impact Assessment The disclosed substring is not shown to be independently sufficient to authenticate to WeChat, so direct account compromise is not established from this issue alone. However, it exposes unnecessary credential material and increases risk when combined with another partial disclosure, predictable token formats, compromised logs, or access to the plaintext cache. The command only needs to report whether token acquisition succeeded; revealing any part of the bearer credential exceeds that requirement. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never print bearer-token substrings. 2. Replace the output with a non-sensitive status message: ```python get_access_token() print("WeChat API token acquired successfully") ``` 3. If operational correlation is necessary, generate a one-way fingerprint with a keyed or approved hash and disclose only a very short identifier. 4. Review Agent, CI, and terminal logs for historical token fragments and restrict access to retained records. 5. Ensure error handlers do not print request URLs containing access tokens or AppSecret values. ]]>
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 Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (27)

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The feature list and quick-start commands describe uploading assets, converting content, and creating drafts on the WeChat platform, but do not warn users that local article content and images are transmitted to an external service. For markdown skill descriptions, operations affecting user data or privacy should be clearly disclosed.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The markdown file includes a `menu.py delete` command as part of quick-start usage, but provides no warning that this operation deletes the current custom menu configuration. For markdown files, user-facing documentation should disclose behaviors that may affect system integrity or configuration state.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The skill requires sensitive WeChat credentials via environment variables or a local secrets file but gives no privacy, storage, or handling guidance. This can lead users to place app secrets in insecure locations, expose them in shell history or repositories, or mishandle them in shared environments, enabling unauthorized access to the WeChat MP account.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill documents destructive operations such as draft deletion and menu deletion but provides no warning, confirmation step, or guidance about irreversible effects. In an automation context, this increases the chance of accidental content loss or disruption of a production WeChat public account through normal user use or scripted invocation.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
A markdown-to-HTML converter should not silently acquire and execute external packages at runtime, because that expands its privilege and trust boundary beyond file conversion. In this skill context, the behavior is more dangerous because agent-executed tools may run unattended on user systems or CI hosts, causing unreviewed package installation and environment changes.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
Installing packages without explicit warning or consent is unsafe because users may not expect the tool to alter the environment or contact package infrastructure. In unattended agent/automation contexts this is especially risky, as the installation may occur implicitly with the privileges of the invoking process.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
import markdown
except ImportError:
    import subprocess
    subprocess.check_call([sys.executable, "-m", "pip", "install", "markdown", "-q", "--break-system-packages"])
    import markdown

THEMES_DIR = os.path.join(os.path.dirname(__file__), "..", "themes")
Confidence
97% confidence
Finding
The script executes pip automatically when the markdown package is missing, which introduces an unnecessary code execution and dependency-fetching capability for a simple document conversion tool. This can modify the host environment, pull unpinned code from package indexes, and break system Python installations due to the use of --break-system-packages.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_menu():
    """查询当前菜单"""
    token = get_access_token()
    url = f"https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={token}"
    data = _http_get(url)
    if "errcode" in data and data["errcode"] != 0:
        _api_error(data)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The delete_menu function issues a live API request to remove the current WeChat menu, which is a destructive operation affecting system state. There is no confirmation prompt, cautionary message, or other user-facing disclosure before deletion occurs.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This code file contains natural-language strings and docstrings that force a specific language/locale for users. Under the policy, language restriction is a violation unless the skill offers user opt-in or clearly documents a justified region-specific constraint, which is not present here.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
The docstring for _get_config says it gets AppID and AppSecret 'from environment variables or configuration file', but the implementation specifically probes hard-coded .secrets paths under the user's home/workspace and reads them directly. This is more than an omitted detail because the documentation suggests a generic config source while the code actively reaches into local secret storage locations.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The code caches the WeChat access token in a predictable temp-directory file without setting restrictive permissions or warning the user that credentials will persist on disk. On multi-user systems or environments with weak temp-file isolation, another local user or process could read or replace the token, enabling unauthorized API actions as the公众号.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
The README content is entirely in Chinese and does not indicate that the skill is region- or language-specific, nor does it offer users a language option. This can violate language/locale policy when a specific language is effectively forced without opt-in or justification.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The package description states the skill purpose in Chinese for key functional terms such as '草稿发布、菜单管理、自动回复、Markdown排版' while providing no indication that the skill is region- or language-specific or that users can opt into that locale. Under the policy, forcing a specific language or locale without opt-in can be a natural-language policy violation.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
The module docstring and command-line messages are presented only in Chinese, which imposes a specific language on users without opt-in or alternative locale support. This matches the policy concern for language or locale restrictions expressed in natural-language content.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The comments and docstring around md_to_html describe producing WeChat-compatible HTML with inline styles and say CSS rules are converted to inline style form. However, the code simply appends a style block, which contradicts the documented behavior and could mislead reviewers about compatibility and side effects.

Static analysis

No suspicious patterns detected.