Back to skill

Security audit

公众号作者文章抓取

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real local WeChat article fetcher, but it handles account sessions and includes unsafe cleanup, dependency, and URL-handling paths that need review before installation.

Install only if you are comfortable giving this skill local shell, network, browser-profile, and WeChat session access. Use a dedicated low-privilege WeChat Official Accounts account, avoid shared or synced folders for the skill directory, do not share QR/login artifacts casually, pin and review dependencies before first run, and avoid clear-login when WECHAT_FETCHER_PROFILE_DIR or WECHAT_FETCHER_LOGIN_ARTIFACTS_DIR are set to custom paths.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/main.py:753
Finding
Authentication Cookies May Be Disclosed to Unvalidated Article Destinations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.py:753-758`, `scripts/main.py:1030-1064` **Vulnerability Type**: Authentication cookie disclosure through an unrestricted HTTP client **Risk Level**: High ### Vulnerable Code ```python def build_async_client(cookies: dict[str, str]) -> httpx.AsyncClient: return httpx.AsyncClient( headers=base_headers(), cookies=cookies, follow_redirects=True, timeout=httpx.Timeout(30.0, connect=30.0), ) ``` ```python async def download_articles( *, cookies: dict[str, str], articles: list[dict[str, Any]], output_dir: Path, account: dict[str, Any], concurrency: int, ) -> list[dict[str, Any]]: semaphore = asyncio.Semaphore(max(concurrency, 1)) async with build_async_client(cookies) as client: tasks = [ download_single_article( client=client, semaphore=semaphore, index=index, article=article, output_dir=output_dir, account=account, ) for index, article in enumerate(articles, start=1) ] return await asyncio.gather(*tasks) async def download_single_article( *, client: httpx.AsyncClient, semaphore: asyncio.Semaphore, index: int, article: dict[str, Any], output_dir: Path, account: dict[str, Any], ) -> dict[str, Any]: async with semaphore: try: response = await client.get(article["link"]) response.raise_for_status() parsed = parse_article_content(response.text, article) ``` ### Technical Analysis The asynchronous HTTP client is initialized with cookies extracted from the authenticated WeChat browser session. These cookies are supplied as a plain name-to-value dictionary instead of cookie objects with explicit domain and path restrictions. The same authenticated client is then used to request every URL found in `a ...[truncated 1874 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a separate, cookie-free HTTP client for downloading public article content. 2. Before every request, validate the URL: - Require the `https` scheme. - Require an exact approved hostname such as `mp.weixin.qq.com`. - Reject embedded credentials, nonstandard ports, malformed hosts, and unsupported URL forms. 3. Disable automatic redirects or inspect every redirect target before following it. 4. Reject redirects that cross the approved origin. 5. Preserve domain and path attributes when copying cookies from Playwright instead of reducing them to a plain dictionary. 6. Attach authentication cookies only to requests that explicitly require them. 7. Consider resolving destinations and blocking loopback, link-local, private, and metadata-service addresses to prevent SSRF if broader host support is introduced. 8. Add tests covering external links, cross-origin redirects, HTTP downgrade redirects, and cookie non-disclosure. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/main.py:241
Finding
Environment-Controlled Cache Paths Permit Recursive Deletion of Arbitrary Directories<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.py:30-33`, `scripts/main.py:241-248` **Vulnerability Type**: Unrestricted recursive deletion using environment-controlled paths **Risk Level**: High ### Vulnerable Code ```python PROFILE_DIR = Path(os.environ.get("WECHAT_FETCHER_PROFILE_DIR", APP_ROOT / ".playwright-profile")) LOGIN_ARTIFACTS_DIR = Path( os.environ.get("WECHAT_FETCHER_LOGIN_ARTIFACTS_DIR", APP_ROOT / "login_artifacts") ) ``` ```python def command_clear_login() -> dict[str, Any]: removed_paths: list[str] = [] if PROFILE_DIR.exists(): shutil.rmtree(PROFILE_DIR) removed_paths.append(str(PROFILE_DIR)) if LOGIN_ARTIFACTS_DIR.exists(): shutil.rmtree(LOGIN_ARTIFACTS_DIR) removed_paths.append(str(LOGIN_ARTIFACTS_DIR)) return { "status": "cleared", "removed_paths": removed_paths, "profile_dir": str(PROFILE_DIR), "login_artifacts_dir": str(LOGIN_ARTIFACTS_DIR), } ``` ### Technical Analysis The paths deleted by `command_clear_login()` can be overridden through `WECHAT_FETCHER_PROFILE_DIR` and `WECHAT_FETCHER_LOGIN_ARTIFACTS_DIR`. The values are used without canonicalization or safety validation. The function does not verify that the selected paths: - Are descendants of the Skill directory or a dedicated cache root. - Have the expected cache directory names. - Are different from the project root, user home, or filesystem root. - Do not resolve through unsafe path components. - Actually contain Skill-generated authentication artifacts. The documented workflow encourages users and Agents to invoke `clear-login` when handing the project to another person. A poisoned or accidentally misconfigured execution environment can therefore convert a legitimate cleanup command into destructive deletion of unrelated data. ### Attack Path 1. An attacker-controlled wrapper, automation configuration, shell profile, or poisoned environment sets one of the pat ...[truncated 822 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve both the configured path and approved parent directory with `Path.resolve()` immediately before deletion. 2. Require each deletion target to be a strict descendant of `APP_ROOT` or another explicitly approved cache root. 3. Refuse to delete: - Filesystem roots. - The user's home directory. - The project or Skill root. - Empty paths. - Relative paths that escape the approved root. 4. Require expected terminal directory names such as `.playwright-profile` and `login_artifacts`. 5. Check for a Skill-specific marker file before recursive deletion. 6. Reject symlink-based targets and inspect path components before removal. 7. Ignore environment overrides for destructive operations unless the path was explicitly approved through a safer configuration mechanism. 8. For non-default paths, require clear user confirmation that includes the fully resolved deletion target. 9. Add tests for `/`, the home directory, the project root, `..` traversal, symlink paths, and unrelated absolute directories. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/run_fetcher.sh:1
Finding
Automatic Installation of Mutable, Unpinned Dependencies Creates a Supply-Chain Execution Risk<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run_fetcher.sh:1-10`, `scripts/requirements.txt:1-5` **Vulnerability Type**: Automatic installation and execution of dependencies without exact version or hash verification **Risk Level**: Medium ### Vulnerable Code ```sh #!/bin/zsh set -e cd "$(dirname "$0")" if [ ! -x ".venv/bin/python" ]; then python3 -m venv .venv .venv/bin/pip install -r requirements.txt fi exec ./.venv/bin/python main.py "$@" ``` ```text beautifulsoup4>=4.12.3 httpx>=0.27.0 markdownify>=0.13.1 pillow>=11.1.0 playwright>=1.52.0 ``` ### Technical Analysis The launcher automatically creates a virtual environment and invokes pip whenever `.venv/bin/python` is absent. Every dependency uses only a lower-bound version constraint. As a result, the effective code installed and executed can change after the Skill has been audited. No lock file, exact version pin, package hash, or trusted artifact constraint is used. Pip may execute package build or installation logic, and imported dependencies execute with the same privileges as the Skill. The listed package names are conventional, and the audit found no custom package index, obvious typosquatting, or known malicious package deliberately included in the repository. The vulnerability is the mutable and automatically executed dependency resolution process. ### Attack Path 1. The Skill is run on a system where `scripts/.venv/bin/python` does not exist. 2. `run_fetcher.sh` automatically invokes pip without a separate approval step. 3. Pip resolves the newest available versions satisfying the lower bounds, including transitive dependencies. 4. An upstream package or transitive dependency is compromised, taken over, or replaced with a malicious release. 5. Malicious code executes during installation, import, or runtime with the privileges of the Agent user. ### Impact Assessment A compromised dependency could access all resources available to the Skill process, including: - ...[truncated 347 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace lower-bound constraints with a reviewed lock file containing exact versions for direct and transitive dependencies. 2. Record cryptographic hashes for every permitted distribution. 3. Install with hash enforcement, for example: ```bash pip install --require-hashes -r requirements.lock ``` 4. Separate dependency installation from normal Skill execution. 5. Inform the user and obtain approval before performing network-based package installation. 6. Use a trusted package index explicitly and prevent unexpected index overrides where appropriate. 7. Prefer prebuilt, reviewed wheels and avoid arbitrary source builds in automated environments. 8. Periodically regenerate the lock file in a controlled environment and audit dependency advisories. 9. Verify the integrity and ownership of an existing `.venv` before executing its Python interpreter. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/main.py:382
Finding
Sensitive Browser Profile and Login QR Artifacts Are Created Without Explicit Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.py:382-383`, `scripts/main.py:593-604`, `scripts/main.py:705-716` **Vulnerability Type**: Insecure local storage permissions for authentication material **Risk Level**: Medium ### Vulnerable Code ```python def ensure_login(*, display_mode: str, quiet: bool) -> tuple[str, dict[str, str]]: PROFILE_DIR.mkdir(parents=True, exist_ok=True) LOGIN_ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True) ``` ```python def write_login_qr_artifacts( *, png_bytes: bytes, qr_signature: str, display_mode: str, quiet: bool, ) -> str: png_path = LOGIN_ARTIFACTS_DIR / "login_qr.png" txt_path = LOGIN_ARTIFACTS_DIR / "login_qr.txt" png_path.write_bytes(png_bytes) ascii_qr = render_qr_ascii(png_bytes) txt_path.write_text(ascii_qr + "\n", encoding="utf-8") ``` ```python def write_login_status(status: str, display_mode: str, **payload: Any) -> None: status_path = LOGIN_ARTIFACTS_DIR / "login_status.json" data = { "status": status, "display_mode": display_mode, "updated_at": datetime.now().isoformat(timespec="seconds"), **payload, } status_path.write_text( json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8", ) ``` ### Technical Analysis The persistent Playwright profile may contain authenticated browser state, cookies, local storage, and other session-related information. The login artifact directory contains an active QR image, a text representation of that QR code, and status metadata. The directories and files are created with process-default permissions. The implementation does not explicitly enforce: - Mode `0700` for authentication directories. - Mode `0600` for QR and status files. - Ownership checks. - Permission repair for existing files. - Prompt deletion of expired QR artifacts. On systems with permissive umasks, shared workspaces, or multiple local users, these artifacts may ...[truncated 1011 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create authentication directories with mode `0700`. 2. Create QR and status files with mode `0600` using explicit low-level open flags or a secure helper. 3. Set a restrictive umask while creating authentication artifacts. 4. Verify file ownership and permissions on every startup. 5. Repair or reject existing profile and artifact directories with unsafe permissions. 6. Write files atomically through a securely created temporary file in the same protected directory. 7. Delete QR images and text immediately after authentication, timeout, or cancellation. 8. Avoid returning or logging sensitive artifact paths unless required by the selected workflow. 9. Document that browser profiles and QR artifacts must never be placed in shared or cloud-synchronized directories. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (23)

Vague Triggers

High
Confidence
97% confidence
Finding
The trigger language is intentionally broad and directs the agent to prioritize this skill even for casual, colloquial mentions, which can cause the skill to activate without clear, informed user consent. Because this skill performs high-impact actions—shell execution, dependency installation, config edits, local credential reuse, and bulk network scraping—overbroad triggering materially raises the risk of unintended execution and data handling.

Ae1

High
Category
analysis-evasion
Content
- `./scripts/requirements.txt`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `./scripts/requirements.txt`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README describes output directories and login cache locations elsewhere, but the flagged section does not clearly warn users up front that the tool will persist scraped article content locally and retain authenticated session artifacts. In an agent-driven workflow, that omission can cause operators to run the skill without realizing it creates local copies of third-party content and sensitive login state, increasing the chance of accidental data exposure, repo commits, or unsafe sharing of the project folder.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill instructs the agent to read and modify local files, create virtual environments, install dependencies, invoke shell scripts, and perform network-backed scraping, but it declares no explicit tool scope or permission boundaries. In an agent environment, this creates excessive implicit authority: the skill can trigger sensitive filesystem, shell, and network actions without a machine-readable restriction layer, increasing the chance of overreach or misuse if invoked on ambiguous user requests.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The documentation explicitly suggests sending the generated login QR code image through IM for scanning, but does not warn that possession of that QR can allow another party to authenticate the WeChat account tied to the session. In this skill’s context, the QR is a live authentication artifact, so casual sharing materially increases account takeover and privacy risk, especially when used by agents or automation that may forward artifacts broadly.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
`clear-login` recursively deletes the Playwright profile directory and login artifacts directory, which are persistent local state. In an agent/tooling context, destructive deletion unrelated to content fetching can cause loss of authentication state and potentially remove more data than intended if the environment variables point to unexpected locations.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The code deletes stored login/profile directories immediately with `shutil.rmtree` and no warning or confirmation. Because these paths are influenced by environment variables, misuse or malicious invocation could wipe persistent browser/login data unexpectedly and disrupt other local workflows.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
def default_output_parent() -> Path:
    # In the bundled skill package, keep output under the skill root so agents
    # can use the tool without asking the user for a separate output path.
    skill_root = APP_ROOT.parent
    if (skill_root / "SKILL.md").exists():
        return skill_root
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The manifest describes a local WeChat article fetcher that batches article retrieval and outputs files, but this code also executes local OS commands via osascript to show dialogs and spawns external viewer processes to display QR images. These subprocess/GUI capabilities are not an obvious requirement of article scraping itself and introduce broader host interaction than the skill's stated purpose suggests.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_osascript(script: str) -> str | None:
    try:
        result = subprocess.run(
            ["osascript", "-e", script],
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
close_qr_viewer()
    try:
        if sys.platform == "darwin":
            ACTIVE_QR_VIEWER = subprocess.Popen(
                ["qlmanage", "-p", str(path)],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
)
            return
        if shutil.which("xdg-open"):
            ACTIVE_QR_VIEWER = subprocess.Popen(
                ["xdg-open", str(path)],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The default headers hard-code Accept-Language to zh-CN, and related request parameters elsewhere also fix lang=zh_CN. This imposes a specific language/locale behavior without offering user choice or documenting opt-in, matching the policy category for language or locale constraints.

Tainted flow: 'article_url' from input (line 376, user input) → httpx.get (network output)

Medium
Category
Data Flow
Content
def fetch_seed_article_info(article_url: str) -> dict[str, Any]:
    response = httpx.get(
        article_url,
        headers={
            "User-Agent": USER_AGENT,
Confidence
98% confidence
Finding
The tool accepts a user-supplied `article_url` and fetches it directly with `httpx.get` and redirects enabled, without validating scheme, host, or destination. In an agent setting this creates an SSRF-style capability: an attacker can make the skill send arbitrary outbound requests, potentially to internal services, localhost, or tracking endpoints rather than genuine WeChat article URLs.

Unpinned Dependencies

Low
Category
Supply Chain
Content
beautifulsoup4>=4.12.3
httpx>=0.27.0
markdownify>=0.13.1
pillow>=11.1.0
Confidence
97% confidence
Finding
The dependency is specified with a lower bound only, which allows future installs to resolve to different versions over time. This weakens reproducibility and can silently introduce vulnerable or incompatible releases through normal dependency updates or supply-chain compromise.

Unpinned Dependencies

Low
Category
Supply Chain
Content
beautifulsoup4>=4.12.3
httpx>=0.27.0
markdownify>=0.13.1
pillow>=11.1.0
playwright>=1.52.0
Confidence
98% confidence
Finding
Using httpx>=0.27.0 leaves the actually installed version unconstrained above the minimum, so environments may pull different releases with different security properties. In a scraping skill that performs network access, this increases supply-chain and patch-verification risk because you cannot prove which version is deployed.

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
92% confidence
Finding
The manifest does not pin httpx, so it is impossible to verify from this file whether deployment will use a vulnerable or patched release. In a network-heavy scraping skill, unverifiable dependency state reduces assurance and can expose the runtime to known library flaws if resolution selects an affected version.

Unpinned Dependencies

Low
Category
Supply Chain
Content
beautifulsoup4>=4.12.3
httpx>=0.27.0
markdownify>=0.13.1
pillow>=11.1.0
playwright>=1.52.0
Confidence
97% confidence
Finding
The markdownify dependency is unpinned, so builds are not reproducible and may unexpectedly consume a release with a security regression or breaking behavior. Because this skill converts scraped HTML into Markdown, parser-related bugs could affect availability or content processing safety.

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
Because markdownify is not pinned, the file cannot demonstrate that the installed version avoids the listed advisories. Given this skill ingests untrusted remote HTML and transforms it, parser/resource-exhaustion issues in an affected release could be reachable and harm availability.

Unpinned Dependencies

Low
Category
Supply Chain
Content
beautifulsoup4>=4.12.3
httpx>=0.27.0
markdownify>=0.13.1
pillow>=11.1.0
playwright>=1.52.0
Confidence
99% confidence
Finding
Pillow is unpinned despite being a historically security-sensitive image-processing library. Since the skill may fetch and process remote content, leaving Pillow version selection open increases the chance of pulling a vulnerable build that could enable denial of service or worse when handling crafted images.

Unverifiable Dependency: pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +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
96% confidence
Finding
Pillow has many historical advisories, and this manifest does not pin a version, so you cannot tell whether installs are safe. In the context of scraping and processing remote content, an affected Pillow version could be exposed to crafted images, making denial of service and potentially more severe memory-safety issues more plausible than for a typical library.

Unpinned Dependencies

Low
Category
Supply Chain
Content
httpx>=0.27.0
markdownify>=0.13.1
pillow>=11.1.0
playwright>=1.52.0
Confidence
97% confidence
Finding
The Playwright dependency is specified with only a minimum version, making installations non-deterministic. For a browser automation component that interacts with external web content, unpinned versions increase both supply-chain risk and operational instability across environments.

Static analysis

No suspicious patterns detected.