Back to skill

Security audit

Liuzln Openclaw Skills Wechat Article Fetcher

Security checks for vulnerabilities and agentic risk

Overview

This WeChat article archiving skill is purpose-aligned, but it needs review because its weak URL handling and broad helper scripts can reach or execute more than users would expect.

Review before installing. Use it only in a contained environment with restricted outbound network access and a quota-limited output directory, avoid running it as root, avoid the arbitrary --script wrapper option, and verify that fetched URLs are genuine HTTPS mp.weixin.qq.com articles before use.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch.py:133
Finding

Server-Side Request Forgery Through Substring-Based URL Validation

Content
View full analysis

Vulnerability Details

File Location:

  • scripts/fetch.py:133-135 and scripts/fetch.py:163-169
  • wechat_article_fetcher.py:132-134 and wechat_article_fetcher.py:170-175
  • examples/fetch_wechat_article.py:134-166

Vulnerability Type: Server-Side Request Forgery caused by improper URL validation
Risk Level: High

Vulnerable Code

python
# Validate URL
if not url or "mp.weixin.qq.com" not in url:
    logger.error("Please provide a valid WeChat Official Account article URL")
    return None

The validated value is subsequently passed directly to Playwright:

python
logger.info(f"Accessing: {url}")

try:
    await page.goto(url, wait_until="domcontentloaded")
except Exception as e:
    logger.warning(f"domcontentloaded timed out, trying load: {e}")
    await page.goto(url, wait_until="load")

The standalone example contains no equivalent hostname validation before calling page.goto:

python
async def fetch_wechat_article(url: str = None):
    """Fetch a WeChat Official Account article."""
    if url is None:
        url = CONFIG["url"]

    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=CONFIG["headless"],
            args=["--no-sandbox", "--disable-dev-shm-usage"]
        )
        page = await browser.new_page(
            user_agent=CONFIG["user_agent"],
            viewport={"width": 1920, "height": 1080}
        )
        page.set_default_timeout(CONFIG["timeout"])

        try:
            logger.info(f"Accessing WeChat article: {url}")

            try:
                await page.goto(url, wait_until="domcontentloaded")
            except Exception as e:
                logger.warning(f"domcontentloaded timed out, trying load: {e}")
                await page.goto(url, wait_until="load")

Technical Analysis

