Back to skill

Security audit

WeChat Official Account Draft Management

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a real WeChat draft-management tool, but it handles account credentials and can publish or delete content with weak local secret-storage and confirmation guidance.

Install only if you are comfortable giving the tool WeChat Official Account API authority to upload media, read drafts, create drafts, submit publications, and delete drafts. Avoid putting the AppSecret in ~/.zshrc; use a dedicated secret manager or tightly permissioned local environment file, and harden or disable the token cache if possible. Treat publish and delete commands as irreversible account actions and confirm the target media_id 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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:34
Finding
WeChat AppSecret Persisted in a Shell Startup File<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:34-35` **Vulnerability Type**: Plaintext long-lived credential storage **Risk Level**: Medium ### Vulnerable Code ```bash export WECHAT_APPID="your-app-id" export WECHAT_APPSECRET="your-app-secret" ``` ### Technical Analysis The installation instructions recommend storing the WeChat AppSecret directly in `~/.zshrc`. A shell startup file is plaintext, is loaded by every interactive shell, and is commonly copied into workstation backups or included unintentionally in diagnostic archives and configuration repositories. The AppSecret is a long-lived account credential used with the AppID to request WeChat access tokens. Unlike a short-lived token, disclosure can permit repeated token acquisition until the secret is rotated. Although transmitting the credential to the official WeChat API is necessary for this Skill, persistent storage in a general-purpose shell configuration file is not the minimum-privilege storage method. Exploitation requires an attacker, malicious local process, backup operator, or accidentally exposed repository to obtain read access to the user's shell configuration. The finding does not indicate that the Skill sends the secret to any unauthorized endpoint. ### Attack Path 1. A user follows the documented setup instructions and writes the AppID and AppSecret to `~/.zshrc`. 2. The file is exposed through local account compromise, overly broad permissions, workstation backup access, support bundles, dotfile synchronization, or accidental source-control inclusion. 3. An attacker extracts `WECHAT_APPID` and `WECHAT_APPSECRET`. 4. The attacker submits the credentials to the official WeChat token endpoint. 5. Subject to WeChat account permissions and controls such as IP allowlisting, the attacker obtains an access token and invokes authorized account APIs. 6. The attacker can operate on drafts or publishing resources available to the compromised WeChat application identity. ## ...[truncated 660 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not recommend storing the AppSecret in `.zshrc`, `.bashrc`, or another general-purpose shell startup file. 2. Store the credential in an operating-system keychain or a dedicated secret manager. 3. Retrieve the credential only when the command is invoked and keep it out of shell history, process arguments, logs, and source-controlled configuration. 4. If a local environment file is unavoidable: - Place it in a dedicated directory with mode `0700`. - Set the file mode to `0600`. - Exclude it explicitly from source control and backup exports where appropriate. - Load it only for the relevant process rather than every interactive shell. 5. Document AppSecret rotation and incident-response procedures. 6. Retain and clearly document WeChat IP allowlisting as an additional defense, but do not treat it as a substitute for secure secret storage. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/channel.py:64
Finding
WeChat Bearer Access Token Cached Without Explicit Permission Hardening<![CDATA[ ## Vulnerability Details **File Location**: `scripts/channel.py:19-23, 42-46, 64-70` **Vulnerability Type**: Plaintext bearer-token storage with permissions inherited from the process umask **Risk Level**: Medium ### Vulnerable Code ```python # 存储文件 CONFIG_DIR = os.path.expanduser("~/.config/channel") ACCESS_TOKEN_FILE = os.path.join(CONFIG_DIR, "access_token.json") DRAFTS_CACHE_FILE = os.path.join(CONFIG_DIR, "drafts_cache.json") def ensure_config_dir(): """确保配置目录存在""" os.makedirs(CONFIG_DIR, exist_ok=True) ``` ```python # 检查缓存的 token if os.path.exists(ACCESS_TOKEN_FILE): try: with open(ACCESS_TOKEN_FILE, 'r', encoding='utf-8') as f: cache = json.load(f) ``` ```python # 缓存 token ensure_config_dir() with open(ACCESS_TOKEN_FILE, 'w', encoding='utf-8') as f: json.dump({ 'access_token': token, 'expires_at': datetime.now().timestamp() + expires_in - 300 # 提前5分钟过期 }, f) ``` ### Technical Analysis The application caches a WeChat bearer access token in plaintext at `~/.config/channel/access_token.json`. Neither `ensure_config_dir()` nor the file creation operation explicitly sets restrictive permissions. Consequently, the directory and file modes are determined by the user's current umask and any permissions on pre-existing paths. A bearer token authorizes API operations based on possession. A process or local user that can read the cache does not need the AppSecret during the token's remaining lifetime. The code also trusts an existing cache file without checking that it is a regular file owned by the current user or that its permissions are sufficiently restrictive. The cache itself supports the legitimate requirement to avoid unnecessary token requests. The security issue is the absence of explicit access-control enforcement and secure file creation, rather than caching as such. ### Attack Path 1. The user invokes any command, causing the Skill to request a WeChat access token. 2. The pr ...[truncated 1577 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer storing access tokens in an operating-system credential store or dedicated secret manager. 2. If a filesystem cache is required: - Create `~/.config/channel` with mode `0700`. - Create the token file with mode `0600`. - Reject cache files not owned by the current user. - Reject non-regular files and symbolic links. - Validate and correct permissions on existing directories and files. 3. Use secure atomic creation: - Create a temporary file in the same protected directory. - Use exclusive creation with restrictive permissions. - Write and flush the complete JSON document. - Atomically replace the destination. 4. Remove expired token files instead of leaving obsolete credentials on disk. 5. Avoid including tokens in exception output, diagnostics, or logs. 6. Consider keeping the token only in memory for short-lived invocations where the additional API request rate remains acceptable. 7. Add automated tests verifying directory mode, file mode, ownership checks, symbolic-link rejection, and expired-cache deletion. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (35)

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

