Back to skill

Security audit

jike-wechat-writer

Security checks for vulnerabilities and agentic risk

Overview

This WeChat writing skill is mostly purpose-aligned, but it gives broad file, memory, network, and credential-bearing authority with several under-scoped security risks users should review before installing.

Install only if you trust the publisher and are comfortable with the skill making authenticated calls to the 100.city API, fetching external URLs, writing article/theme files, and updating persistent memory. Before use, avoid setting 100CITY_BASE_URL to untrusted hosts, do not pass internal/private URLs to style extraction, review MEMORY.md changes, and treat generated HTML as potentially active content until sanitized.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (5)

T01 · Skill Instruction Hijacking

Error
Location
references/module-c-style.md:83
Finding

Remote Style Reports Can Hijack Agent Instructions and Persist Across Sessions

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/render.py:376
Finding

Unrestricted URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
str: """Fetch HTML from a URL.""" req = urllib.request.Request(url, headers={ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36", }) with urllib.request.urlopen(req, timeout=30) as resp: charset = resp.headers.get_content_charset() or "utf-8" return resp.read().decode(charset) ``` ```python def cmd_extract_style(args): """Extract style fragments from a WeChat article URL.""" url = args.url name = args.name theme_id = args.id description = args.description or "Custom theme extracted from a WeChat article" if not url.startswith("http://") and not url.startswith("https://"): print("Error: provide an article URL beginning with http:// or https://", file=sys.stderr) sys.exit(1) print(f"Fetching article: {url}") try: html = _fetch_html(url) except Exception as e: print(f"Error: unable to fetch article — {e}", file=sys.stderr) sys.exit(1) ``` ### Technical Analysis The `extract-style` command claims to process WeChat article URLs, but the implementation accepts every URL beginning with `http://` or `https://`. It does not validate the hostname, resolved IP address, destination port, redirect target, response content type, or response size. `urllib.request.urlopen` follows HTTP redirects by default. Therefore, validating only the initial scheme would remain insufficient even if a hostname check were added before the request. An allowed public address could redirect to a loopback, private, link-local, or cloud metadata address. The unbounded `resp.read()` operat ...[truncated 1478 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/render.py:52
Finding

Unsanitized Theme Identifiers Permit Path Traversal Reads and Writes

Content
View full analysis
dict: """Load and return a theme JSON.""" fpath = os.path.join(STYLES_DIR, f"{theme_id}.json") if not os.path.isfile(fpath): print(f"Error: theme '{theme_id}' not found at {fpath}", file=sys.stderr) sys.exit(1) with open(fpath, "r", encoding="utf-8") as f: return json.load(f) ``` ```python # Save out_path = os.path.join(STYLES_DIR, f"{theme_id}.json") os.makedirs(STYLES_DIR, exist_ok=True) with open(out_path, "w", encoding="utf-8") as f: json.dump(theme_data, f, ensure_ascii=False, indent=2) ``` ```python p.add_argument("--theme", "-t", default="blue-minimal", help="Theme ID") e.add_argument("--id", required=True, help="Theme directory name used by --theme") ``` ### Technical Analysis Both the reader and writer concatenate an unvalidated `theme_id` with `STYLES_DIR`. `os.path.join` does not prevent traversal: values containing `../` resolve outside the intended styles directory. The `.json` suffix limits the target filename extension but does not keep access within the project. The extraction command opens the resulting path with write mode, which truncates an existing file. The rendering command can load any readable JSON file reachable through traversal and use its values as raw HTML fragments. No canonical-path containment check, identifier allowlist, symbolic-link defense, or safe overwrite policy is present. ### Attack Path **Arbitrary JSON overwrite:** 1. The attacker chooses a relative identifier such as `../../target`. 2. The workflow invokes: ```bash python3 scripts/render.py extract-style \ "https://example.com/article" \ --name "Theme" \ --id "../../targe ...[truncated 1235 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/render.py:69
Finding

Markdown and Extracted Themes Can Inject Active Content into Generated HTML

Content
View full analysis
str: """Escape HTML entities — must run before inline formatting.""" return text.replace("&", "&").replace("<", "<").replace(">", ">") def _apply_inline(text: str) -> str: """Apply inline Markdown formatting to a single text string.""" text = _escape_html(text) # images: ![alt](url) text = re.sub( r"!\[([^\]]*)\]\(([^)]+)\)", r'\1', text ) # links: [text](url) → keep text only text = re.sub(r"\[([^\]]*)\]\([^)]+\)", r"\1", text) text = re.sub(r"`([^`]+)`", r"\1", text) text = re.sub(r"\*\*(.+?)\*\*", r"\1", text) text = re.sub(r"\*(.+?)\*", r"\1", text) return text ``` ```python return ( "\n\n\n" '\n' '\n' f"{theme.get('name', 'Article')}\n" "\n\n" f"{body_html}\n" "\n\n" ) ``` ```python def _templatize(node: _Node, frag_type: str) -> str: """Convert a node's HTML into a fragment template with placeholders.""" html = node.outer_html() text = node.get_text().strip() html = re.sub( r'style="([^"]*)"', lambda m: f'style="{_clean_style(m.group(1))}"', html ) if frag_type == "image": html = re.sub(r'data-src="[^"]*"', 'src="{SRC}"', html) html = re.sub(r'src="[^"]*"', 'src="{SRC}"', html) html = re.sub(r"src='[^']*'", "src='{SRC}'", html) html = re.sub(r'alt="[^"]*"', 'alt="{ALT}"', html) return html ``` ### Technical Analysis `_escape_h ...[truncated 2709 chars]
Remediation
View remediation
`. 11. Consider rendering previews in a sandboxed iframe on an isolated origin with a restrictive Content Security Policy. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/writer.py:56
Finding

Connectivity Check Discloses API Key Material and Allows Credential Redirection

Content
View full analysis
12 else f"Key: {key}" ) try: data = api_request("GET", "/agent_adk/capabilities/trend_news") ``` ### Technical Analysis The mandatory connectivity check prints the first eight and last four characters of longer API keys. If a key is 12 characters or shorter, it prints the entire credential. Command output may be retained in terminal history, agent transcripts, CI logs, monitoring systems, or support records. The API base URL is also configurable through `100CITY_BASE_URL` or `config.json`, but no HTTPS or hostname validation is performed before attaching the bearer token. A modified environment or configuration can redirect authenticated requests to an attacker-controlled HTTP or HTTPS endpoi ...[truncated 1500 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (21)

