Back to skill

Security audit

wechat-article-reader

Security checks for vulnerabilities and agentic risk

Overview

This WeChat article reader mostly does what it says, but its network fetching is not safely limited to WeChat and could be abused to reach unintended web services.

Review this before installing in any environment with access to private networks or internal services. The skill should strictly validate HTTPS mp.weixin.qq.com hosts, disable or revalidate redirects, bound response sizes and retry settings, and pin dependencies. Avoid using it on sensitive or private article links unless you are comfortable with the URL and extracted content being processed by the skill and possibly by the surrounding OpenClaw summarization flow.

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

Error
Location
main.py:34
Finding

SSRF Through Weak Host Validation and Unrestricted Redirect Following

Content
View full analysis

Vulnerability Details

File Location: main.py:34-37 and main.py:56-61
Vulnerability Type: Server-Side Request Forgery (SSRF)
Risk Level: High

Vulnerable Code

python
def is_wechat_url(self, url: str) -> bool:
    """检查是否为微信公众号链接"""
    parsed = urlparse(url)
    return 'mp.weixin.qq.com' in parsed.netloc
python
response = self.session.get(
    url,
    headers=headers,
    timeout=self.timeout,
    allow_redirects=True
)

Technical Analysis

The hostname check performs a substring search against parsed.netloc rather than an exact comparison against the normalized hostname. Consequently, a URL whose authority contains the trusted string can pass validation even when its actual destination is not WeChat. Examples accepted by is_wechat_url() include authorities such as:

text
mp.weixin.qq.com.attacker.example
mp.weixin.qq.com@127.0.0.1

The higher-level extract_url() function imposes a more restrictive pattern on URLs extracted from messages, but fetch_article() is a public method and can be called directly by other integration code. More importantly, requests to initially valid WeChat URLs are made with allow_redirects=True. The final redirect destination is not revalidated, so a permitted URL that returns or is manipulated into returning a redirect can cause the server to contact an arbitrary destination.

No controls reject loopback, private, link-local, reserved, or cloud metadata addresses. DNS resolution is also not checked for private-address results.

Attack Path

  1. An attacker supplies a URL directly to fetch_article() whose authority contains mp.weixin.qq.com as a substring, or supplies an accepted URL that redirects to an attacker-selected destination.
  2. is_wechat_url() approves the initial URL because the trusted hostname appears somewhere in netloc.
  3. requests.Session.get() sends the request and automatically follows r ...[truncated 1092 chars]
Remediation
View remediation

Remediation Suggestions

  • Permit only HTTPS URLs with an exact normalized hostname comparison:

    python
    parsed = urlparse(url)
    if (
        parsed.scheme != "https"
        or parsed.hostname != "mp.weixin.qq.com"
        or parsed.username is not None
        or parsed.password is not None
        or parsed.port not in (None, 443)
    ):
        reject_url()
    
  • Disable automatic redirects with allow_redirects=False.

  • Follow redirects manually only after applying the same scheme, hostname, credential, port, and IP-address validation to every destination.

  • Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IP addresses for both IPv4 and IPv6.

  • Protect against DNS rebinding by ensuring that the validated address is the address used for the connection.

  • Keep the restrictive checks inside fetch_article() rather than relying only on the message URL extractor.

  • Add tests for malicious subdomains, user-information authorities, encoded hostnames, unusual ports, IPv6 addresses, redirects to private addresses, and redirect chains.

T09 · Insecure Skill Coding Practices

Warning
Location
main.py:56
Finding

Denial of Service Through Unbounded Response Processing and Request-Controlled Retry Settings

Content
View full analysis

Vulnerability Details

File Location: main.py:56-65 and main.py:278-287
Vulnerability Type: Uncontrolled Resource Consumption
Risk Level: Medium

Vulnerable Code

python
response = self.session.get(
    url,
    headers=headers,
    timeout=self.timeout,
    allow_redirects=True
)

if response.status_code == 200:
    return self.parse_article(response.text, url)
python
config = request.get("config", {})
timeout = config.get("timeout", 30)
max_retries = config.get("max_retries", 3)

reader = WeChatArticleReader(timeout=timeout, max_retries=max_retries)

# 获取文章
result = reader.fetch_article(url)

Technical Analysis

