Back to skill

Security audit

Welight 公众号排版发布

Security checks for vulnerabilities and agentic risk

Overview

This skill matches its WeChat publishing purpose, but its publishing script can upload local files and route credentials or article content through an arbitrary proxy without strong safeguards.

Install only if you trust the operator, article sources, WeChat config, and any proxy configuration. Before real publishing, use dry-run, inspect every image source and cover path, avoid untrusted HTML, keep WECHAT_APP_SECRET out of shared files, and do not use a custom proxy unless it is administratively controlled and HTTPS-only.

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/publish_wechat.py:107
Finding

Article Image Sources Can Exfiltrate Arbitrary Local Files

Content
View full analysis
Tuple[bytes, str, str]: if source.startswith("data:"): header, encoded = source.split(",", 1) mime_match = re.match(r"data:([^;]+);base64", header, re.I) mime_type = mime_match.group(1) if mime_match else "application/octet-stream" suffix = mimetypes.guess_extension(mime_type) or ".bin" return base64.b64decode(encoded), f"inline{suffix}", mime_type if re.match(r"^https?://", source, re.I): req = urllib.request.Request(source, headers={"User-Agent": USER_AGENT}) with urllib.request.urlopen(req, timeout=30) as resp: payload = resp.read() content_type = resp.headers.get_content_type() parsed = urllib.parse.urlparse(source) filename = Path(parsed.path).name or f"remote{mimetypes.guess_extension(content_type) or '.bin'}" return payload, filename, content_type or detect_content_type(filename) path = Path(source) payload = path.read_bytes() return payload, path.name, detect_content_type(path.name) ``` ```python def replace_body_images(access_token: str, html_text: str, proxy_origin: Optional[str], dry_run: bool) -> str: cache: Dict[str, str] = {} def repl(match: re.Match) -> str: original = match.group(1).strip() if original in cache: replacement = cache[original] elif dry_run: replacement = original cache[original] = replacement else: replacement = upload_inline_image(access_token, original, proxy_origin) cache[original] = replacement return match.group(0).replace(original, replacement, 1) return re.sub ...[truncated 2623 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/publish_wechat.py:60
Finding

Unrestricted Proxy Origin Exposes WeChat Credentials, Tokens, and Publication Content

Content
View full analysis
str: base = proxy_origin.rstrip("/") if proxy_origin else "https://api.weixin.qq.com" url = f"{base}{path}" if access_token: joiner = "&" if "?" in url else "?" url = f"{url}{joiner}access_token={urllib.parse.quote(access_token)}" return url ``` ```python def get_access_token(app_id: str, app_secret: str, proxy_origin: Optional[str]) -> str: url = build_api_url(proxy_origin, "/cgi-bin/stable_token") payload = { "grant_type": "client_credential", "appid": app_id, "secret": app_secret, } data = post_json(url, payload) token = data.get("access_token") if not token: raise RuntimeError(f"Failed to obtain access token: {json.dumps(data, ensure_ascii=False)}") return token ``` ```python def load_config(args: argparse.Namespace) -> Dict[str, str]: config: Dict[str, str] = {} if args.config: config = json.loads(read_text(args.config)) merged = { "appId": args.app_id or config.get("appId") or os.getenv("WECHAT_APP_ID", ""), "appSecret": args.app_secret or config.get("appSecret") or os.getenv("WECHAT_APP_SECRET", ""), "proxyOrigin": args.proxy_origin or config.get("proxyOrigin") or os.getenv("WECHAT_PROXY_ORIGIN", ""), "author": args.author or config.get("author") or os.getenv("WECHAT_AUTHOR", ""), } return merged ``` ### Technical Analysis The optional proxy origin completely replaces the official `https://api.weixin.qq.com` origin for all API operations. The value can come from a command-line argument, JSON configuration, or en ...[truncated 2372 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/normalize_to_markdown.py:19
Finding

Unrestricted URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
Tuple[str, str]: req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) with urllib.request.urlopen(req, timeout=20) as resp: body = resp.read() charset = resp.headers.get_content_charset() or "utf-8" content_type = resp.headers.get("Content-Type", "") return body.decode(charset, errors="replace"), content_type ``` ```python def read_source(args: argparse.Namespace) -> Tuple[str, str, Optional[str]]: if args.url: text, content_type = fetch_url(args.url) source_type = "html" if "html" in content_type.lower() else "text" return text, source_type, args.url ``` ```python if re.match(r"^https?://", source, re.I): req = urllib.request.Request(source, headers={"User-Agent": USER_AGENT}) with urllib.request.urlopen(req, timeout=30) as resp: payload = resp.read() content_type = resp.headers.get_content_type() parsed = urllib.parse.urlparse(source) filename = Path(parsed.path).name or f"remote{mimetypes.guess_extension(content_type) or '.bin'}" return payload, filename, content_type or detect_content_type(filename) ``` ### Technical Analysis Both article normalization and image publishing fetch attacker-influenced URLs through `urllib.request.urlopen()` without destination security controls. The implementation does not: - Restrict resolved addresses to public networks - Block loopback, private, link-local, multicast, or reserved address ranges - Block cloud metadata endpoints - Revalidate destinations after redirects - Protect against DNS rebinding - Restrict destination ports - Limit the number of redire ...[truncated 2300 chars]
Remediation
View remediation
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (13)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/publish_wechat.py (reported line 137)May include surrounding context.

python
data = post_json(url, payload)
    token = data.get("access_token")
    if not token:
        raise RuntimeError(f"Failed to obtain access token: {json.dumps(data, ensure_ascii=False)}")
    return token

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill explicitly supports publishing rendered content to WeChat drafts or formal posts, but it does not require a clear user confirmation or warning that this action changes state in an external account. In an agent setting, that omission can lead to unintended publication, overwriting draft state, or disclosure of content to a live platform without the user appreciating the consequence.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The default prompt describes a very broad capability set: accepting or extracting arbitrary articles, reformatting them, listing themes, applying a selection, and publishing to a WeChat Official Account. That breadth can cause the skill to be invoked for generic article-formatting or publishing requests without clear user intent boundaries, increasing the risk of unintended content transformation or publication actions when publishing prerequisites are already configured.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Line L54 explicitly instructs the skill to show the Chinese theme name when listing themes, which imposes a specific language requirement in the user-facing interface. The file does not offer an opt-in, alternative locale, or justification that the skill is strictly region- or language-specific.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The function accepts arbitrary data: URIs, remote http(s) URLs, and local filesystem paths for image sources, then reads or fetches that content and uploads it to WeChat. In an agent or automation context, this broad input handling can be abused for local file exfiltration or server-side request forgery if untrusted HTML or CLI arguments control image sources.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script silently reads local files or downloads remote resources referenced by image sources and forwards them to WeChat, which can cause unintended disclosure of sensitive data. Because this occurs automatically during publishing, a user may not realize that embedded paths or URLs in the HTML will result in outbound data transmission.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The script reads appId and appSecret from command-line arguments, config, or environment variables and uses them to request an access token from the WeChat API. There is no explicit warning in comments, docstrings, or runtime messaging that sensitive credentials are consumed and transmitted for authentication.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The skill allows acquiring article content from a URL and later repeats URL extraction behavior, but it does not warn that fetching a URL triggers network access and may disclose user-provided URLs or cause retrieval of private/internal resources if runtime access is broad. In agent runtimes, missing disclosure and validation around URL fetching increases the risk of privacy surprises and, depending on implementation, SSRF-style access to internal services.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

This manifest is predominantly written in Chinese, but the description for theme "黑玫瑰" is set to the English-only value "gothic". In a user-facing theme pack, forcing a different language for one entry without opt-in or explanation is a natural-language locale consistency issue under the language policy rule.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

This markdown file documents environment variables including WECHAT_APP_SECRET, which is a sensitive credential, but it does not include any warning about keeping secrets out of version control, logs, or shared config files. Under the markdown-specific missing-warning criterion, the skill description should disclose privacy or security implications when users are asked to supply credentials.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The command flow distinguishes a dry run from a 'real publish' step, but it does not explicitly warn that the final command sends content to an external service and may create or modify remote draft state. For markdown guidance, operations affecting external systems should include a clear user-facing warning about the impact.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The catalog presents nearly all selectable theme names and descriptions in Chinese, and the file does not indicate that this skill is China/Chinese-specific or that users may opt into another language. Under the language/locale policy, a fixed language without user choice or documented justification is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The renderer injects the fixed Chinese string "左右滑动查看组图" into generated output for every slider block. This imposes a specific language on users without offering a locale choice or documenting a region-specific constraint, which matches the language/locale policy violation criteria.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.