Back to skill

Security audit

wechat-publish-pro

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its WeChat publishing purpose, but it includes unsafe shell execution paths and overbroad credential/network handling that require review before installation.

Review or patch this skill before installing. At minimum, remove eval from scripts/publish.sh, stop sourcing ~/.openclaw/.env as shell code, read only the required WeChat variables, store AppSecret with restrictive permissions or a secret store, pin the install source, require confirmation before publishing, and restrict remote image/CSS fetching. Avoid using it with sensitive drafts until those issues are addressed.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish.sh:82
Finding

Arbitrary Shell Execution Through Shared Environment File Sourcing

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish.sh:149
Finding

Command Injection Through Dynamically Constructed Eval Command

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
src/wechat_publish_pro/config.py:19
Finding

Excessive Collection of Secrets From the Shared OpenClaw Environment

Content
View full analysis
dict[str, str]: """从 ~/.openclaw/.env 加载环境变量""" env_file = Path.home() / ".openclaw" / ".env" env_vars = {} if env_file.exists(): with open(env_file, encoding="utf-8") as f: for line in f: line = line.strip() if line and not line.startswith("#"): if "=" in line: key, value = line.split("=", 1) env_vars[key.strip()] = value.strip() return env_vars ``` The collected dictionary is subsequently used as follows: ```python wechat_app_id = openclaw_env.get("WECHAT_APP_ID") or os.environ.get("WECHAT_APP_ID") wechat_app_secret = openclaw_env.get("WECHAT_APP_SECRET") or os.environ.get("WECHAT_APP_SECRET") ``` ### Technical Analysis The function reads and retains every key-value pair in `~/.openclaw/.env`, even though only two WeChat-specific values are consumed. A shared OpenClaw environment file may contain credentials for unrelated services. There is no evidence that the unrelated values are deliberately transmitted. Nevertheless, collecting all secrets violates least privilege and unnecessarily expands the consequences of debugging output, memory disclosure, exception instrumentation, or compromise of a dependency running in the same process. ### Attack Path 1. The shared OpenClaw file contains WeChat credentials and unrelated service secrets. 2. Any CLI operation that calls `settings.load()` detects the OpenClaw directory. 3. `load_openclaw_env()` reads every entry into the `openclaw_env` dictionary. 4. Unrelated secrets remain available in process memory. 5. A memory disclosure, debugger, instrumentation component, or compromised dependency could access values that the publisher ...[truncated 322 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
src/wechat_publish_pro/config.py:100
Finding

Plaintext AppSecret Storage Without Enforced Restrictive Permissions

Content
View full analysis
None: """保存配置到文件""" self.config_dir.mkdir(parents=True, exist_ok=True) config_file = self.get_config_file() data = { "accounts": {k: v.model_dump() for k, v in self.wechat.accounts.items()}, "default_account": self.wechat.default_account, } with open(config_file, "w", encoding="utf-8") as f: yaml.dump(data, f, allow_unicode=True, default_flow_style=False) ``` The account model includes the secret: ```python class WeChatAccount(BaseModel): """微信公众号账号配置""" name: str = "默认账号" app_id: str = "" app_secret: str = "" ``` ### Technical Analysis `model_dump()` serializes `app_secret` directly into `~/.wechat-publish-pro/config.yaml`. Neither the directory nor the file is assigned an explicit restrictive permission mode. Access therefore depends on the user's current umask and existing filesystem permissions. The CLI also accepts the secret using `--app-secret`, which can expose it through shell history and, depending on the operating system, process command-line inspection. ### Attack Path 1. A user configures a WeChat account using `config add-account`. 2. `settings.save()` serializes the AppSecret into plaintext YAML. 3. The file is created using ambient filesystem permissions. 4. Another local user, process, backup collector, or compromised application reads the configuration or shell history. 5. The recovered AppID and AppSecret are used to request WeChat API access tokens. ### Impact Assessment Disclosure of the credentials may permit unauthorized access to the associated WeChat account's API capabilities, subject to WeChat-side IP allowlisting and account permissions. Potential consequences include unauthorized material uploads, draft creation, content manipulation, and ...[truncated 43 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/wechat_publish_pro/platforms/wechat.py:74
Finding

Server-Side Request Forgery Through Unrestricted Remote Image Fetching

