Back to skill

Security audit

Daily Wechat Writer

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly a disclosed WeChat draft-writing workflow, but its uploader can transmit arbitrary local files or unrestricted remote downloads through Markdown image references.

Review before installing. Only run it with trusted Markdown content, avoid untrusted image URLs or local image paths, and use a restricted account/environment because image references can cause local files or internal network responses to be sent to WeChat. The skill also needs clearer disclosure around credential-file use, scheduling, and the fact that part of the writing workflow is currently only simulated.

Vulnerability Patterns
  • 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
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/upload_to_wechat.py:244
Finding

Unrestricted Local File Upload Through Markdown Image References

Content
View full analysis

Vulnerability Details

File Location: scripts/upload_to_wechat.py, lines 83-100, 136-145, and 244-259
Vulnerability Type: Unrestricted local file access and external transmission
Risk Level: High

Vulnerable Code

python
def run_manual(self, title, raw_content):
     # 1. Look for images in content
     images = re.findall(r'!\[(.*?)\]\((.*?)\)', raw_content)

     thumb_media_id = None
     processed_content = raw_content

     # 2. Upload images found in content
     for i, (alt, url) in enumerate(images):
         logger.info(f"Processing image {i+1}: {url}")
         # Add cover logic: first image as cover
         if i == 0:
             thumb_media_id = self.upload_cover_image(url)

         # Upload for article body (returns URL)
         _, wechat_url = self.upload_image(url)
         if wechat_url:
             # Replace markdown image with HTML img tag
             processed_content = processed_content.replace(f'![{alt}]({url})', f'<img src="{wechat_url}" alt="{alt}" />')
python
else:
    image_path = image_path_or_url

# Check if file exists
if not os.path.exists(image_path):
    logger.error(f"Image file not found: {image_path}")
    return None, None

# Upload to WeChat
try:
    with open(image_path, 'rb') as f:
        files = {'media': (os.path.basename(image_path), f)}
        r = requests.post(url, files=files)
        res = r.json()
python
else:
    image_path = image_path_or_url

try:
    with open(image_path, 'rb') as f:
        files = {'media': (os.path.basename(image_path), f)}
        r = requests.post(url, files=files)
        res = r.json()

Technical Analysis

Markdown image destinations are treated either as remote URLs or local filesystem paths. For local paths, the uploader performs no canonicalization, directory restriction, symbolic-link check, file-type validation, or authorization che ...[truncated 1782 chars]

Remediation
View remediation

Remediation Suggestions

  1. Create a dedicated media directory and permit uploads only from that directory.
  2. Resolve every input with Path.resolve(strict=True) and verify that the resolved path remains beneath the approved media root.
  3. Reject symbolic links, non-regular files, device files, pipes, and sockets.
  4. Validate the decoded image format using a trusted image library rather than relying on the extension or API-side rejection.
  5. Enforce an explicit allowlist of image formats and maximum dimensions and sizes.
  6. Separate local-path and remote-URL inputs instead of inferring their type from a string prefix.
  7. Run the uploader under a dedicated, minimally privileged account that cannot read unrelated secrets.
  8. Require explicit user confirmation before transmitting any local file that was not generated by the current workflow.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/upload_to_wechat.py:63
Finding

Server-Side Request Forgery Through Unrestricted Image URLs

Content
View full analysis

Vulnerability Details

File Location: scripts/upload_to_wechat.py, lines 63-80 and 124-132
Vulnerability Type: Server-side request forgery and unrestricted network access
Risk Level: High

Vulnerable Code

python
# Handle remote URL
temp_file = None
if image_path_or_url.startswith(('http://', 'https://')):
    try:
        # logger.info(f"Downloading image from URL: {image_path_or_url}")
        headers = {'User-Agent': 'Mozilla/5.0'}
        img_resp = requests.get(image_path_or_url, headers=headers, stream=True)
        img_resp.raise_for_status()

        # Create a temporary file
        filename = os.path.basename(image_path_or_url.split('?')[0])
        if not filename: filename = "temp_image.jpg"
        # Force jpg extension if missing, WeChat needs it
        if not filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')):
            filename += ".jpg"

        temp_file = filename
        with open(temp_file, 'wb') as f:
            for chunk in img_resp.iter_content(chunk_size=8192):
                f.write(chunk)
        image_path = temp_file