Tainted flow: 'req' from os.environ.get (line 95, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The script lets the HTTP destination be overridden by the 100CITY_BASE_URL environment variable or config.json, then sends the Bearer API key to that URL in the Authorization header. If an attacker can influence environment variables or the local config, they can redirect requests to an attacker-controlled server and exfiltrate the credential; in this skill context, that is more dangerous because the tool is specifically designed to make authenticated outbound API calls.

Content

Scanner excerpt · scripts/writer.py (reported line 98)May include surrounding context.

python
req = urllib.request.Request(url, data=data, headers=headers, method=method)

    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            result = json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        err_body = e.read().decode("utf-8", errors="replace")

Vague Triggers

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The activation criteria are unusually broad and explicitly include ambiguous requests like '整理一下' when context merely hints at writing intent. This can cause the skill to trigger on unrelated user tasks, leading the agent to invoke external scripts, consume API-backed resources, and steer the interaction into a content-generation workflow the user did not clearly request.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Defaulting to WeChat Official Account output when the platform is unspecified imposes a single publishing context without confirming user intent. In practice this can misroute content into the wrong format, tone, and workflow, and may unnecessarily activate downstream modules tailored to a specific platform.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The file presents the skill entirely in Chinese and uses mandatory language such as '必须' and prescriptive response rules, but it does not state that Chinese is optional or limited to a China-specific use case. Under the policy, forcing a specific language without user opt-in is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs the agent to persist inferred user interest data into dated memory files without requiring explicit user consent, notice, retention limits, or sensitivity checks. This creates a privacy risk because the agent may silently build behavioral profiles from user activity and reuse them later in ways the user did not expect.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
79% confidence
Finding

The entire instruction set is written as a Chinese-only module and gives no indication that language choice is optional or user-selectable. Under the policy, forcing a specific language without opt-in can be a natural-language policy violation unless clearly justified as region-specific.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The '记忆来源' section describes this module in terms of reading MEMORY.md and recent MEMORY/{日期}.md files to build understanding, which presents the module as consuming existing memory. However, L29 explicitly instructs the skill to update MEMORY.md during creation, introducing a write side effect that contradicts the earlier read-only operational description.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs the agent to update persistent memory with newly learned user preferences, but it does not require notifying the user or obtaining consent before modifying stored profile data. This can lead to silent retention of sensitive preferences, inaccurate profile updates, or long-term privacy harms if the memory is later reused in other contexts.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill explicitly instructs reading persistent user preferences from MEMORY.md and using them across sessions, but it provides no requirement to disclose that retention to the user or obtain consent. This creates a privacy and transparency issue because user-linked preference data may be stored and later reused without the user's awareness, which can violate expected session boundaries and data-minimization principles.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This section directs the agent to persist an account name and style report URL into MEMORY.md for future reuse, again without any user-facing disclosure, consent flow, or retention limit. Even though the data is not highly sensitive by itself, it is still cross-session behavioral preference data tied to a user and can accumulate into a profile over time.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The activation conditions include broad natural-language phrases such as asking to "add pictures" or proceeding toward publication, which can cause the skill to trigger during ordinary conversation without a clear, scoped confirmation step. Because this module performs file modification and external image acquisition, unintended invocation could overwrite article content or initiate network-backed actions the user did not explicitly mean to authorize at that moment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The workflow instructs the agent to overwrite the article file after replacing placeholders, but the skill does not prominently warn up front that execution is destructive and in-place. This increases the risk of accidental data loss, especially when combined with the broad activation logic, because users may not realize that invoking image handling will directly modify the source document rather than producing a preview or separate output.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The activation criteria include broad phrases such as '排版一下', which can easily match ordinary editing or formatting requests that do not necessarily imply consent to run the styling workflow. In this skill, misactivation is more dangerous because the workflow may later prompt for external links, invoke local scripts, and persist preferences to MEMORY.md, expanding behavior beyond a simple formatting request.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs the agent to run extract-style against arbitrary article URLs and save derived themes locally, but it does not require notifying the user that this causes network access and local persistence of scraped style data. That creates a consent and data-handling issue: a user asking for styling may unknowingly trigger outbound requests and storage of content-derived assets.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill says the chosen theme ID is recorded to MEMORY.md for future use, but it does not instruct the agent to disclose that persistence to the user. Persistent preference storage is lower impact than network fetching, but still affects user expectations and can create privacy or cross-session behavior concerns if done silently.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The module docstring and CLI help/examples are written in Chinese, and later user-facing status/error messages are also Chinese-only. This imposes a specific language/locale on all users with no opt-in or alternative, which matches the language-policy violation criteria.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The module docstring and usage block describe the script as supporting topic search, trend/news, and image generation, but they do not mention the implemented style-by-name and style-by-url commands that analyze WeChat writing style. This is not just incomplete help text in one place: the top-level documentation presents the intended capabilities of the script while omitting a substantive analysis feature actually present in code.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

This code file contains user-facing natural-language documentation and CLI help oriented entirely in Chinese, establishing a fixed language/locale experience. The policy for natural-language violations applies to all file types, and there is no indication that users can select another language or that the locale restriction is explicitly justified.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The argument parser description says the CLI provides article-creation support with topic search and trend capabilities, but the same parser also exposes style-by-name, style-by-url, and generate-image commands. Because this description summarizes the tool's purpose while excluding major implemented functions, it diverges from the actual behavior users can invoke.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This JSON manifest-style file includes a natural-language description of applicability ('适合科技、商务类公众号文章') but does not define any concrete activation phrase, scope boundary, or exclusion condition. In a manifest/context where descriptions may influence automatic selection, the wording is broad enough to create ambiguous matching for many business or technology-related articles.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

This JSON manifest uses Chinese-only natural-language values for the style name, description, groups, and labels, which can amount to a language/locale constraint embedded in the skill asset. There is no indication that users can choose another language or that the locale restriction is explicitly documented as region-specific.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.