Back to skill

Security audit

Wechat MP Article Stats Comments Suite - 微信公众号管理套件

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly does the advertised WeChat public-account work, but it handles powerful account credentials and can forward a WeChat session cookie to arbitrary URLs, so it needs human review before use.

Install only in an isolated environment with narrowly scoped WeChat credentials. Do not set WECHAT_MP_COOKIE globally unless you trust every URL the agent may fetch, and avoid processing untrusted html_url, cover_url, style_url, or og:image sources. Require human review before publish, delete, blacklist, comment moderation, or multi-account clone actions.

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

T09 · Insecure Skill Coding Practices

Error
Location
fetch_article.py:85
Finding

WeChat Session Cookie Disclosure Through an Unrestricted Article URL

Content
View full analysis
Tuple[Optional[str], Optional[str], Optional[int]]: headers = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" ), "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Referer": "https://mp.weixin.qq.com/", "Upgrade-Insecure-Requests": "1", } if cookie: headers["Cookie"] = cookie try: r = requests.get(url, headers=headers, timeout=timeout) except Exception as e: return None, "request_failed: %s" % e, None ``` ```python cookie = str(req.get("cookie") or os.environ.get("WECHAT_MP_COOKIE", "")).strip() ... else: page_html, err, status = _fetch(url, cookie) ``` ### Technical Analysis The requested URL is taken directly from the request body and passed to `requests.get()` without validating its scheme, hostname, port, resolved address, or redirect destination. Although the script is declared as a WeChat article fetcher, it does not restrict requests to `mp.weixin.qq.com`. If the request does not contain an explicit cookie, the script automatically loads the sensitive `WECHAT_MP_COOKIE` environment variable. `_fetch()` then adds this value to the `Cookie` header for any supplied URL. Consequently, an untrusted URL can receive the victim's complete WeChat session cookie. The lack of URL restrictions also creates a server-side request forgery primitive. The script can request localhost, private network services, link-local addresses, and other destinations reachable from the execution enviro ...[truncated 1324 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
publish.py:154
Finding

Cookie Disclosure and Server-Side Request Forgery Through Publishing URLs

Content
View full analysis
Tuple[Optional[str], Optional[str]]: u = (url or "").strip() if not u: return None, "url is empty" p = urlparse(u) if p.scheme not in ("http", "https"): return None, "unsupported scheme: %s" % (p.scheme or "") headers = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" ), "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Referer": "https://mp.weixin.qq.com/", "Upgrade-Insecure-Requests": "1", } if cookie: headers["Cookie"] = cookie try: r = requests.get(u, headers=headers, timeout=timeout, stream=True) except Exception as e: return None, "request_failed: %s" % e ``` ```python def _fetch_image_url( url: str, *, cookie: str = "", timeout: int = 25, max_bytes: int = 5_000_000, ) -> Tuple[Optional[bytes], Optional[str], Optional[str]]: u = (url or "").strip() if not u: return None, "url is empty", None p = urlparse(u) if p.scheme not in ("http", "https"): return None, "unsupported scheme: %s" % (p.scheme or ""), None headers = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" ), "Accept": "image/*,*/*;q=0.8", "Referer": "https://mp.weixin.qq.com/", } if cookie: headers["Cookie"] = cookie tr ...[truncated 3500 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
drafts.py:121
Finding

Cookie Disclosure and SSRF During Draft Cover Cloning

Content
View full analysis
Tuple[Optional[bytes], Optional[str], Optional[str]]: u = (url or "").strip() if not u: return None, "url is empty", None p = urlparse(u) if p.scheme not in ("http", "https"): return None, "unsupported scheme: %s" % (p.scheme or ""), None headers = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" ), "Accept": "image/*,*/*;q=0.8", "Referer": "https://mp.weixin.qq.com/", } if cookie: headers["Cookie"] = cookie try: r = requests.get(u, headers=headers, timeout=timeout, stream=True) except Exception as e: return None, "request_failed: %s" % e, None ``` ```python cookie = str(req.get("cover_url_cookie") or os.environ.get("WECHAT_MP_COOKIE", "")).strip() try: cover_max_bytes = int(req.get("cover_url_max_bytes", 5_000_000)) except Exception: cover_max_bytes = 5_000_000 results = [] for tgt in target_accs: ... for idx, it in enumerate(src_items): ... thumb_url = str(it.get("thumb_url") or req.get("cover_url") or "").strip() if not thumb_url: clone_err = "article[%d] missing thumb_url and no cover_url fallback provided" % idx break img_bytes, ferr, ctype = _fetch_image_url(thumb_url, cookie=cookie, max_bytes=cover_max_bytes) ``` ### Technical Analysis The clone operation obtains a thumbnail URL from the source draft or from the request-controlled `cover_url` fallback. The fetch helper permits any HTTP or HTTPS host and automaticall ...[truncated 1664 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
drafts.py:408
Finding

Draft Request-File Handling Permits Paths Outside the Workspace