python
temp_file = None
if image_path_or_url.startswith(('http://', 'https://')):
    try:
        headers = {'User-Agent': 'Mozilla/5.0'}
        img_resp = requests.get(image_path_or_url, headers=headers, stream=True)
        img_resp.raise_for_status()
        filename = "cover_temp.jpg"
        with open(filename, 'wb') as f:
            for chunk in img_resp.iter_content(chunk_size=8192):
                f.write(chunk)
        image_path = filename
        temp_file = filename
    except Exception as e:
        print(f"❌ Cover download error: {e}")
        return None

Technical Analysis

Image URLs extracted from article Markdown are requested directly by the uploader. The implementation has no hostname allowlist, destination IP validation, DNS rebinding defense, redirect val ...[truncated 1747 chars]

Remediation
View remediation

Remediation Suggestions

  1. Prefer approved local media and disable arbitrary remote fetching unless it is strictly required.
  2. If remote images are required, allowlist trusted HTTPS hostnames and ports.
  3. Resolve destination hostnames before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IP addresses for both IPv4 and IPv6.
  4. Disable redirects or validate the hostname and resolved IP address at every redirect hop.
  5. Protect against DNS rebinding by ensuring the validated address is the address actually used for the connection.
  6. Do not inherit untrusted proxy configuration for this request path.
  7. Require a valid image content type and independently decode and validate the image before upload.
  8. Apply outbound firewall rules so the uploader cannot reach metadata endpoints or internal management networks.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/upload_to_wechat.py:72
Finding

Predictable Temporary Files Permit File Overwrite and Deletion

Content
View full analysis

Vulnerability Details

File Location: scripts/upload_to_wechat.py, lines 72-80, 110-113, 129-132, and 154-157
Vulnerability Type: Unsafe temporary-file creation
Risk Level: Medium

Vulnerable Code

python
# Create a temporary file
filename = os.path.basename(image_path_or_url.split('?')[0])
if not filename: filename = "temp_image.jpg"
# Force jpg extension if missing, WeChat needs it
if not filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')):
    filename += ".jpg"

temp_file = filename
with open(temp_file, 'wb') as f:
    for chunk in img_resp.iter_content(chunk_size=8192):
        f.write(chunk)
python
finally:
    # Clean up temp file
    if temp_file and os.path.exists(temp_file):
        os.remove(temp_file)
python
filename = "cover_temp.jpg"
with open(filename, 'wb') as f:
    for chunk in img_resp.iter_content(chunk_size=8192):
        f.write(chunk)
image_path = filename
temp_file = filename
python
finally:
    if temp_file and os.path.exists(temp_file):
        os.remove(temp_file)

Technical Analysis

Remote image downloads are stored in the current working directory using either an attacker-influenced URL basename or the fixed name cover_temp.jpg. Files are opened with mode wb, which truncates existing files and follows symbolic links. The cleanup logic subsequently deletes the pathname.

This creates a classic unsafe temporary-file condition. A pre-existing file with the chosen name can be overwritten. If an attacker can create a symbolic link in the working directory, the write can be redirected to another file writable by the uploader. Cleanup can also remove a pre-existing pathname after it has been overwritten.

Concurrent uploader executions additionally share the same cover filename, allowing one invocation to corrupt, consume, or delete another invocation's temporary data.

Attack Path

  1. Th ...[truncated 1096 chars]
Remediation
View remediation