Content
View full analysis
ImageUploadResult: """上传图片到微信素材库""" token = await self._get_access_token() # 判断是 URL 还是本地文件 if image_path.startswith(("http://", "https://")): # 下载远程图片 resp = await self._client.get(image_path) image_data = resp.content filename = image_path.split("/")[-1].split("?")[0] else: # 读取本地文件 path = Path(image_path) if not path.exists(): return ImageUploadResult( success=False, message=f"图片文件不存在: {image_path}" ) image_data = path.read_bytes() filename = path.name # 上传到微信 url = f"{self.BASE_URL}/material/add_material" params = {"access_token": token, "type": "image"} files = {"media": (filename, image_data, "image/jpeg")} resp = await self._client.post(url, params=params, files=files) ``` ### Technical Analysis The function accepts any HTTP or HTTPS destination and fetches it from the host running the Skill. It does not reject loopback, private, link-local, multicast, or cloud metadata addresses. It also does not validate redirect destinations, response status, content type, filename, or response size. This creates an SSRF primitive from a machine that may have access to internal services unavailable to the attacker. The fetched response is stored fully in memory and then forwarded to the WeChat material API while being labeled as JPEG data. A related unrestricted URL fetch exists in `src/wechat_publish_pro/converter/css_theme.py:145-149`. ### Attack Path 1. The attacker controls or influences the image or cover URL supplied to `upload-image` or `publish --cover`. 2. The URL points to an internal endpoint, loopback serv ...[truncated 742 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
src/wechat_publish_pro/converter/wechat_style.py:559
Finding

Unsanitized Markdown Content and URL Schemes in Generated HTML

Content
View full analysis
str: """转换图片""" return re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', f'\\1', text) def _convert_links(self, text: str) -> str: """转换链接""" return re.sub(r'\[([^\]]+)\]\(([^)]+)\)', f'\\1', text) def _convert_inline_code(self, text: str) -> str: """转换行内代码""" return re.sub(r'`([^`]+)`', f'\\1', text) ``` The title is also interpolated directly at `src/wechat_publish_pro/converter/wechat_style.py:76-79`: ```python # 添加标题 if title: html = f'

{title}

{html}' ``` ### Technical Analysis Markdown labels, image sources, link destinations, titles, table cells, and ordinary text are inserted into generated HTML without context-appropriate escaping. The converter also lacks a final HTML sanitizer and does not restrict dangerous URL schemes such as `javascript:`. As a result, crafted input can break out of attributes, add tags, or preserve active HTML. Whether script execution occurs after publishing depends on sanitization performed by WeChat or another downstream renderer, but locally generated HTML may be opened or embedded before such sanitization occurs. ### Attack Path 1. An attacker supplies or modifies a Markdown file, title, link, or image reference. 2. The crafted value contains HTML markup, attribute delimiters, or a dangerous URL scheme. 3. The regex-based converter inserts the value directly into an HTML element. 4. The generated file is opened in a browser, embedded in another applicat ...[truncated 496 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
SKILL.md:23
Finding

Installation From a Mutable Unpinned Git Repository

Content
View full analysis
=0.25.0", "pydantic>=2.0.0", "pydantic-settings>=2.0.0", "pyyaml>=6.0", "pillow>=10.0.0", "typer>=0.9.0", "rich>=13.0.0", ] ``` ### Technical Analysis The documented installation command installs from the repository's mutable default branch rather than from an immutable reviewed commit or signed release. The effective installed code can therefore change after this Skill has been audited. The broad dependency ranges also permit later package versions to be selected without reproducible review. No malicious dependency was identified in the audited snapshot, but the installation model creates a supply-chain risk. ### Attack Path 1. The upstream repository, maintainer account, default branch, or release process is compromised. 2. Malicious code is committed without changing the installation command documented by the Skill. 3. A user follows the documented `pip install git+https://...` instruction. 4. Pip retrieves and executes build or package code from the changed repository. 5. The altered package runs with the installing user's privileges. ### Impact Assessment A successful upstream compromise can lead to arbitrary code execution during installation or later package invocation. The scope includes the installing user's files, credentials, environment variables, and network access. ]]>
Remediation
View remediation
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
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (37)

YARA rule 'agent_skill_remote_bootstrap_execution': Remote script or code download followed by execution/bootstrap installation [agent_skills]

High
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · README.md (reported line 8)May include surrounding context.

wechat-publish-pro

把 Markdown 文章转换为微信公众号格式并发布到草稿箱。

装上

bash
pip install git+https://github.com/yuesf/wechat-publish-pro.git