Content
View full analysis
= 3 else "" if isinstance(raw, str): rs = raw.strip() if rs == "-": raw = sys.stdin.read() elif rs.startswith("@") and len(rs) > 1: p = rs[1:] try: with open(p, "r", encoding="utf-8") as f: raw = f.read() except Exception as e: print("Failed to read json file: %s" % e, file=sys.stderr) sys.exit(1) ``` ### Technical Analysis The `@file` request mode opens the supplied path directly. It does not reject absolute paths, `..` traversal, or symbolic links that resolve outside the current workspace. This differs from other modules in the project, which call `_normalize_local_path()` before reading request files. It also contradicts the Skill documentation's stated rule that local file parameters are restricted to relative paths within the current working directory and its descendants. The selected file must contain a JSON object before normal command processing continues. This limits the set of useful targets, but it does not restore the intended access-control boundary. Sensitive JSON configuration files outside the workspace can be opened and their values can influence authenticated draft operations. ### Attack Path 1. An attacker or untrusted Agent input controls the command argument supplied to `drafts.py`. 2. It supplies an absolute or traversing path, for example: ```text drafts.py publish @../../sensitive-request.json ``` 3. The script opens the file outside the workspace without checking its resolved location. 4. If the file contains a JSON object, the object is accepted as the authenticated command request. 5. Values from the external file may select draft identifiers or otherwise drive list, get, delete, p ...[truncated 755 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:83
Finding

Skill Documentation Encourages Unsafe Cross-Origin Cookie Forwarding

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (36)

Tainted flow: 'params' from os.environ.get (line 58, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · comments.py (reported line 60)May include surrounding context.

python
url = WECHAT_API_BASE + "/cgi-bin/token"
    params = {"grant_type": "client_credential", "appid": appid, "secret": secret}
    try:
        resp = requests.get(url, params=params, timeout=20)
    except Exception as e:
        return None, "request_failed: %s" % e
    try:

Tainted flow: 'params' from os.environ.get (line 60, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · drafts.py (reported line 62)May include surrounding context.

python
url = WECHAT_API_BASE + "/cgi-bin/token"
    params = {"grant_type": "client_credential", "appid": appid, "secret": secret}
    try:
        resp = requests.get(url, params=params, timeout=20)
    except Exception as e:
        return None, "request_failed: %s" % e
    try:

Tainted flow: 'params' from os.environ.get (line 60, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · drafts.py (reported line 179)May include surrounding context.

python
params = {"access_token": access_token, "type": "image"}
    try:
        files = {"media": (filename, image_bytes, content_type)}
        resp = requests.post(url, params=params, files=files, timeout=60)
    except Exception as e:
        return None, "request_failed: %s" % e

Tainted flow: 'url' from os.environ.get (line 1058, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · publish.py (reported line 827)May include surrounding context.

python
url = WECHAT_API_BASE + "/cgi-bin/token"
    params = {"grant_type": "client_credential", "appid": appid, "secret": secret}
    try:
        resp = requests.get(url, params=params, timeout=20)
    except Exception as e:
        return None, "request_failed: %s" % e
    try:

Tainted flow: 'url' from os.environ.get (line 1058, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · publish.py (reported line 845)May include surrounding context.

python
def _wechat_post_json(url: str, payload: dict) -> Tuple[Optional[dict], Optional[str]]:
    try:
        body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        resp = requests.post(
            url,
            data=body,
            headers={"Content-Type": "application/json; charset=utf-8"},

Tainted flow: 'url' from os.environ.get (line 1058, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · publish.py (reported line 895)May include surrounding context.

python
try:
        with open(safe["path"], "rb") as f:
            files = {"media": (os.path.basename(safe["path"]), f, cover_mime)}
            resp = requests.post(url, params=params, files=files, timeout=60)
    except Exception as e:
        return None, "request_failed: %s" % e

Tainted flow: 'params' from os.environ.get (line 53, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · stats.py (reported line 55)May include surrounding context.

python
url = WECHAT_API_BASE + "/cgi-bin/token"
    params = {"grant_type": "client_credential", "appid": appid, "secret": secret}
    try:
        resp = requests.get(url, params=params, timeout=20)
    except Exception as e:
        return None, "request_failed: %s" % e
    try:

Tainted flow: 'params' from os.environ.get (line 77, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · users.py (reported line 79)May include surrounding context.

python
url = WECHAT_API_BASE + "/cgi-bin/token"
    params = {"grant_type": "client_credential", "appid": appid, "secret": secret}
    try:
        resp = requests.get(url, params=params, timeout=20)
    except Exception as e:
        return None, "request_failed: %s" % e
    try:

Tainted flow: 'q' from os.environ.get (line 118, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · users.py (reported line 122)May include surrounding context.

python
q["access_token"] = access_token
    url = WECHAT_API_BASE + path
    try:
        resp = requests.get(url, params=q, timeout=30)
    except Exception as e:
        return None, "request_failed: %s" % e
    try:

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding

Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding

The description presents a general-purpose WeChat public account backend skill, explicitly mentioning publishing Markdown to drafts, styling/cover, comment and user management, and data statistics. This code chunk is much narrower and different in behavior: it only manages existing drafts via WeChat APIs, supports deletion/get/list/publish submission, checks the draft-box switch status, and includes an additional cross-account clone feature that copies draft content and cover images into other configured accounts. That cloning/multi-account replication behavior is a substantive undeclared capability. Meanwhile, several declared core functions—Markdown posting/creation, comment management, user management, and analytics—are absent from this code chunk. Therefore the supplied code does not accurately match the declared description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description suggests a公众号后台运营 skill that performs authenticated management actions like sending Markdown to drafts, managing styles/covers, comments, users, and viewing stats. This code does none of those backend management tasks. Instead, it is a content-fetching/extraction utility for existing WeChat article pages, with optional local file input/output. Its primary purpose is materially different from the declared one, and it includes undeclared capabilities around remote retrieval and local file processing.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The description overstates the implemented scope. The code clearly supports '把这篇 Markdown 发到公众号草稿' and related styling/cover workflows, which matches part of the declaration. However, it does not implement '评论与用户管理、数据统计等' as claimed; there are no API calls or logic for fetching yesterday's reading statistics, moderating comments, or managing users. The module docstring even says those are future extensions or separate scripts. Therefore the declared description does not accurately represent the supplied code chunk's actual behavior.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill declares required environment variables and documents file/network operations, but does not declare an explicit tool/permission scope. In an agent setting, missing scope boundaries can cause the skill to be invoked with broader-than-necessary access, increasing the chance of unintended file reads, writes, and outbound requests involving sensitive content or credentials.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The invocation guidance includes a broad catch-all trigger like 'similar WeChat backend operations,' which can cause over-selection of this skill for loosely related prompts. Because the skill handles sensitive credentials, file access, and networked account operations, overly broad activation increases the risk of accidental execution on unintended tasks or adversarial prompt steering.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The natural-language description and inline documentation are presented entirely in Chinese, with no indication that the skill is region-specific or that users may choose another language. Per the policy, forcing a specific language without opt-in or justification is a locale/language policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code performs an irreversible delete operation against the WeChat comments API, but the function only includes an internal docstring and no user-facing confirmation, warning, or log at execution time. In this file, users can invoke delete directly from the CLI usage path without any runtime disclosure that a comment will be permanently removed.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The clone path fetches arbitrary user-influenced URLs for cover images and optionally attaches a cookie from input or environment, creating a server-side request forgery capability. An attacker could induce requests to internal services or trusted third parties, and the optional cookie forwarding makes this more dangerous because sensitive authenticated resources may be fetched and then uploaded into another account.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file is described as draft management, but it also implements cross-account draft cloning that copies article content from one account to others and re-uploads cover media under different credentials. That expands the blast radius from single-account management to multi-account content replication, enabling unintended cross-tenant actions if invoked incorrectly or by an over-privileged agent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script exposes irreversible draft deletion as a direct command with no confirmation, dry-run mode, or additional guardrails. In an agent context, misunderstood prompts, prompt injection, or accidental invocation could destroy content across one or multiple configured accounts.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

Submitting a draft for publication is a consequential external action that can publish content to a live public channel, yet the command executes immediately once called. In an agent-driven workflow, lack of explicit user confirmation and release controls increases the risk of accidental or unauthorized publication, with reputational and compliance consequences.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The code passes a user-controlled url directly to requests.get() with no hostname, scheme, or destination restrictions, so the skill can be used to fetch arbitrary remote resources rather than only mp.weixin.qq.com article pages. In an agent/tooling context, this expands the skill into a generic network fetch primitive that can be abused for unintended outbound access, internal service probing if network reachability exists, or retrieval of attacker-chosen content under the cover of a trusted WeChat-management skill.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill can fetch arbitrary external HTML, CSS, and images from user-supplied URLs, which expands its behavior from local publishing into a generic network-fetching agent. In an agent setting, this creates SSRF-style risk, can touch internal-only services if network egress is broad, and may be abused to relay authenticated requests when paired with supplied cookies.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Remote HTML fetching accepts user-supplied URLs and optionally forwards a cookie string from request data or environment, without a strong trust boundary or visible disclosure. That can leak session cookies to third-party hosts, enable authenticated scraping of unintended targets, and increase SSRF impact in agent deployments.

Content

No source excerpt is available for this finding.

Tainted flow: 'safe' from os.environ.get (line 1277, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · publish.py (reported line 1212)May include surrounding context.

python
sys.exit(1)
        os.makedirs(os.path.dirname(safe["path"]) or ".", exist_ok=True)
        try:
            with open(safe["path"], "w", encoding="utf-8") as f:
                f.write(final_html)
        except Exception as e:
            _json_out({"error": "write_failed", "message": "Failed to write debug_html_out: %s" % e})

Static analysis

No suspicious patterns detected.