Critical
Category
Data Flow
Content
try:
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req, timeout=30) as response:
            data = json.loads(response.read().decode('utf-8'))
            
            if 'access_token' in data:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
try:
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req, timeout=30) as response:
            data = json.loads(response.read().decode('utf-8'))
            
            if 'access_token' in data:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
try:
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req, timeout=30) as response:
            data = json.loads(response.read().decode('utf-8'))
            
            if 'access_token' in data:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
try:
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req, timeout=30) as response:
            data = json.loads(response.read().decode('utf-8'))
            
            if 'access_token' in data:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
try:
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req, timeout=30) as response:
            data = json.loads(response.read().decode('utf-8'))
            
            if 'access_token' in data:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
'Content-Length': len(data)
        })
        
        with urllib.request.urlopen(req, timeout=60) as response:
            result = json.loads(response.read().decode('utf-8'))
            
            if 'url' in result:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
'Content-Length': len(data)
        })
        
        with urllib.request.urlopen(req, timeout=60) as response:
            result = json.loads(response.read().decode('utf-8'))
            
            if 'url' in result:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The documented purpose understates actual behavior by omitting automatic cover generation, temporary file creation, use of the external `sips` utility, and comment-control settings. Description/behavior mismatches are dangerous because users and policy systems may authorize the skill under false assumptions, while the hidden behaviors expand local execution and content-modification surface.

Credential Access

High
Category
Privilege Escalation
Content
## 认证

### 获取 Access Token

```
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 认证

### 获取 Access Token

```
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
os.makedirs(CONFIG_DIR, exist_ok=True)

def get_access_token() -> Optional[str]:
    """获取 Access Token"""
    appid = os.getenv('WECHAT_APPID')
    appsecret = os.getenv('WECHAT_APPSECRET')
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
os.makedirs(CONFIG_DIR, exist_ok=True)

def get_access_token() -> Optional[str]:
    """获取 Access Token"""
    appid = os.getenv('WECHAT_APPID')
    appsecret = os.getenv('WECHAT_APPSECRET')
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
return token
            else:
                print(f"❌ 获取 Access Token 失败: {data.get('errmsg', 'Unknown error')}")
                return None
                
    except Exception as e:
Confidence
83% confidence
Finding
The script caches the retrieved access token under ~/.config/channel/access_token.json without setting restrictive file permissions or validating the directory/file mode. On multi-user systems or misconfigured environments, another local user or process may read the token and act on the associated WeChat account until expiry.

Credential Access

High
Category
Privilege Escalation
Content
parser.print_help()
        return
    
    # 获取 access token
    access_token = get_access_token()
    if not access_token:
        return
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill declares access to secrets and describes file, shell, and network-backed behavior, but it does not define any explicit tool scope or permission boundaries. In an agent setting, this can lead to over-broad execution authority, making accidental secret exposure, unintended file access, or unsafe command execution more likely if the implementation is invoked with excessive privileges.

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: channel
description: WeChat Official Account Draft Box management tool. Create and manage graphic draft articles via WeChat API, supporting text and images. Automatically extracts the first paragraph as summary. Supports draft creation, listing, publishing, and deletion.
env:
  - WECHAT_APPID
  - WECHAT_APPSECRET
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill exposes a destructive `delete` operation without warning that it permanently removes drafts or recommending confirmation before use. In automation contexts, this increases the chance of irreversible content loss from operator error, prompt injection, or misuse by downstream workflows.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The deletion endpoint is documented as a normal operation without any warning that it permanently removes draft content. In a content-management skill, undocumented destructive actions increase the risk of accidental data loss, especially if an agent or user invokes the endpoint without confirmation safeguards.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The document instructs users to upload local image files to WeChat using multipart/form-data but does not clearly warn that the file contents are transmitted to a third-party external service. In a skill that manages WeChat drafts and media, this omission can lead to inadvertent disclosure of sensitive local images or embedded metadata.

External Transmission

Medium
Category
Data Exfiltration
Content
}]
    }
    
    response = requests.post(url, json=data)
    return response.json()
```
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from typing import List, Dict, Optional

# 微信 API 配置
WECHAT_API_BASE = "https://api.weixin.qq.com/cgi-bin"

# 存储文件
CONFIG_DIR = os.path.expanduser("~/.config/channel")
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from typing import List, Dict, Optional

# 微信 API 配置
WECHAT_API_BASE = "https://api.weixin.qq.com/cgi-bin"

# 存储文件
CONFIG_DIR = os.path.expanduser("~/.config/channel")
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from typing import List, Dict, Optional

# 微信 API 配置
WECHAT_API_BASE = "https://api.weixin.qq.com/cgi-bin"

# 存储文件
CONFIG_DIR = os.path.expanduser("~/.config/channel")
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from typing import List, Dict, Optional

# 微信 API 配置
WECHAT_API_BASE = "https://api.weixin.qq.com/cgi-bin"

# 存储文件
CONFIG_DIR = os.path.expanduser("~/.config/channel")
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from typing import List, Dict, Optional

# 微信 API 配置
WECHAT_API_BASE = "https://api.weixin.qq.com/cgi-bin"

# 存储文件
CONFIG_DIR = os.path.expanduser("~/.config/channel")
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.