配一下

第一步:获取微信公众号凭证

  1. 登录 https://developers.weixin.qq.com/ 公众号平台
  2. 获取 AppID 和 AppSecret

第二步:配置凭证

bash
# 初始化
wechat-publish-pro config init

# 微信公众号的凭证
wechat-publish-pro config set wechat.app_id <你的AppID>
wechat-publish-pro config set wechat.app_secret <你的AppSecret>

第三步:设置 IP 白名单

把运行机器的 IP 添加到微信公众号后台白名单

YARA rule 'agent_skill_remote_bootstrap_execution': Remote script or code download followed by execution/bootstrap installation [agent_skills]

High
Category
YARA Match
Confidence
85% confidence
Finding

The installation instruction pulls code directly from a GitHub repository via 'pip install git+https://...', which bypasses the safety and reproducibility benefits of pinned package releases. In an agent context, remote bootstrap from a mutable repository can lead to execution of unreviewed or changed code, creating a software supply-chain risk.

Content

Scanner excerpt · SKILL.md (reported line 26)May include surrounding context.

封面生成*: 自动处理封面图,支持本地/网络图片

  • 直接发布: 一键发布到微信公众号草稿箱

触发条件

当用户需要以下操作时自动触发:

  • 将 Markdown 或 HTML 文章发布到微信公众号草稿箱
  • 转换 Markdown 为公众号格式 HTML
  • 使用特定主题发布公众号文章
  • 测试微信连接

安装

bash
# 从 GitHub 安装
pip install git+https://github.com/yuesf/wechat-publish-pro.git

配置

第一步:获取微信公众号凭证

  1. 登录 https://developers.weixin.qq.com/ 公众号平台
  2. 获取 AppID 和 AppSecret

第二步:配置凭证

环境变量

bash
export WECHAT_APP_ID=your_wechat_app_id
export WECHAT_APP_SECRET=your_wechat_app_secret

配置文件

bash
# 初始化配置
wechat-publish-pro config init

# 设置配置
wechat-publish-pro config set wechat.app_id <AppID>
wechat-publish-pro config set wechat.app_secr

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 · src/wechat_publish_pro/config.py (reported line 21)May include surrounding context.

python
def load_openclaw_env() -> dict[str, str]:
    """从 ~/.openclaw/.env 加载环境变量"""
    env_file = Path.home() / ".openclaw" / ".env"
    env_vars = {}
    if env_file.exists():

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 · src/wechat_publish_pro/config.py (reported line 67)May include surrounding context.

python
def load_openclaw_env() -> dict[str, str]:
    """从 ~/.openclaw/.env 加载环境变量"""
    env_file = Path.home() / ".openclaw" / ".env"
    env_vars = {}
    if env_file.exists():

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/publish.sh (reported line 84)May include surrounding context.

sh
def load_openclaw_env() -> dict[str, str]:
    """从 ~/.openclaw/.env 加载环境变量"""
    env_file = Path.home() / ".openclaw" / ".env"
    env_vars = {}
    if env_file.exists():
        with open(env_file, encoding="utf-8") as f:

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 · src/wechat_publish_pro/config.py (reported line 22)May include surrounding context.

python
def load_openclaw_env() -> dict[str, str]:
    """从 ~/.openclaw/.env 加载环境变量"""
    env_file = Path.home() / ".openclaw" / ".env"
    env_vars = {}
    if env_file.exists():
        with open(env_file, encoding="utf-8") as f:

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · src/wechat_publish_pro/converter/css_theme.py (reported line 47)May include surrounding context.

python
if properties:
            rules.append(CSSRule(selector=selector, properties=properties))

    return rules


