Back to skill

Security audit

Wechat Search

Security checks for vulnerabilities and agentic risk

Overview

This WeChat search skill has a coherent search purpose, but it runs unbundled external helper code with broad environment access and overstates some safety controls.

Review before installing. Use this only in an environment where exposing search queries to third-party search and fetch providers is acceptable, and avoid running it with broad cloud tokens or sensitive credentials in the agent environment. The publisher should bundle or directly implement the Tavily client, restrict child-process environment variables, validate URLs by hostname, pin dependencies, and either implement or remove the claimed compliance controls.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T09 · Insecure Skill Coding Practices

Warning
Location
wechat_search_fixed.py:78
Finding

External subprocesses inherit the complete parent environment

Content
View full analysis

Vulnerability Details

File Location: wechat_search.py:84-97, wechat_search_simple.py:71-84, wechat_search_fixed.py:78-92, wechat_search_fixed.py:107-114
Vulnerability Type: Excessive credential and environment exposure to child processes
Risk Level: Medium

Vulnerable Code

wechat_search.py:84-97:

python
tavily_key = os.environ.get('TAVILY_API_KEY')
if not tavily_key:
    print("Error: TAVILY_API_KEY environment variable not set", file=sys.stderr)
    return []
    
# Construct search query with site restriction
search_query = f'{query} site:mp.weixin.qq.com'

# Use subprocess to call Tavily Node.js script
result = subprocess.run([
    'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
    search_query, '-n', str(max_results)
], capture_output=True, text=True, timeout=30, 
env={**os.environ, 'TAVILY_API_KEY': tavily_key})

wechat_search_simple.py:71-84:

python
tavily_key = os.environ.get('TAVILY_API_KEY')
if not tavily_key:
    print("Error: TAVILY_API_KEY environment variable not set", file=sys.stderr)
    return []
    
# Construct search query with site restriction
search_query = f'{query} site:mp.weixin.qq.com'

# Call Tavily Node.js script
result = subprocess.run([
    'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
    search_query, '-n', str(max_results)
], capture_output=True, text=True, timeout=30, 
env={**os.environ, 'TAVILY_API_KEY': tavily_key})

wechat_search_fixed.py:78-92:

python
env = os.environ.copy()
if 'TAVILY_API_KEY' not in env:
    # Try to get from common locations
    tavily_config_path = os.path.expanduser('~/.openclaw/tavily-config.json')
    if os.path.exists(tavily_config_path):
        with open(tavily_config_path, 'r') as f:
            config = json.load(f)
            env['TAVILY_API_KEY'] = config.get('api_key', '')

result = su
...[truncated 2204 chars]
Remediation
View remediation

Remediation Suggestions

  • Construct an explicit allowlisted environment instead of using os.environ.copy() or {**os.environ, ...}.
  • Include only variables required to start the child process, such as a controlled PATH, locale settings, and TAVILY_API_KEY.
  • Apply the same restricted environment to the openclaw subprocess.
  • Resolve executables through trusted absolute paths and verify their ownership and permissions.
  • Run network-search helpers under a dedicated low-privilege account or sandbox with restricted filesystem and network access.
  • Avoid storing unrelated credentials in the environment of the process hosting third-party Skills.

T08 · Insecure Dependencies

Warning
Location
wechat_search_fixed.py:87
Finding

Unbundled and unpinned external JavaScript is executed with Skill privileges

Content
View full analysis

Vulnerability Details

File Location: wechat_search.py:92-97, wechat_search_simple.py:79-84, wechat_search_fixed.py:87-92
Vulnerability Type: Unverified external executable dependency
Risk Level: Medium

Vulnerable Code

wechat_search_fixed.py:87-92:

python
result = subprocess.run([
    'node',
    '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
    search_query,
    '-n', str(max_results)
], capture_output=True, text=True, timeout=30, env=env)

wechat_search.py:92-97:

python
# Use subprocess to call Tavily Node.js script
result = subprocess.run([
    'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
    search_query, '-n', str(max_results)
], capture_output=True, text=True, timeout=30, 
env={**os.environ, 'TAVILY_API_KEY': tavily_key})

wechat_search_simple.py:79-84:

python
# Call Tavily Node.js script
result = subprocess.run([
    'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
    search_query, '-n', str(max_results)
], capture_output=True, text=True, timeout=30, 
env={**os.environ, 'TAVILY_API_KEY': tavily_key})

Technical Analysis

All three implementations execute /root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs, but that file is not included in the audited project. The Skill does not pin a version, verify a cryptographic checksum, validate ownership or permissions, or otherwise establish the integrity of the JavaScript before executing it.