The URL check tests whether the literal string mp.weixin.qq.com occurs anywhere in the supplied value. It does not parse the URL or verify its sche ...[truncated 2280 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse URLs using urllib.parse.urlsplit and reject malformed values.
  2. Require the HTTPS scheme.
  3. Compare the normalized hostname exactly:
python
from urllib.parse import urlsplit

parsed = urlsplit(url)
if parsed.scheme != "https" or parsed.hostname != "mp.weixin.qq.com":
    raise ValueError("Only HTTPS URLs on mp.weixin.qq.com are permitted")
  1. Reject embedded credentials, unexpected ports, fragments, and malformed hostname encodings.
  2. Resolve the hostname and reject every loopback, private, link-local, multicast, reserved, or unspecified IP address using Python's ipaddress module.
  3. Revalidate every redirect destination. Prefer disabling automatic redirects and handling each redirect explicitly.
  4. Apply network-level egress controls that prevent the browser container from reaching internal networks and cloud metadata services.
  5. Use one shared URL-validation function across the module, CLI, batch tool, and example implementation.
  6. Add tests for deceptive hostnames, user-info syntax, alternate IP representations, IPv6, DNS rebinding, and redirects to private destinations.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch.py:43
Finding

Unrestricted Retrieval of Page-Controlled Image URLs

Content
View full analysis

Vulnerability Details

File Location:

  • scripts/fetch.py:43-57 and scripts/fetch.py:68-98
  • wechat_article_fetcher.py:33-47 and wechat_article_fetcher.py:56-86
  • examples/fetch_wechat_article.py:56-70 and examples/fetch_wechat_article.py:78-119

Vulnerability Type: Secondary SSRF and uncontrolled resource consumption
Risk Level: High

Vulnerable Code

The page controls the src and data-src values:

python
for idx, img_element in enumerate(img_elements, 1):
    try:
        src = await img_element.get_attribute("src")
        data_src = await img_element.get_attribute("data-src")
        img_url = data_src or src

        if not img_url:
            continue

        if img_url.startswith("//"):
            img_url = "https:" + img_url
        elif img_url.startswith("/"):
            img_url = "https://mp.weixin.qq.com" + img_url

        alt = await img_element.get_attribute("alt") or f"image_{idx}"
        alt = sanitize_filename(alt)

        parsed = urlparse(img_url)
        path = unquote(parsed.path)
        ext = Path(path).suffix.lower()
        if not ext or ext not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']:
            ext = '.jpg'

        img_filename = f"{idx:03d}_{alt}{ext}"
        img_path = images_dir / img_filename

        success = await download_image(page, img_url, img_path)

The response is fully buffered and written without destination, type, or size checks:

python
async def download_image(page: Page, img_url: str, save_path: Path) -> bool:
    """Download an image."""
    try:
        response = await page.request.get(img_url)
        if response.ok:
            image_data = await response.body()
            with open(save_path, 'wb') as f:
                f.write(image_data)
            logger.info(f"Saved image: {save_path.name}")
            return True
        else:
            logger.warning(f"Image download failed (HTTP {response.status}): {img_url}")
            return False
    exc
...[truncated 2471 chars]
Remediation
View remediation

Remediation Suggestions

  1. Permit only HTTPS image URLs.
  2. Maintain an explicit allowlist of expected image hosts, such as the specific WeChat CDN domains required by the application.
  3. Resolve and validate destination IP addresses before every request.
  4. Reject loopback, private, link-local, multicast, reserved, and unspecified destinations.
  5. Disable automatic redirects or validate every redirect destination.
  6. Verify the response Content-Type against an allowlist such as image/jpeg, image/png, image/gif, and image/webp.
  7. Enforce strict per-image and aggregate download-size limits.
  8. Limit the maximum number of images processed per article.
  9. Stream response data in bounded chunks rather than calling response.body() for an unbounded response.
  10. Apply browser-context timeouts and network-level egress restrictions.
  11. Delete partial files when validation or download operations fail.
  12. Do not rely on the URL extension to determine whether a response is an image.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch.py:145
Finding

Chromium Sandbox Disabled While Rendering Remote Content

Content
View full analysis

Vulnerability Details

File Location:

  • scripts/fetch.py:145-148
  • wechat_article_fetcher.py:158-161
  • examples/fetch_wechat_article.py:143-146

Vulnerability Type: Unsafe browser security configuration
Risk Level: Medium

Vulnerable Code

python
browser = await p.chromium.launch(
    headless=headless,
    args=["--no-sandbox", "--disable-dev-shm-usage"]
)

The example contains the same configuration:

python
browser = await p.chromium.launch(
    headless=CONFIG["headless"],
    args=["--no-sandbox", "--disable-dev-shm-usage"]
)

Technical Analysis

The Skill renders remote, potentially attacker-controlled web content while explicitly passing Chromium's --no-sandbox option. Chromium's sandbox is a defense-in-depth boundary designed to contain compromised renderer processes.

Disabling it does not independently create a browser-engine exploit. However, if a malicious page triggers a Chromium or browser-component vulnerability, the absence of the sandbox substantially reduces the containment protecting the host. This is particularly significant because the URL-validation weakness allows attacker-controlled pages to be loaded.

The risk depends on the installed Chromium version, enabled browser features, containerization, operating-system controls, and privileges of the account running the Skill.

Attack Path

  1. An attacker causes the Skill to navigate to attacker-controlled content.
  2. The page delivers content designed to exploit a vulnerability in the installed Chromium build or one of its browser components.
  3. Chromium processes the page with its sandbox disabled.
  4. A successful renderer compromise is not contained by the normal Chromium sandbox boundary.
  5. The resulting code may execute with the operating-system identity and accessible resources of the Skill process.

This attack requires a separate browser vulnerability or equivalent browser compromise; the --no-sandbox setting increases its resulting imp ...[truncated 667 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the --no-sandbox argument and run Chromium with its normal sandbox enabled:
python
browser = await p.chromium.launch(
    headless=headless,
    args=["--disable-dev-shm-usage"]
)
  1. Run the Skill as a dedicated, unprivileged operating-system user.
  2. Never run the browser process as root.
  3. If the environment cannot support Chromium sandboxing, execute the entire Skill inside a hardened disposable container or virtual machine.
  4. Use a read-only root filesystem and a dedicated, quota-limited output volume.
  5. Drop Linux capabilities, enable no_new_privileges, and apply seccomp and AppArmor or SELinux restrictions.
  6. Restrict outbound network access to the exact WeChat and image hosts required.
  7. Keep Playwright and its managed Chromium build pinned to reviewed, supported security-patched versions.
  8. Destroy the browser context and execution environment after each untrusted fetch.
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (23)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The quickstart explicitly instructs users to scrape articles and save JSON, screenshots, and images to disk, but it does not warn that this may capture sensitive or copyrighted content or create persistent local artifacts. In a data-collection skill, omission of disclosure and safe-handling guidance increases the risk of unintentional over-collection, privacy issues, and unsafe storage of fetched content.

Content

No source excerpt is available for this finding.

Skill Enumeration

Medium
Category
Agent Snooping
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.

Content

Scanner excerpt · README_QUICKSTART.md (reported line 177)May include surrounding context.

md
## 📚 更多文档

详细文档请查看:`skills/wechat-article-fetcher/SKILL.md`

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill documents capabilities that involve network access, shell execution, and reading/writing local files, but it does not declare any explicit tool scope or permission boundaries. This creates an overbroad execution model where an agent may invoke more powerful tools than a user expects, increasing the chance of unintended file modification, data persistence, or network access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill saves article text, images, and screenshots locally by default, but the documentation does not present this as a prominent user warning at the point of use. This can lead users to expose disk contents, retain copyrighted or sensitive material unintentionally, or store artifacts in locations they did not mean to persist.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

解决方案: 安装系统依赖:

bash
sudo apt install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
  libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
  libxfixes3 libxrandr2 libgbm1 libasound2

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file’s natural-language interface and documentation are entirely in Chinese, and user-facing messages throughout the script assume Chinese-language output. This imposes a specific language/locale without offering user opt-in or documenting that the skill is intentionally region-specific, which matches the language-policy violation criteria.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The module description and all user-facing CLI messages are written only in Chinese, which imposes a specific language on users without offering opt-in or alternatives. Under the stated policy, a fixed language choice is a natural-language policy issue unless the locale restriction is explicitly documented and justified.

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 · fetch_direct.py (reported line 62)May include surrounding context.

python
# 运行命令
    try:
        result = subprocess.run(
            cmd,
            cwd=str(skill_dir.parent),
            capture_output=False,

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The --script option allows the wrapper to execute any user-specified Python script inside the chosen virtual environment, which expands the tool from a WeChat article fetcher into a generic code-execution launcher. In an agent or skill context, this increases the chance that untrusted inputs can cause execution of unintended code or bypass the intended scope and safety expectations of the manifest.

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

python
# 运行脚本
    try:
        result = subprocess.run(cmd, cwd=os.getcwd())
        sys.exit(result.returncode)
    except KeyboardInterrupt:
        print("\n⚠️  被用户中断")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill explicitly states it fetches full article content, downloads embedded images, generates screenshots, and exports structured JSON, but it does not clearly warn users that this stores third-party content locally and may create privacy, retention, or copyright/compliance risks. In a scraping/archival skill, omission of this disclosure can lead users to unknowingly collect and persist data in ways that violate policy or expectations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill performs outbound network access to a supplied URL and saves retrieved content, images, screenshots, and metadata to local disk, but the API does not enforce explicit acknowledgement of these side effects. In agent workflows, silent network and filesystem actions can surprise users, leak sensitive browsing targets into logs/storage, or persist untrusted remote content without consent.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This helper allows the caller to choose an arbitrary virtual-environment Python interpreter and then executes a Python script through it, expanding the skill from article fetching into general external code execution under a user-supplied runtime. In an agent setting, that capability is riskier than necessary because a malicious or compromised environment path could run unexpected code or dependencies with the agent's privileges.

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 · wechat_article_fetcher.py (reported line 460)May include surrounding context.

python
try:
        # 运行命令
        result = subprocess.run(
            cmd,
            cwd=str(script_dir.parent.parent),
            capture_output=False,

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The file's headings, instructions, and option descriptions are all presented in Chinese, which effectively forces a specific language for users of this skill documentation. The policy allows locale constraints only when they are optional or clearly justified, and neither is stated here.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The skill description includes Chinese-only instructional content without indicating whether another language is supported or that the locale restriction is intentional. Under the language/locale policy, natural-language instructions should not implicitly force a specific language unless the constraint is clearly justified or the user is given a choice.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The module docstring and CLI descriptions are entirely in Chinese, which imposes a specific language on users without any opt-in or alternative. This can violate language/locale policy when skills are expected to be accessible without forcing one language.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

This code performs a crawl/fetch of a remote WeChat article and writes results to an output directory, but the function and surrounding docstrings only describe it as a simple fetch interface. There is no explicit warning in the code-facing description that using the skill will make network requests and save fetched content locally.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

This code file contains natural-language strings that present the tool description exclusively in Chinese, and the CLI help/usage text continues that same single-language constraint. Under the policy rule, forcing a specific language without offering a user choice or documenting a justified locale restriction is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

This file contains natural-language strings such as the module docstring, CLI description, and examples entirely in Chinese. Under the policy rule, forcing a specific language without user opt-in is a locale/language policy violation unless the constraint is clearly documented and justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The manifest description and the entire skill documentation are presented in Chinese, with no indication that users may choose another language or that the skill is intentionally limited to a Chinese-speaking audience. Under the policy rule for language/locale constraints, forcing a specific language without opt-in can be a natural-language policy concern.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The inline comments at L329-L341 say the function will handle an existing running loop by creating a new task, but the implementation returns asyncio.run(_run()). That behavior contradicts the stated intent in the comments and can mislead maintainers about how asynchronous execution is handled.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The virtual-environment helper launches an external Python process without a strong user-facing warning or consent checkpoint. In agent contexts, spawning another interpreter materially changes execution boundaries and can execute code from a different environment than the main skill, which increases operational and supply-chain risk.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.