def css_to_inline_style(rules: list[CSSRule]) -> Dict[str, str]:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README states that the tool converts Markdown and publishes to the WeChat public account draft box, but it does not clearly warn users that article content and associated metadata will be transmitted to WeChat. This can mislead users about data handling and create privacy or confidentiality exposure when sensitive drafts are published through the skill.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The invocation examples are generic natural-language phrases like '把这篇文章发到公众号' and '帮我发布到微信,测试一下', which can overlap with ordinary conversation and cause accidental skill activation. In an agent setting, that raises the chance of unintended publication actions or content transfer to an external platform without sufficiently explicit user confirmation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill advertises 'one-click publish' to the WeChat draft box but does not prominently warn that article content, cover images, and possibly remotely sourced media will be uploaded to WeChat. This omission can mislead users about the data-transfer consequences of using the skill, especially in an agent environment where actions may be automated.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases are broad enough to match ordinary user requests such as 'publish this article to WeChat' or 'use a blue theme,' which can cause the skill to activate automatically without a distinct confirmation boundary. Because this skill performs an external publishing action, overbroad activation increases the risk of unintended content upload to a third-party platform using stored credentials.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The comment states '完全独立实现,不依赖任何外部 CLI 工具' ('fully independently implemented, does not rely on any external CLI tools'). However, the script explicitly checks for and uses external tooling such as python3 and invokes the wechat-publish-pro publisher command, so the documentation contradicts the actual behavior.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The script sources $HOME/.openclaw/.env and exports its contents, which is a sensitive credential access path because the help text identifies WECHAT_APP_ID and WECHAT_APP_SECRET as required environment variables. Although there is a log indicating the file is loaded, there is no explicit warning or disclosure that secrets will be read from a local credential file.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The save() method serializes app_id and app_secret directly into ~/.wechat-publish-pro/config.yaml in plaintext. Storing long-lived API credentials unencrypted on disk increases the risk of credential disclosure through local compromise, backups, sync tools, or overly permissive file permissions.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

The hardcoded external API endpoint indicates that document content may be sent off-host to a third-party service during conversion. In this skill context, Markdown may contain proprietary or sensitive text, so external transmission creates confidentiality and compliance risk even if the code is not overtly malicious.

Content

Scanner excerpt · src/wechat_publish_pro/converter/__init__.py (reported line 41)May include surrounding context.

python
"""Markdown 转换器"""

    # mdnice API 端点
    MDNICE_API = "https://api.mdnice.com/api/v1/markdown"

    def __init__(self, options: Optional[ConvertOptions] = None) -> None:
        self.options = options or ConvertOptions()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

When API mode is enabled, the full Markdown content is transmitted to a third-party or user-specified endpoint without any visible disclosure or consent mechanism at the call site. This can expose sensitive document contents, embedded secrets, private links, or unpublished material to external services, and the risk is higher because the endpoint can be overridden dynamically.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The function accepts attacker-controlled URLs and fetches remote CSS without restriction, allowing untrusted network access from the host running the skill. This can enable SSRF-style behavior against internal services, unexpected data exfiltration via request metadata, or privacy issues from contacting external servers during theme loading.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The docstring states that the code has already been HTML-escaped and is therefore safe to wrap with span tags, but _render_code_block only escapes &, <, and > before calling _highlight_code_escaped. Because quotes are not escaped, the regex substitutions in L274-L275 that assume &quot; and &#39; never match, and more importantly the safety claim in the docstring is inaccurate relative to the actual escaping performed.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · src/wechat_publish_pro/platforms/wechat.py (reported line 19)May include surrounding context.

python
class WeChatPlatform(Platform):
    """微信公众号平台"""

    BASE_URL = "https://api.weixin.qq.com/cgi-bin"

    def __init__(self, app_id: str, app_secret: str) -> None:
        self.app_id = app_id

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The code sends app_id and app_secret to the WeChat API to obtain an access token, but there is no user-facing notice, logging, or warning comment indicating that configured credentials are transmitted over the network. This is a safety-relevant external operation involving sensitive authentication material.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The adapter accepts arbitrary http/https URLs for image uploads and fetches them server-side without any allowlist, size limits, or destination validation. If an attacker can control image_path, this creates an SSRF primitive that can be used to probe internal services, access cloud metadata endpoints, or force the server to make unexpected outbound requests.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The function accepts either a remote URL or a local path, then silently downloads remote content or reads a local file for upload to WeChat. There is no confirmation prompt, print/log message, or warning comment/docstring disclosing that the skill may fetch external data and transmit file contents to a third-party service.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

Natural-language policy checks apply to all file types. The file presents the skill content entirely in Chinese and does not offer an explicit language choice or explain that the skill is intended only for Chinese-speaking users, which may amount to forcing a specific language without user opt-in.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

This is a markdown file, so missing-warning checks apply to the natural-language description. The feature list advertises uploading images and publishing to a WeChat draft box, which can affect user data or external accounts, but the document provides no warning or disclosure about these actions.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The package description is written entirely in Chinese and provides no indication that other languages are supported or that the locale restriction is intentional. Under the stated policy, language constraints should either be user-selectable or clearly documented as justified.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.