The effective behavior of the reviewed Skill therefore depends on mutable code outside the reviewed artifact. The hard-coded location under /root also assumes a privileged workspace layout. In addition, node is resolved through PATH, creating another executable-trust boundary.

This is not remote payload retrieval by the reviewed Python code: no code is downloaded from an external URL. It is an i ...[truncated 1017 chars]

Remediation
View remediation

Remediation Suggestions

  • Bundle the required Tavily client code in the reviewed package or use a documented API directly from Python.
  • Pin the external component to a reviewed version and verify a cryptographic digest before execution.
  • Verify that the script and all parent directories are owned by a trusted account and are not writable by less-privileged users.
  • Use an absolute, trusted path for the Node.js runtime instead of resolving node through an uncontrolled PATH.
  • Avoid root-specific workspace paths and run the helper with a dedicated low-privilege identity.
  • Restrict the child environment, filesystem access, and outbound network destinations.

T09 · Insecure Skill Coding Practices

Warning
Location
wechat_search_fixed.py:222
Finding

Substring-based URL validation accepts attacker-controlled lookalike domains

Content
View full analysis

Vulnerability Details

File Location: wechat_search.py:39-49, wechat_search_simple.py:13-22, wechat_search_fixed.py:222-233
Vulnerability Type: Improper URL and hostname validation
Risk Level: Medium

Vulnerable Code

wechat_search.py:39-49:

python
def is_valid_wechat_url(self, url):
    """Check if URL is a valid WeChat Official Account article"""
    try:
        parsed = urlparse(url)
        if 'mp.weixin.qq.com' not in parsed.netloc:
            return False
        # Valid WeChat URLs have /s in the path
        return '/s' in parsed.path
    except:
        return False

wechat_search_simple.py:13-22:

python
def is_valid_wechat_url(url):
    """Check if URL is a valid WeChat Official Account article"""
    try:
        parsed = urlparse(url)
        if 'mp.weixin.qq.com' not in parsed.netloc:
            return False
        return '/s' in parsed.path
    except:
        return False

wechat_search_fixed.py:222-233:

python
def is_valid_wechat_url(self, url):
    """Check if URL is a valid WeChat Official Account article"""
    try:
        if not url.startswith('http'):
            return False
        if 'mp.weixin.qq.com' in url:
            return True
        if 'weixin.sogou.com/link' in url:
            return True
        return False
    except:
        return False

Technical Analysis

The first two variants test whether the trusted hostname text appears anywhere in parsed.netloc. This accepts attacker-controlled hosts such as mp.weixin.qq.com.attacker.example. It can also mishandle crafted authority components containing user information.

The fixed variant is weaker because it searches the entire URL string. Trusted text can therefore appear in an attacker-controlled path, query parameter, fragment, user-information component, or lookalike hostname. Its scheme check also accepts any string begi ...[truncated 1045 chars]

Remediation
View remediation

Remediation Suggestions

  • Parse every URL with urllib.parse.urlsplit.
  • Require scheme == "https".
  • Reject URLs containing embedded usernames or passwords.
  • Normalize the hostname by lowercasing it and handling a trailing dot.
  • Require an exact hostname match against an explicit allowlist, such as mp.weixin.qq.com.
  • If subdomains are needed, verify them using label boundaries rather than substring matching.
  • Treat Sogou redirect links as untrusted until the final redirect destination has been resolved and validated.
  • Avoid presenting unvalidated external links as trusted WeChat content.

other

Note
Location
wechat_search_fixed.py:100
Finding

Documented robots.txt, rate-limiting, and retry controls are not enforced

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:68-85, README.md:67-84, wechat_search_fixed.py:20-26, wechat_search_fixed.py:100-119
Vulnerability Type: Misleading security and compliance controls
Risk Level: Low

Vulnerable Code and Claims

SKILL.md:68-85:

markdown
### Layer 3: Direct Web Fetch
- Final fallback when both APIs are unavailable
- Scrapes WeChat search results directly from搜狗微信搜索
- Implements proper delays and respects robots.txt
- Parses HTML to extract article metadata

## Compliance & Ethics
- **Respects robots.txt**: Checks and follows robots.txt directives
- **Rate limiting**: Minimum 5-second delay between requests
- **Transparent identification**: Clear User-Agent string identifying the bot
- **Public content only**: Only accesses publicly available articles
- **No data retention**: Does not store full article content, only metadata

## Error Handling
- Automatic retry on network failures (up to 3 attempts)

wechat_search_fixed.py:20-26 defines settings but does not enforce them:

