Back to skill

Security audit

WeChat Article Multi Publisher

Security checks for vulnerabilities and agentic risk

Overview

This WeChat publishing skill is mostly purpose-aligned, but it can publish or delete account content and can upload local or remotely fetched content with insufficient safeguards.

Review this skill before installing. Use it only with trusted article inputs and a dedicated, low-privilege working directory. Avoid processing untrusted Markdown or HTML that can reference local image paths or internal URLs, run --dry-run first, and be careful with --publish and --delete-draft because they mutate a real WeChat account.

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)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish_wechat.py:215
Finding

Arbitrary URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
Article: resp = requests.get(url, timeout=30) resp.raise_for_status() ``` ```python def upload_image_from_url(self, token: str, image_url: str) -> str: img = requests.get(image_url, timeout=self.timeout) img.raise_for_status() content_type = img.headers.get("Content-Type", "image/jpeg") if not content_type.startswith("image/"): return "" ``` ```python def upload_content_image_from_url(self, token: str, image_url: str) -> str: img = requests.get(image_url, timeout=self.timeout) img.raise_for_status() content_type = img.headers.get("Content-Type", "image/jpeg") if not content_type.startswith("image/"): return "" ``` ```python # Upload a network image to the material library if args.upload_material_url: wechat_cfg = get_account_config(cfg, args.account.strip() or None) app_id = (wechat_cfg.get("app_id") or "").strip() app_secret = (wechat_cfg.get("app_secret") or "").strip() if not app_id or not app_secret: raise RuntimeError("Configuration is missing app_id or app_secret") client = WeChatClient(app_id=app_id, app_secret=app_secret, timeout=args.timeout) token = client.get_token() media_id = client.upload_image_from_url(token, args.upload_material_url.strip()) ``` ```python if is_url(input_value): article = extract_from_url(input_value) ``` ### Technical Analysis The command-line input, network image arguments, and image URLs extracted from article content are passed directly to `requests.get`. The implementation does not: - Reject loopback, private, link-local, reserved, multicast, or cloud metadata addresses. - Resolve hostnames and va ...[truncated 1961 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/publish_wechat.py:524
Finding

Markdown Image Paths Can Cause Unauthorized Local File Uploads

