T09 · Insecure Skill Coding Practices
- Location
scripts/run_upload.py:3- Finding
Hardcoded WeChat Application Credentials
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly matches a WeChat draft-upload purpose, but it also ships under-disclosed draft-reading/export scripts and exposes WeChat credentials in source and command-line usage.
Review before installing. Remove the hardcoded credential wrapper and the get_draft scripts unless you explicitly need draft export, rotate any exposed AppSecret, avoid passing secrets on the command line, and run only with a WeChat account and workspace where local plaintext draft exports would be acceptable.
scripts/run_upload.py:3Hardcoded WeChat Application Credentials
SKILL.md:45AppSecret Exposed Through Command-Line Arguments
scripts/get_draft.py:30Undeclared Over-Fetching and Plaintext Storage of Private Draft Content
scripts/run_upload.py:14Relative Script Execution Enables Local Code Substitution
SKILL.md:34Unpinned Third-Party Dependency Installation
A skill that claims to create drafts but instead only queries existing drafts and saves their contents locally represents a significant trust violation. In this context, the user is expected to supply sensitive WeChat credentials for publishing, so undeclared access to historical drafts or local export of article contents could leak proprietary or unpublished material.
A skill that claims to create drafts but instead only queries existing drafts and saves their contents locally represents a significant trust violation. In this context, the user is expected to supply sensitive WeChat credentials for publishing, so undeclared access to historical drafts or local export of article contents could leak proprietary or unpublished material.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
GET https://api.weixin.qq.com/cgi-bin/token
?grant_type=client_credential
The script’s stated purpose in the skill metadata is uploading local Markdown into the WeChat draft box, but this file instead retrieves existing drafts and can export their full HTML content. That scope mismatch is dangerous because a user invoking an upload-oriented skill may unknowingly grant credentials that are then used for data discovery and exfiltration of existing private draft content.
The script's behavior materially differs from the declared skill purpose: instead of uploading local Markdown to create a draft, it fetches existing drafts from the WeChat account and prints/exports their HTML content. In a skill context, this expands access from publishing to content exfiltration, increasing the risk of unauthorized retrieval of unpublished articles and account data.
The file hardcodes a WeChat appid and secret directly into sys.argv, exposing live credentials in source code. In a skill designed to publish content to a public account, compromised credentials could enable unauthorized draft creation, content manipulation, API abuse, and possible takeover of connected publishing workflows.
The script executes the full contents of a local file with exec(), which means any modification to upload_draft.py results in arbitrary code execution in the current process. In an agent skill context, this is especially dangerous because the wrapper auto-runs with embedded credentials and a target file path, so tampering with the referenced script can immediately lead to credential theft, unauthorized API use, or broader system compromise.
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
exec(open('upload_draft.py', encoding='utf-8').read())
This is a true arbitrary-code-execution pattern: file contents are read directly from disk and passed into exec() without validation. If an attacker, another tool, or even an accidental local change alters upload_draft.py, the wrapper will execute that code with the same privileges and access to the embedded WeChat credentials.
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
exec(open('upload_draft.py', encoding='utf-8').read())
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# ─────────────────────────────────────────────
# 2. 获取 Access Token
# ─────────────────────────────────────────────
def get_access_token(appid, secret):
The skill requests users to provide local file paths, runs a Python script, and calls external WeChat APIs, but the manifest declares no explicit tool scope or permissions boundary. This creates an authorization gap where file read/write and network actions may occur without clear least-privilege constraints or user-visible guardrails.
The trigger description is broad and ambiguous, covering general requests like publishing or uploading to a public account without tight boundaries on prerequisites, account scope, or side effects. Overbroad activation increases the chance the agent invokes a credentialed, networked, file-accessing skill in situations where the user did not intend to authorize those actions.
Accepting the AppSecret as a command-line argument exposes it to process listings, shell history, audit logs, and job runners. In this script, the credential is highly sensitive because it enables access token acquisition and subsequent access to WeChat draft data.
This code enumerates all draft titles and, when a title is provided, prints the full draft HTML and metadata to stdout. In the context of an upload-focused skill, this expands capabilities into reading and disclosing existing content, increasing the chance of unintended exposure through logs, terminal history, or downstream agent handling.
The script saves retrieved draft HTML content to a local file automatically, creating persistent copies of potentially sensitive unpublished articles without an explicit warning or consent step. This increases the risk of local data leakage through shared workstations, backups, source trees, or accidental reuse of the exported file.
The script sends the AppID and AppSecret to the WeChat token endpoint, and later uses the resulting access token in another API request. Although the code prints success/failure messages, it does not clearly warn users that their supplied credentials and account draft data will be transmitted to an external service.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def get_access_token(appid, secret):
"""获取 access_token"""
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"
response = requests.get(url)
result = response.json()
No suspicious patterns detected.