python
default_config = {
    "max_results": 5,
    "search_strategy": "auto",
    "cache_duration_hours": 1,
    "request_delay_ms": 5000,
    "user_agent": "OpenClaw-WeChat-Search-Bot/1.0"
}

wechat_search_fixed.py:100-119 performs the fallback without a robots.txt check, configured delay, configured user agent, or retry loop:

python
def web_fetch_wechat(self, query, max_results=5):
    """Use OpenClaw's web_fetch tool to fetch WeChat search results"""
    try:
        # Construct WeChat search URL (using Sogou as proxy)
        encoded_query = urllib.parse.quote(query)
        search_url = f"https://weixin.sogou.com/weixin?type=2&query={encoded_query}"
        
        result = subprocess.run([
            'openclaw', 'tool', 'web_fetch',
            '--url', search_url,
            '--extract-mode', 'markdown'
        ],
...[truncated 1953 chars]
Remediation
View remediation

Remediation Suggestions

  • Implement a robots.txt check before direct fetching and define safe behavior when robots.txt cannot be retrieved.
  • Enforce request_delay_ms using a monotonic-clock rate limiter.
  • Pass the configured user agent to the actual fetch mechanism.
  • Add a bounded retry loop with exponential backoff and jitter for transient failures.
  • Test each documented control with automated tests.
  • Remove or qualify any documentation claims that are not implemented.
  • Align configuration key names and structure across README.md, SKILL.md, example-config.json, and the Python implementation.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Advertising OpenClaw web search, web fetch, and compliance safeguards while only invoking a Tavily script via subprocess creates misleading trust signals and hides the real third-party and local execution dependencies. Such misrepresentation increases the chance that secrets, queries, or execution privileges are exposed in ways users and operators did not approve.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Advertising OpenClaw web search, web fetch, and compliance safeguards while only invoking a Tavily script via subprocess creates misleading trust signals and hides the real third-party and local execution dependencies. Such misrepresentation increases the chance that secrets, queries, or execution privileges are exposed in ways users and operators did not approve.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Advertising OpenClaw web search, web fetch, and compliance safeguards while only invoking a Tavily script via subprocess creates misleading trust signals and hides the real third-party and local execution dependencies. Such misrepresentation increases the chance that secrets, queries, or execution privileges are exposed in ways users and operators did not approve.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Advertising OpenClaw web search, web fetch, and compliance safeguards while only invoking a Tavily script via subprocess creates misleading trust signals and hides the real third-party and local execution dependencies. Such misrepresentation increases the chance that secrets, queries, or execution privileges are exposed in ways users and operators did not approve.

Content

No source excerpt is available for this finding.

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
95% confidence
Finding

The code forwards the full parent environment to an external child process via {**os.environ, 'TAVILY_API_KEY': tavily_key}, not just the needed API key. In the context of executing another script from a separate workspace path, this can leak unrelated secrets, tokens, and sensitive configuration to code that does not need them, making compromise of that helper far more damaging.

Content

Scanner excerpt · wechat_search.py (reported line 97)May include surrounding context.

python
'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
                search_query, '-n', str(max_results)
            ], capture_output=True, text=True, timeout=30, 
            env={**os.environ, 'TAVILY_API_KEY': tavily_key})
            
            if result.returncode == 0:
                return self._parse_tavily_results(result.stdout, max_results)

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
82% confidence
Finding

The code copies the entire parent environment and forwards it to a child process, which unnecessarily exposes all available secrets and runtime metadata to that subprocess. Even though the apparent goal is only to supply TAVILY_API_KEY, broad propagation increases blast radius if the called script is compromised, replaced, or logs its environment.

Content

Scanner excerpt · wechat_search_fixed.py (reported line 78)May include surrounding context.

python
search_query = f'{query} site:mp.weixin.qq.com'
            
            # Call Tavily search script directly
            env = os.environ.copy()
            if 'TAVILY_API_KEY' not in env:
                # Try to get from common locations
                tavily_config_path = os.path.expanduser('~/.openclaw/tavily-config.json')

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
96% confidence
Finding

The code forwards the entire parent environment to a subprocess via {**os.environ, 'TAVILY_API_KEY': tavily_key}, which can expose unrelated secrets, tokens, and internal configuration to the child process. In a skill ecosystem where helper scripts or dependencies may be less trusted, this broad environment propagation increases the blast radius of any compromise or logging within the Node.js script.

Content

Scanner excerpt · wechat_search_simple.py (reported line 84)May include surrounding context.

python
'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
            search_query, '-n', str(max_results)
        ], capture_output=True, text=True, timeout=30, 
        env={**os.environ, 'TAVILY_API_KEY': tavily_key})
        
        if result.returncode == 0:
            return parse_tavily_results(result.stdout, max_results)

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · README.md (reported line 47)May include surrounding context.