Remediation Suggestions

  1. Use tempfile.TemporaryDirectory or tempfile.NamedTemporaryFile with exclusive creation and restrictive permissions.
  2. Store temporary files outside the project and current working directories.
  3. Never derive a temporary pathname directly from a remote URL.
  4. Avoid fixed filenames and ensure every invocation receives a unique path.
  5. Keep the temporary file handle open where possible rather than reopening the pathname.
  6. Verify that any path used for cleanup was created by the current invocation.
  7. Run the process with a restrictive umask and minimal filesystem permissions.
  8. Add concurrency tests to confirm that parallel uploads cannot share or delete each other's files.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/upload_to_wechat.py:67
Finding

Unbounded Remote Image Downloads Enable Resource Exhaustion

Content
View full analysis

Vulnerability Details

File Location: scripts/upload_to_wechat.py, lines 67-80 and 127-132
Vulnerability Type: Unbounded network download and denial of service
Risk Level: Medium

Vulnerable Code

python
headers = {'User-Agent': 'Mozilla/5.0'}
img_resp = requests.get(image_path_or_url, headers=headers, stream=True)
img_resp.raise_for_status()

# Create a temporary file
filename = os.path.basename(image_path_or_url.split('?')[0])
if not filename: filename = "temp_image.jpg"
# Force jpg extension if missing, WeChat needs it
if not filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')):
    filename += ".jpg"

temp_file = filename
with open(temp_file, 'wb') as f:
    for chunk in img_resp.iter_content(chunk_size=8192):
        f.write(chunk)
python
headers = {'User-Agent': 'Mozilla/5.0'}
img_resp = requests.get(image_path_or_url, headers=headers, stream=True)
img_resp.raise_for_status()
filename = "cover_temp.jpg"
with open(filename, 'wb') as f:
    for chunk in img_resp.iter_content(chunk_size=8192):
        f.write(chunk)

Technical Analysis

Both remote-download paths omit connect and read timeouts. They also write response bodies until the server closes the connection, without enforcing a maximum byte count or validating Content-Length.

An attacker-controlled endpoint can keep the connection open while transmitting data slowly, causing the uploader to block indefinitely. It can also transmit a very large or infinite response, consuming available disk space. Because the first Markdown image is downloaded once as the cover and again as a body image, resource consumption may be duplicated.

Attack Path

  1. An attacker supplies a Markdown image URL pointing to a server they control.
  2. The uploader initiates a streaming request without a timeout.
  3. The server either transmits indefinitely, sends an excessively large response, or deliberately se ...[truncated 656 chars]
Remediation
View remediation

Remediation Suggestions

  1. Configure explicit connect and read timeouts, for example a short connection deadline and a bounded per-read timeout.
  2. Reject responses whose declared Content-Length exceeds the permitted image size.
  3. Track the cumulative number of streamed bytes and abort immediately when the configured maximum is exceeded.
  4. Limit the number of images processed per article and the total bytes downloaded per invocation.
  5. Decode and validate image data before submitting it to WeChat.
  6. Store downloads on a filesystem with an enforced quota.
  7. Close response objects deterministically by using a context manager.
  8. Apply process-level execution deadlines and resource limits as defense in depth.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Rogue AgentSelf-Modification, Session Persistence
Findings (18)

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/upload_to_wechat.py (reported line 26)May include surrounding context.

python
self.expires_at = 0

    def get_access_token(self):
        """Fetch or return cached access token"""
        current_time = time.time()
        if self.access_token and current_time < self.expires_at:
            return self.access_token

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/upload_to_wechat.py (reported line 38)May include surrounding context.

python
self.expires_at = 0

    def get_access_token(self):
        """Fetch or return cached access token"""
        current_time = time.time()
        if self.access_token and current_time < self.expires_at:
            return self.access_token

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/upload_to_wechat.py (reported line 42)May include surrounding context.

python
self.expires_at = 0

    def get_access_token(self):
        """Fetch or return cached access token"""
        current_time = time.time()
        if self.access_token and current_time < self.expires_at:
            return self.access_token

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/upload_to_wechat.py (reported line 43)May include surrounding context.