The response is fully buffered by requests and converted to text before being passed to BeautifulSoup and the lxml parser. There is no response-size limit, streamed byte counter, or content-type validation. A very large response can therefore consume substantial memory during download, decoding, DOM construction, text extraction, and summary generation.

The timeout and max_retries values are accepted from the request configuration without type checks or upper bounds. An attacker who can control this configuration can request a very long timeout or an excessive retry count. When the destination repeatedly times out, the worker may remain occupied for approximately the configured timeout multiplied by the retry count.

Attack Path

  1. An attacker invokes the Skill with an excessively large timeout or max_retries value, or causes an accepted destination to return an oversized or slowly delivered response.
  2. The configuration is passed directly to requests and the retry loop without validation.
  3. A slow destination holds the worker for repeated timeout intervals, or a large response is fully loaded into memory.
  4. The response is decoded and parsed into an HTML tree, further increasing CPU and memory consumption.
  5. Repeated requests ...[truncated 573 chars]
Remediation
View remediation

Remediation Suggestions

  • Validate configuration types and enforce conservative bounds, such as a timeout of 1–30 seconds and no more than three attempts.
  • Reject booleans, strings, negative numbers, and excessively large numeric values.
  • Use stream=True and read the response in bounded chunks.
  • Abort the request once a fixed compressed and decompressed byte limit is reached.
  • Validate Content-Type before parsing and accept only expected HTML media types.
  • Apply an overall operation deadline in addition to per-request connect and read timeouts.
  • Limit redirect counts and total bytes across the entire redirect chain.
  • Consider execution-level memory, CPU, and concurrency limits for the Skill worker.
  • Add automated tests for oversized responses, slow responses, malformed configuration values, and retry exhaustion.

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Unpinned Third-Party Dependencies Permit Unreviewed Future Releases

Content
View full analysis

Vulnerability Details

File Location: requirements.txt:1-4
Vulnerability Type: Non-Reproducible and Unsafe Dependency Resolution
Risk Level: Medium

Vulnerable Code

text
requests>=2.31.0
beautifulsoup4>=4.12.0
lxml>=4.9.0
fake-useragent>=1.4.0

The documented installation process executes:

bash
pip install -r requirements.txt

Technical Analysis

Every dependency uses an open-ended minimum version constraint. Installation can therefore select any compatible future release rather than a specific version reviewed with the Skill. The project provides no lockfile, package hashes, or integrity-enforced installation process.

This does not establish that any currently named package is malicious. It creates a supply-chain weakness because a compromised maintainer account, malicious future release, or dependency-level compromise could be incorporated automatically during a normal installation or rebuild. It also prevents deterministic reproduction of the audited environment.

The compiled lxml dependency additionally increases the importance of exact version review because native components can contain memory-safety defects and execute native installation or runtime code.

Attack Path

  1. A direct dependency or one of its transitive dependencies publishes a compromised future version that still satisfies the open-ended constraint.
  2. A user follows the documented pip install -r requirements.txt command after that release becomes available.
  3. The package resolver selects the newer, unreviewed version.
  4. Malicious package installation hooks or imported runtime code execute under the account installing or running the Skill.
  5. The compromised dependency can access data and resources available to that account, including the Skill process's network access and readable files.

Impact Assessment

If a resolved dependency is compromised, arbitrary Python or nati ...[truncated 438 chars]

Remediation
View remediation