text

## Configuration
Create `~/.openclaw/wechat-search-config.json` to customize behavior:

```json
{

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 52)May include surrounding context.

text

## Configuration
Create `~/.openclaw/wechat-search-config.json` to customize behavior:

```json
{

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding

The skill metadata declares no explicit tool scope, yet the analyzed implementation reportedly uses sensitive capabilities including environment access, filesystem reads, network access, and shell/subprocess execution. Missing permission declarations make the skill's real trust boundary opaque and can enable users or hosting platforms to invoke a skill with more power than its manifest suggests.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill tells users it may fall back to direct web fetching and third-party search services but does not clearly warn that their search terms will be transmitted to external providers and websites. For potentially sensitive Chinese-language or organizational research queries, that omission can create privacy, confidentiality, and regulatory risks.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill executes an external Node.js script from another skill's workspace path, expanding its effective privileges beyond simple WeChat search. This creates a trust-boundary problem: if that script or path is modified, this skill will run unexpected code and expose its inherited capabilities and data to that code.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · wechat_search.py (reported line 93)May include surrounding context.

python
search_query = f'{query} site:mp.weixin.qq.com'
            
            # Use subprocess to call Tavily Node.js script
            result = subprocess.run([
                'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
                search_query, '-n', str(max_results)
            ], capture_output=True, text=True, timeout=30,

Tainted flow: 'tavily_key' from os.environ.get (line 84, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · wechat_search.py (reported line 93)May include surrounding context.

python
search_query = f'{query} site:mp.weixin.qq.com'
            
            # Use subprocess to call Tavily Node.js script
            result = subprocess.run([
                'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
                search_query, '-n', str(max_results)
            ], capture_output=True, text=True, timeout=30,

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The manifest describes a compliance-focused WeChat article search skill using OpenClaw web search, Tavily API, and web fetch. While network search itself is expected, explicitly harvesting credentials from process environment state and ~/.openclaw/tavily-config.json adds a credential-access capability that is not stated as part of the skill's purpose.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · wechat_search_fixed.py (reported line 87)May include surrounding context.

python
config = json.load(f)
                        env['TAVILY_API_KEY'] = config.get('api_key', '')
            
            result = subprocess.run([
                'node',
                '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
                search_query,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · wechat_search_fixed.py (reported line 110)May include surrounding context.

python
encoded_query = urllib.parse.quote(query)
            search_url = f"https://weixin.sogou.com/weixin?type=2&query={encoded_query}"
            
            result = subprocess.run([
                'openclaw', 'tool', 'web_fetch',
                '--url', search_url,
                '--extract-mode', 'markdown'

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · wechat_search_simple.py (reported line 80)May include surrounding context.

python
search_query = f'{query} site:mp.weixin.qq.com'
        
        # Call Tavily Node.js script
        result = subprocess.run([
            'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
            search_query, '-n', str(max_results)
        ], capture_output=True, text=True, timeout=30,

Tainted flow: 'tavily_key' from os.environ.get (line 71, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · wechat_search_simple.py (reported line 80)May include surrounding context.

python
search_query = f'{query} site:mp.weixin.qq.com'
        
        # Call Tavily Node.js script
        result = subprocess.run([
            'node', '/root/.openclaw/workspace/skills/tavily-search/scripts/search.mjs',
            search_query, '-n', str(max_results)
        ], capture_output=True, text=True, timeout=30,

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

This markdown file documents behavior that sends user queries to third-party services and, as a fallback, directly fetches search-result pages from external sites. Although the README explains the strategy and compliance posture, it does not clearly warn users that their search terms will be transmitted off-system to Brave, Tavily, or Sogou/WeChat-related endpoints.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified as requests>=2.25.0, which permits installation of many different future or older-compatible versions rather than a known-reviewed release. This weakens supply-chain integrity and reproducibility, and in this skill's network-facing context it increases the chance of pulling a vulnerable requests version during deployment.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
# Runtime dependencies
requests>=2.25.0

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding

Because the manifest does not pin requests, it is impossible to verify whether deployed environments will receive a version affected by known advisories. In a skill that performs web search and fetching, a vulnerable HTTP client can materially increase risk of credential leakage, TLS/verification issues, or other request-handling flaws depending on the resolved version.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The module documentation states that the skill uses the Tavily Search API, which implies a direct API-based implementation. In practice, the code delegates the search to a separate Node.js script through subprocess, so the documentation misstates how the capability is implemented in a way that affects the skill's execution model and trust boundary.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.