python
self.expires_at = 0

    def get_access_token(self):
        """Fetch or return cached access token"""
        current_time = time.time()
        if self.access_token and current_time < self.expires_at:
            return self.access_token

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/upload_to_wechat.py (reported line 47)May include surrounding context.

python
self.expires_at = 0

    def get_access_token(self):
        """Fetch or return cached access token"""
        current_time = time.time()
        if self.access_token and current_time < self.expires_at:
            return self.access_token

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/upload_to_wechat.py (reported line 48)May include surrounding context.

python
self.expires_at = 0

    def get_access_token(self):
        """Fetch or return cached access token"""
        current_time = time.time()
        if self.access_token and current_time < self.expires_at:
            return self.access_token

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill is explicitly designed to collect data from multiple external platforms on a schedule and then upload generated content to WeChat, but the description does not clearly warn users about this automated outbound data flow. This creates a meaningful transparency and consent risk: users may not realize that third-party services are queried regularly and that generated content is automatically sent to an external publishing platform.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The description says the skill 'ensures Chinese title is used,' which imposes a specific language behavior by default. Under the policy, forcing a language or locale without user opt-in is a natural-language policy violation unless the regional constraint is explicitly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script description references WeChat and all user-facing status messages are hard-coded in Chinese, with no indication that language is configurable or optional. This creates a natural-language locale policy concern because the skill implicitly enforces a specific language without user opt-in.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The script falsely claims to perform external research, AI writing, image generation, and WeChat draft upload, but in reality it only prints progress text and sleeps. This is dangerous because it can mislead operators into believing automated actions occurred, causing bad publication decisions, workflow bypass, or misplaced trust in nonexistent integrations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The user-facing output is entirely in Chinese and instructs the user to reply using Chinese trigger phrases such as “选1/选2/选3”. This imposes a specific language on users without any opt-in, alternative locale, or documented justification for the constraint.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script accepts image URLs from markdown content and performs server-side HTTP requests to fetch them. This creates SSRF-style behavior and broadens the tool from local article upload into arbitrary network retrieval, which can be abused to contact internal services, unexpected hosts, or attacker-controlled endpoints.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The code downloads attacker-influenced remote content and writes it to disk using a filename derived from the URL without explicit consent, size checks, or strong temporary-file handling. This can lead to untrusted content being stored locally, resource exhaustion, or unintended overwrites in the current working directory.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
89% confidence
Finding

The script persists downloaded remote content to a predictable filename in the working directory before cleanup. If the process crashes or if filenames collide, untrusted files may remain on disk or overwrite existing files, increasing persistence and local file-safety risk.

Content

Scanner excerpt · scripts/upload_to_wechat.py (reported line 70)May include surrounding context.

python
img_resp = requests.get(image_path_or_url, headers=headers, stream=True)
                img_resp.raise_for_status()
                
                # Create a temporary file
                filename = os.path.basename(image_path_or_url.split('?')[0])
                if not filename: filename = "temp_image.jpg"
                # Force jpg extension if missing, WeChat needs it

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

Referencing an environment variable for an API key is normal, but the skill text does not include any guidance on secure credential handling, storage, rotation, or logging avoidance. In a workflow that encourages testing and automation, this omission can lead to accidental exposure of the Imagen API key through misconfiguration, shell history, logs, or shared deployment environments.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

With no manifest available, the skill's purpose is unknown, so accessing environment-based secret/config state is not justified by any declared intent. The code checks for GOOGLE_IMAGEN_API_KEY, which indicates capability to inspect runtime credential/configuration beyond simple local argument handling.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

Natural-language policy violations include forcing a specific language without user opt-in. These user-facing messages are emitted only in Chinese, while the rest of the script and CLI are otherwise in English, and there is no documented locale choice or justification for restricting output language.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The code's main function is to upload article content to WeChat, but it also probes and reads ~/.openclaw/credentials/wechat.env for secrets. In the absence of a manifest declaring local credential-file access, this is a context-expanding capability because it accesses sensitive local data beyond the content being uploaded.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.