Content
View full analysis
str: soup = BeautifulSoup(html_content, "html.parser") img_tags = soup.find_all("img") if not img_tags: return html_content for img in list(img_tags): src = (img.get("src") or "").strip() if not src: img.decompose() continue if src.startswith(("http://", "https://")): try: new_url = client.upload_content_image_from_url(token, src) if new_url: img["src"] = new_url else: img.decompose() except Exception: img.decompose() else: local_path = Path(src) ext = local_path.suffix.lower().lstrip(".") if ext in ("jpg", "jpeg", "png") and local_path.exists(): try: new_url = client.upload_content_image(token, local_path) if new_url: img["src"] = new_url else: img.decompose() except Exception: img.decompose() else: img.decompose() return str(soup) ``` ```python def upload_image_from_path(self, token: str, image_path: Path) -> str: ext = image_path.suffix.lower().lstrip(".") if ext not in ("jpg", "jpeg", "png"): return "" actual_path = compress_image_if_needed(image_path) actual_ext = actual_path.suffix.lower().lstrip(".") mime_map = { "jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png", } content_type = mime_map.get(actual_ext, "i ...[truncated 2212 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/publish_wechat.py:427
Finding

Incomplete HTML Sanitization Allows Active Content in Local Previews

Content
View full analysis
str: template = (template or "standard").strip().lower() soup = BeautifulSoup(content_html, "html.parser") for tag in soup.find_all(["script", "style"]): tag.decompose() ``` ```python body_html = "".join(str(node) for node in soup.contents) wrapper = ( "
" f"{body_html}" "
" ) return wrapper ``` ```python article.content = optimize_for_wechat_html(article.content, template=template) preview_path = Path.cwd() / f"{slugify(article.title)}-wechat-preview.html" preview_path.write_text(article.content, encoding="utf-8") ``` The Markdown conversion path also accepts input that appears to be HTML without sanitizing it first: ```python def markdown_to_html(text: str) -> str: text = decode_escaped_unicode(text or "") if is_probably_html(text): return text ``` ### Technical Analysis The sanitizer removes only `script` and `style` elements. It does not apply a tag-and-attribute allowlist and does not remove or normalize: - Event-handler attributes such as `onerror`, `onload`, or `onclick`. - Unsafe URL schemes such as `javascript:`. - SVG-based active content. - Forms and deceptive user-interface elements. - `iframe`, `object`, or `embed` elements that may originate from Markdown or raw HTML. - Other browser-active attributes and elements. The resulting content is written directly to an HTML file. If an operator opens that ...[truncated 1329 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/requirements.txt:1
Finding

Mutable, Unverified Python Dependencies Create Supply-Chain Exposure

Content
View full analysis
=2.31.0 beautifulsoup4>=4.12.0 markdown>=3.5.0 pyyaml>=6.0.0 Pillow>=10.0.0 ``` ```python def install_dependencies() -> None: req = Path(__file__).resolve().parent / "requirements.txt" if not req.exists(): raise RuntimeError(f"Dependency file not found: {req}") subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", str(req)]) ``` ### Technical Analysis Every dependency uses a lower-bound constraint rather than an exact audited version. Pip may therefore install any current or future release satisfying that constraint. No lock file or cryptographic artifact hash is supplied, so installations are not reproducible and package integrity is not pinned to reviewed distributions. The package names are conventional and the project does not configure a suspicious package index. The issue is therefore an unsafe dependency-management practice rather than evidence that a malicious dependency is presently included. The `--install` option executes pip under the privileges of the current process. Python package installation and import can execute package-controlled code, making dependency integrity security-sensitive. ### Attack Path 1. An operator invokes the documented installation command or runs the script with `--install`. 2. Pip resolves the mutable version ranges against the configured package index. 3. A future compromised, malicious, or incompatible release satisfies the lower-bound constraint. 4. Pip downloads and installs that unreviewed release. 5. Package-controlled code can run during installation or when imported by the publishing script, with the privileges of the invoking user. ### Impact Assessment A compromised dependency co ...[truncated 451 chars]
Remediation
View remediation
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
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (44)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
pip install -r scripts/requirements.txt

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/publish_wechat.py --list-accounts

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/publish_wechat.py --list-accounts

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/publish_wechat.py --list-accounts

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/publish_wechat.py --list-accounts

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/publish_wechat.py --list-accounts

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/publish_wechat.py --list-accounts

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/publish_wechat.py --list-accounts

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/publish_wechat.py --list-accounts

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python scripts/publish_wechat.py --list-accounts

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill advertises capabilities that imply file access, shell execution, and network operations, but it does not declare any explicit tool scope or permission boundaries in the manifest. This increases the chance that an agent or user invokes the skill without understanding its effective authority, especially given that it can publish content, upload images, and manipulate drafts via scripts.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The documented commands include direct publish and draft deletion operations, but the skill description does not prominently warn that these actions can irreversibly change live WeChat account content. In an agent setting, missing change-risk disclosures can lead to accidental destructive actions or unintended publication under the user's account.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill supports uploading local files and remote image URLs to WeChat material storage, but it does not warn users that local content and fetched remote content will be transferred to third-party infrastructure. This creates privacy and data-handling risk, particularly if users upload sensitive local images or cause the tool to fetch untrusted URLs.

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 · scripts/publish_wechat.py (reported line 29)May include surrounding context.

python
yaml = None
    BeautifulSoup = None

TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
DRAFT_ADD_URL = "https://api.weixin.qq.com/cgi-bin/draft/add"
DRAFT_BATCHGET_URL = "https://api.weixin.qq.com/cgi-bin/draft/batchget"
DRAFT_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/draft/delete"

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 · scripts/publish_wechat.py (reported line 30)May include surrounding context.

python
yaml = None
    BeautifulSoup = None

TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
DRAFT_ADD_URL = "https://api.weixin.qq.com/cgi-bin/draft/add"
DRAFT_BATCHGET_URL = "https://api.weixin.qq.com/cgi-bin/draft/batchget"
DRAFT_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/draft/delete"

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 · scripts/publish_wechat.py (reported line 31)May include surrounding context.

python
yaml = None
    BeautifulSoup = None

TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
DRAFT_ADD_URL = "https://api.weixin.qq.com/cgi-bin/draft/add"
DRAFT_BATCHGET_URL = "https://api.weixin.qq.com/cgi-bin/draft/batchget"
DRAFT_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/draft/delete"

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 · scripts/publish_wechat.py (reported line 32)May include surrounding context.

python
yaml = None
    BeautifulSoup = None

TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
DRAFT_ADD_URL = "https://api.weixin.qq.com/cgi-bin/draft/add"
DRAFT_BATCHGET_URL = "https://api.weixin.qq.com/cgi-bin/draft/batchget"
DRAFT_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/draft/delete"

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 · scripts/publish_wechat.py (reported line 33)May include surrounding context.

python
yaml = None
    BeautifulSoup = None

TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
DRAFT_ADD_URL = "https://api.weixin.qq.com/cgi-bin/draft/add"
DRAFT_BATCHGET_URL = "https://api.weixin.qq.com/cgi-bin/draft/batchget"
DRAFT_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/draft/delete"

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 · scripts/publish_wechat.py (reported line 34)May include surrounding context.

python
yaml = None
    BeautifulSoup = None

TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
DRAFT_ADD_URL = "https://api.weixin.qq.com/cgi-bin/draft/add"
DRAFT_BATCHGET_URL = "https://api.weixin.qq.com/cgi-bin/draft/batchget"
DRAFT_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/draft/delete"

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 · scripts/publish_wechat.py (reported line 35)May include surrounding context.

python
yaml = None
    BeautifulSoup = None

TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
DRAFT_ADD_URL = "https://api.weixin.qq.com/cgi-bin/draft/add"
DRAFT_BATCHGET_URL = "https://api.weixin.qq.com/cgi-bin/draft/batchget"
DRAFT_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/draft/delete"

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 · scripts/publish_wechat.py (reported line 36)May include surrounding context.

python
yaml = None
    BeautifulSoup = None

TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"
DRAFT_ADD_URL = "https://api.weixin.qq.com/cgi-bin/draft/add"
DRAFT_BATCHGET_URL = "https://api.weixin.qq.com/cgi-bin/draft/batchget"
DRAFT_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/draft/delete"

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Launching a subprocess to install packages is a system-management capability, not a direct requirement of extracting Markdown/web content and publishing to WeChat. This gives the skill execution behavior outside its stated content-processing and publishing scope.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/publish_wechat.py (reported line 63)May include surrounding context.

python
req = Path(__file__).resolve().parent / "requirements.txt"
    if not req.exists():
        raise RuntimeError(f"未找到依赖文件: {req}")
    subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", str(req)])


def slugify(text: str) -> str:

Tainted flow: 'files' from requests.get (line 755, network input) → requests.post (network output)

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 · scripts/publish_wechat.py (reported line 680)May include surrounding context.

python
content_type = mime_map.get(actual_ext, "image/jpeg")
        with actual_path.open("rb") as fh:
            files = {"media": (actual_path.name, fh, content_type)}
            resp = requests.post(
                MATERIAL_ADD_URL,
                params={"access_token": token, "type": "image"},
                files=files,

Tainted flow: 'image_url' from requests.post (line 766, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
95% confidence
Finding

The function fetches an arbitrary user-supplied image URL with requests.get, which can be abused for server-side request forgery (SSRF) against internal services or cloud metadata endpoints if this script runs in a sensitive network environment. In this skill's context, remote article and image ingestion is expected, but that also makes unvalidated outbound fetching more dangerous because URLs come directly from content, CLI input, or extracted article HTML.

Content

Scanner excerpt · scripts/publish_wechat.py (reported line 696)May include surrounding context.

python
return media_id

    def upload_image_from_url(self, token: str, image_url: str) -> str:
        img = requests.get(image_url, timeout=self.timeout)
        img.raise_for_status()
        content_type = img.headers.get("Content-Type", "image/jpeg")
        if not content_type.startswith("image/"):