Remediation Suggestions

  • Pin every direct and transitive dependency to an exact, reviewed version.
  • Generate and commit a lockfile using a dependency-management tool such as pip-tools, Poetry, or an equivalent reproducible resolver.
  • Record cryptographic hashes and install with pip --require-hashes.
  • Update dependencies through a controlled process that includes changelog review, automated tests, vulnerability scanning, and integrity verification.
  • Use a private or explicitly configured trusted package index where appropriate.
  • Run installation and the Skill itself as a dedicated, unprivileged account in an isolated environment.
  • Continuously scan the lockfile with an established dependency vulnerability scanner and promptly review security updates.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (14)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The README instructs users to submit WeChat article URLs and describes remote fetching/summarization, but it does not warn that article links, metadata, and possibly content will be transmitted to the skill runtime and potentially to downstream LLM services for summarization. This creates a real privacy/transparency issue because users may paste sensitive or access-controlled article URLs without understanding where that data is sent or logged.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill describes network-capable behavior ('requests获取网页内容') but does not declare any explicit tool scope, permissions, or allowed-tools boundaries. This creates a mismatch between documented behavior and execution constraints, which can lead to unintended outbound requests, reduced reviewability, and easier abuse if the runtime grants broader network access than users expect.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The keyword trigger list is broad and generic, using common Chinese terms like '读取', '总结', and '摘要' that can appear in many unrelated conversations. This can cause the skill to activate unintentionally, leading to unnecessary processing of user content or URLs and increasing the chance of privacy-impacting or confusing behavior across supported platforms.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The entire skill description and all example invocations are presented only in Chinese, with no indication that users may interact in other languages or that the Chinese-only scope is an intentional, documented locale constraint. Under the policy, forcing a specific language without user opt-in can be a natural-language policy issue unless the restriction is clearly justified.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill advertises activation by directly sending a link or vague natural-language prompts, but it does not define strict trigger boundaries, exclusions, or domain-validation expectations in the skill contract. This can cause over-broad invocation or accidental processing of unintended URLs/content, especially in chat environments where links may appear incidentally.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The HTTP header sets Accept-Language to prefer zh-CN, which imposes a specific language/locale behavior. Under the policy, forcing a locale without user choice is a natural-language policy concern even in code string literals.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
97% confidence
Finding

The dependency is specified with a lower bound only (requests>=2.31.0), which makes builds non-reproducible and allows future installs to resolve to unexpected versions. In a security-sensitive context, this increases supply-chain risk and makes it difficult to verify whether known-vulnerable or newly-broken releases are being pulled in.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
requests>=2.31.0
beautifulsoup4>=4.12.0
lxml>=4.9.0
fake-useragent>=1.4.0

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
94% confidence
Finding

The manifest does not pin requests, and the package has multiple known advisories across its release history. Because the installed version is not fixed, it is impossible to verify from this file alone whether deployments are protected from issues such as credential leakage or other historical flaws.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
97% confidence
Finding

beautifulsoup4>=4.12.0 is unpinned, so the environment may install different versions over time without review. This weakens software supply-chain control and can introduce compatibility or security issues unexpectedly.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
requests>=2.31.0
beautifulsoup4>=4.12.0
lxml>=4.9.0
fake-useragent>=1.4.0

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
98% confidence
Finding

lxml>=4.9.0 is not pinned to a specific release, which is particularly relevant because lxml has had multiple historical security advisories. Without exact pinning, it is harder to ensure deployments consistently use a reviewed, non-vulnerable version.

Content

Scanner excerpt · requirements.txt (reported line 3)May include surrounding context.

text
requests>=2.31.0
beautifulsoup4>=4.12.0
lxml>=4.9.0
fake-useragent>=1.4.0

Unverifiable Dependency: lxml has 14 known advisory(ies) (CVE-2021-43818 (lxml's HTML Cleaner allows crafted and SVG embedded scripts to pass through); CVE-2014-3146 (lxml Cross-site Scripting Via Control Characters); CVE-2021-28957 (lxml vulnerable to Cross-Site Scripting ) +11 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding

lxml has a significant advisory history, and because the requirement is not pinned, the actual installed version cannot be validated for safety. Given this skill parses external article content, a vulnerable parser library could increase risk when processing untrusted HTML/XML.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
96% confidence
Finding

fake-useragent>=1.4.0 is also unpinned, which creates reproducibility and supply-chain integrity issues. Although not inherently malicious, allowing arbitrary newer versions increases exposure to unexpected behavior or compromised upstream releases.

Content

Scanner excerpt · requirements.txt (reported line 4)May include surrounding context.

text
requests>=2.31.0
beautifulsoup4>=4.12.0
lxml>=4.9.0
fake-useragent>=1.4.0

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The natural-language description and trigger behavior are entirely specified in Chinese, implying the skill is intended to activate and operate only for Chinese-language inputs. There is no indication that users can opt into this language constraint or select another locale.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

This Python test file contains multiple natural-language strings exclusively in Chinese, including the module description, test labels, and runtime messages. Under the policy rule for language/locale, forcing a specific language without user opt-in is a natural-language policy concern unless the locale restriction is explicitly justified.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.