Back to skill

Security audit

wechat-article-to-markdown

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real WeChat-to-Markdown converter, but article content can cause it to fetch arbitrary image URLs and save unbounded remote content locally, so it needs review before installation.

Install only if you are comfortable running a headless browser and a network-fetching converter on WeChat article URLs you trust. Avoid using it in environments with access to private internal services or sensitive local networks, and watch disk usage because downloaded image responses are not bounded. A safer release would restrict image hosts to WeChat-controlled CDNs, validate redirects and private IP ranges, enforce download size limits, and pin dependencies.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
wechat_article_to_markdown.py:92
Finding
Unrestricted Article-Controlled Image Fetching Enables SSRF<![CDATA[ ## Vulnerability Details **File Location**: `wechat_article_to_markdown.py:92-107` and `wechat_article_to_markdown.py:178-185` **Vulnerability Type**: Server-Side Request Forgery through unrestricted remote image URLs **Risk Level**: Medium ### Vulnerable Code ```python # wechat_article_to_markdown.py:92-107 async with semaphore: try: url = img_url if not img_url.startswith("//") else f"https:{img_url}" # Infer extension ext_match = re.search(r"wx_fmt=(\w+)", url) or re.search( r"\.(\w{3,4})(?:\?|$)", url ) ext = ext_match.group(1) if ext_match else "png" filename = f"img_{index:03d}.{ext}" filepath = img_dir / filename resp = await client.get( url, headers={"Referer": "https://mp.weixin.qq.com/"}, timeout=15.0, ) resp.raise_for_status() filepath.write_bytes(resp.content) ``` ```python # wechat_article_to_markdown.py:178-185 img_urls = [] seen = set() for img in content_el.find_all("img", src=True): src = img["src"] if src not in seen: seen.add(src) img_urls.append(src) ``` ### Technical Analysis The initial command-line article URL is restricted to a string beginning with `https://mp.weixin.qq.com/`, but image URLs extracted from the article DOM are not subject to equivalent validation. An article-controlled `src` or `data-src` value is passed directly to `httpx.AsyncClient.get()`. The implementation does not validate: - The URL scheme - The destination hostname - The resolved IP address - The destination port - Redirect destinations - Whether the destination is loopback, link-local, private, reserved, or multicast Consequently, content returned by a WeChat page can direct the host running the Skill to issue requests to destinations that the user could not otherwise access directly. Downloading remote article images is necessary for the declared functionality, but unrestricted a ...[truncated 1817 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only `https` image URLs. 2. Maintain an explicit allowlist of WeChat image CDN hostnames required by the application. 3. Reject URLs containing embedded credentials, nonstandard ports, malformed hostnames, or unsupported schemes. 4. Resolve the hostname before connecting and reject all loopback, private, link-local, reserved, multicast, and unspecified IPv4 and IPv6 addresses. 5. Disable automatic redirects or validate the scheme, hostname, port, and resolved address of every redirect destination. 6. Account for DNS rebinding by ensuring that the validated address is the address used for the connection. 7. Consider using the browser’s already-authorized article resources rather than performing unrestricted secondary requests. 8. Add tests covering loopback addresses, private IPv4 and IPv6 ranges, cloud metadata addresses, protocol-relative URLs, redirects, encoded IP addresses, and malicious DNS resolution. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
wechat_article_to_markdown.py:101
Finding
Unbounded Image Downloads Permit Memory, Disk, and Bandwidth Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `wechat_article_to_markdown.py:101-107` and `wechat_article_to_markdown.py:118-131` **Vulnerability Type**: Uncontrolled resource consumption **Risk Level**: Low ### Vulnerable Code ```python # wechat_article_to_markdown.py:101-107 resp = await client.get( url, headers={"Referer": "https://mp.weixin.qq.com/"}, timeout=15.0, ) resp.raise_for_status() filepath.write_bytes(resp.content) return img_url, f"images/{filename}" ``` ```python # wechat_article_to_markdown.py:118-131 async def download_all_images( img_urls: list[str], img_dir: Path ) -> dict[str, str]: """Concurrent download of all images, returning a remote-to-local mapping.""" if not img_urls: return {} print(f"🖼 Downloading {len(img_urls)} images " f"(concurrency {IMAGE_CONCURRENCY})...") semaphore = asyncio.Semaphore(IMAGE_CONCURRENCY) async with httpx.AsyncClient() as client: tasks = [ download_image(client, url, img_dir, i + 1, semaphore) for i, url in enumerate(img_urls) ] results = await asyncio.gather(*tasks) ``` ### Technical Analysis Each response is fully buffered through `resp.content` before it is written to disk. No maximum response size, aggregate download size, image count, or accepted content type is enforced. The semaphore limits simultaneous downloads to five, but it does not constrain: - The total number of scheduled download tasks - The size of any individual response - The total memory consumed by concurrent buffered responses - The total amount of disk space written - The aggregate network bandwidth consumed The inferred filename extension is derived from the URL rather than a verified image MIME type. Therefore, a destination can return arbitrary large content while appearing to be an image URL. ### Attack Path 1. An attacker supplies an article containing many unique image elements or image URLs that return very l ...[truncated 965 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Stream response bodies rather than accessing `resp.content`. 2. Enforce a conservative maximum size for each image and abort the transfer when the limit is exceeded. 3. Track and enforce a maximum aggregate byte count per article. 4. Limit the number of images processed from a single article. 5. Check `Content-Length` before downloading when available, while still enforcing limits during streaming because that header is optional and untrusted. 6. Permit only expected image MIME types and verify file signatures where practical. 7. Delete partial files when downloads fail or exceed limits. 8. Bound both concurrency and the number of pending tasks instead of constructing a task for every URL at once. 9. Add tests for oversized responses, missing or false `Content-Length` headers, excessive image counts, and non-image response bodies. ]]>

T08 · Insecure Dependencies

Note
Location
pyproject.toml:9
Finding
Unpinned Third-Party Dependencies Create Supply-Chain Exposure<![CDATA[ ## Vulnerability Details **File Location**: `pyproject.toml:9-14` and `requirements.txt:1-4` **Vulnerability Type**: Unpinned third-party dependencies **Risk Level**: Low ### Vulnerable Code ```toml # pyproject.toml:9-14 dependencies = [ "camoufox[geoip]", "markdownify", "beautifulsoup4", "httpx", ] ``` ```text # requirements.txt:1-4 camoufox[geoip] markdownify beautifulsoup4 httpx ``` The documented installation path invokes package resolution without a reviewed lock file: ```bash uv tool install wechat-article-to-markdown # Or: pipx install wechat-article-to-markdown ``` ### Technical Analysis All direct dependencies are declared without exact versions, bounded version ranges, or integrity hashes. As a result, separate installations may resolve to different dependency versions over time. This does not prove that any currently listed package is malicious. The security issue is that installation and upgrade behavior is not reproducible and can automatically incorporate future compromised, malicious, or incompatible releases without a code change in this project. The `camoufox[geoip]` dependency is particularly security-sensitive because it provides browser and network-facing functionality and may introduce additional transitive dependencies. No reviewed lock file or hash-pinned installation manifest is present in the audited project. ### Attack Path 1. A direct or transitive dependency publishes a compromised release, or its package repository account is taken over. 2. A user installs or reinstalls the Skill after that release becomes eligible for dependency resolution. 3. The package manager selects the new release because no reviewed version constraints or hashes prevent it. 4. Malicious build, installation, import-time, or runtime behavior executes with the privileges of the user installing or running the Skill. ### Impact Assessment The exact impact depends on the behavior of a compromised dependency. Because dependencies execu ...[truncated 518 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin direct dependencies to reviewed versions or narrow compatible ranges. 2. Generate and commit a lock file that includes resolved transitive dependencies. 3. Use hash verification for deployment or installation manifests where supported. 4. Separate development, testing, and runtime dependencies to reduce the installed attack surface. 5. Use automated vulnerability and dependency monitoring, but require review before accepting updates. 6. Regularly regenerate the lock file in a controlled environment and run unit and live integration tests before release. 7. Document the supported dependency update process so users do not bypass the reviewed lock state. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (19)

Session Persistence

Medium
Category
Rogue Agent
Content
- Anti-detection fetching with Camoufox
- Extract article metadata (title, account name, publish time, source URL)
- Convert WeChat article HTML to Markdown
- Download article images to local `images/` and rewrite links
- Handle WeChat `code-snippet` blocks with language fences

## Installation
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Skill Enumeration

Medium
Category
Agent Snooping
Content
.agents/skills/wechat-article-to-markdown

# Or copy SKILL.md only
curl -o .agents/skills/wechat-article-to-markdown/SKILL.md \
  https://raw.githubusercontent.com/jackwener/wechat-article-to-markdown/main/SKILL.md
```
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Skill Enumeration

Medium
Category
Agent Snooping
Content
.agents/skills/wechat-article-to-markdown

# Or copy SKILL.md only
curl -o .agents/skills/wechat-article-to-markdown/SKILL.md \
  https://raw.githubusercontent.com/jackwener/wechat-article-to-markdown/main/SKILL.md
```
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Skill Enumeration

Medium
Category
Agent Snooping
Content
.agents/skills/wechat-article-to-markdown

# Or copy SKILL.md only
curl -o .agents/skills/wechat-article-to-markdown/SKILL.md \
  https://raw.githubusercontent.com/jackwener/wechat-article-to-markdown/main/SKILL.md
```
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Skill Enumeration

Medium
Category
Agent Snooping
Content
```bash
# Claude Code user-level skills directory (global)
mkdir -p ~/.claude/skills/wechat-article-to-markdown
curl -o ~/.claude/skills/wechat-article-to-markdown/SKILL.md \
  https://raw.githubusercontent.com/jackwener/wechat-article-to-markdown/main/SKILL.md
```
Confidence
85% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Skill Enumeration

Medium
Category
Agent Snooping
Content
```bash
# Claude Code user-level skills directory (global)
mkdir -p ~/.claude/skills/wechat-article-to-markdown
curl -o ~/.claude/skills/wechat-article-to-markdown/SKILL.md \
  https://raw.githubusercontent.com/jackwener/wechat-article-to-markdown/main/SKILL.md
```
Confidence
85% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill explicitly instructs installation and execution of a CLI that fetches remote content from the network and writes Markdown and images to local disk, yet the manifest declares no permissions or allowed-tools scope. That creates a real security transparency and containment issue: operators cannot easily review or restrict the skill's expected network and file-write behavior before use.

Missing User Warnings

Low
Confidence
90% confidence
Finding
The README shows the output directory structure and earlier states that article images are downloaded locally, but it does not explicitly warn users that running the tool will create local directories and save fetched content from a remote URL. For a markdown skill description, file writes affecting user workspace should be clearly disclosed as behavior that may affect local data or filesystem state.

Missing User Warnings

Low
Confidence
90% confidence
Finding
The skill states the output paths, but it does not clearly warn users in the description/usage flow that running it will persist full article content and downloaded images to local storage. This can lead to accidental retention of copyrighted, sensitive, or unexpected content on disk, especially in automated workflows.

Unverifiable Dependency: setuptools has 10 known advisory(ies) (CVE-2013-1633 (Setuptools vulnerable to Man-in-the-middle attacks); CVE-2025-47273 (setuptools has a path traversal vulnerability in PackageIndex.download that lead); CVE-2024-6345 (setuptools vulnerable to Command Injection via package URL) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: wheel has 4 known advisory(ies) (CVE-2026-24049 (Wheel Affected by Arbitrary File Permission Modification via Path Traversal in w); CVE-2022-40898 (pypa/wheel vulnerable to Regular Expression denial of service (ReDoS)); CVE-2022-40898 (An issue discovered in Python Packaging Authority (PyPA) Wheel 0.37.1 and earlie) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: markdownify has 2 known advisory(ies) (CVE-2025-46656 (markdownify allows large headline prefixes such as <h9999999>, which causes memo); CVE-2025-46656 (markdownify allows large headline prefixes such as <h9999999>, which causes memo)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: httpx has 2 known advisory(ies) (CVE-2021-41945 (Improper Input Validation in httpx); CVE-2021-41945 (Encode OSS httpx <=1.0.0.beta0 is affected by improper input validation in `http)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unpinned Dependencies

Low
Category
Supply Chain
Content
camoufox[geoip]
markdownify
beautifulsoup4
httpx
Confidence
95% confidence
Finding
The dependency 'markdownify' is unpinned, so installs may resolve to different versions over time, including newly introduced vulnerable or breaking releases. In a tool that fetches remote WeChat article content and transforms HTML to Markdown, supply-chain unpredictability increases risk because parsing behavior directly affects untrusted input handling.

Unverifiable Dependency: markdownify has 2 known advisory(ies) (CVE-2025-46656 (markdownify allows large headline prefixes such as <h9999999>, which causes memo); CVE-2025-46656 (markdownify allows large headline prefixes such as <h9999999>, which causes memo)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
'markdownify' has known advisories, and because no version is pinned, it is impossible to verify whether the deployed environment avoids the vulnerable releases. This matters more in this skill than in a generic utility because the package processes attacker-controlled HTML content from remote WeChat articles, so parser/transformer denial-of-service bugs are realistically reachable.

Unpinned Dependencies

Low
Category
Supply Chain
Content
camoufox[geoip]
markdownify
beautifulsoup4
httpx
Confidence
93% confidence
Finding
The dependency 'beautifulsoup4' is unpinned, which makes builds non-reproducible and can silently introduce insecure or incompatible versions. Because this skill parses externally sourced HTML from mp.weixin.qq.com, parser dependency drift can materially change security posture and robustness.

Unpinned Dependencies

Low
Category
Supply Chain
Content
camoufox[geoip]
markdownify
beautifulsoup4
httpx
Confidence
95% confidence
Finding
The dependency 'httpx' is unpinned, allowing future installs to pull arbitrary newer or older resolver-selected versions, some of which may contain known flaws. Since this package is responsible for network fetching of remote content, version uncertainty directly affects transport and input-validation security.

Unverifiable Dependency: httpx has 2 known advisory(ies) (CVE-2021-41945 (Improper Input Validation in httpx); CVE-2021-41945 (Encode OSS httpx <=1.0.0.beta0 is affected by improper input validation in `http)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding
'httpx' has known advisories and the manifest does not constrain the installed version, so vulnerable builds may be selected without visibility. Because this skill retrieves remote web content, any input-validation weakness in the HTTP client is more relevant than in an offline-only tool and could expose the agent to malformed response or request-handling issues.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The module description and subsequent user-facing messages indicate the skill is designed to interact in Chinese, and the runtime prints throughout the file are also Chinese-only. This creates a language/locale constraint without any documented user choice or opt-in, which matches the policy-violation category for forced language behavior.