Back to skill

Security audit

News Evening Digest - 新闻晚报

Security checks for vulnerabilities and agentic risk

Overview

This skill is a scheduled news digest, but its documented Feishu/QQ delivery does not match the code, which can post to an undocumented WeCom webhook and notify everyone there.

Review before installing. Only use this if you are comfortable with an automatic scheduled script posting digests externally, and verify the actual destination variable WECOM_WEBHOOK_URL before running it. Treat webhook URLs as secrets, remove the default @all behavior unless wanted, and do not rely on the documented Feishu/QQ delivery claims without fixing the implementation.

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
scripts/fetch_and_digest.py:35
Finding
Untrusted URLs Can Be Misclassified as High-Credibility Sources<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_and_digest.py:35-48` **Vulnerability Type**: Improper URL hostname validation **Risk Level**: Medium ### Vulnerable Code ```python def classify_source_credibility(url): """Classify source credibility level""" if not url: return 'B' high_credibility = ['people.com.cn', 'xinhuanet.com', 'cctv.com', 'gov.cn', 'reuters.com', 'bbc.com', 'apnews.com', 'nytimes.com'] medium_credibility = ['sina.com', '163.com', 'qq.com', 'toutiao.com', 'thepaper.cn', 'huanqiu.com'] for domain in high_credibility: if domain in url: return 'A' # High credibility for domain in medium_credibility: if domain in url: return 'B' # Medium credibility return 'C' # User-generated/social media ``` ### Technical Analysis The function determines source credibility by checking whether a trusted domain appears anywhere in the complete URL string. This does not establish that the URL's actual hostname belongs to the trusted organization. An attacker can place a trusted string in an untrusted URL, including: ```text https://reuters.com.attacker.example/fabricated-report https://attacker.example/article?source=bbc.com https://attacker.example/nytimes.com/fake-news ``` Each of these URLs can receive an `A` credibility rating despite not being controlled by the referenced news organization. The URLs originate in externally supplied Tavily search results. The resulting credibility rating is subsequently displayed in an automatically distributed digest, where an `A` rating is described as an official or highly credible source. There is no independent hostname validation or verification of the article. ### Attack Path 1. An attacker publishes a fabricated article on an attacker-controlled domain. 2. The URL is constructed to contain a trusted-domain string, such as `reuters.com.attacker.exa ...[truncated 1201 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Parse the URL and validate only its normalized hostname: ```python from urllib.parse import urlparse HIGH_CREDIBILITY = { "people.com.cn", "xinhuanet.com", "cctv.com", "gov.cn", "reuters.com", "bbc.com", "apnews.com", "nytimes.com", } def belongs_to_domain(hostname, trusted_domain): hostname = hostname.rstrip(".").lower() trusted_domain = trusted_domain.rstrip(".").lower() return hostname == trusted_domain or hostname.endswith("." + trusted_domain) def classify_source_credibility(url): if not url: return "C" try: parsed = urlparse(url) if parsed.scheme not in {"https", "http"} or not parsed.hostname: return "C" hostname = parsed.hostname.encode("idna").decode("ascii").lower() if any(belongs_to_domain(hostname, domain) for domain in HIGH_CREDIBILITY): return "A" except (TypeError, ValueError, UnicodeError): return "C" return "C" ``` Additional hardening should include: 1. Prefer HTTPS URLs and downgrade or reject plaintext HTTP sources. 2. Maintain an explicit allowlist of canonical publication hostnames. 3. Account for provider-specific domains rather than trusting arbitrary subdomains indiscriminately. 4. Treat search-engine results as untrusted input. 5. Do not describe automated domain classification as fact-checking. 6. Include the normalized source hostname in each digest entry so recipients can assess provenance. 7. Add tests for deceptive domains, query strings, user-info components, mixed case, trailing dots, and internationalized domain names. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/fetch_and_digest.py:302
Finding
Notification Configuration Mismatch Can Cause Unexpected WeCom Delivery<![CDATA[ ## Vulnerability Details **File Locations**: `INSTALL.md:11-17`, `SKILL.md:35-38`, and `scripts/fetch_and_digest.py:302-317` **Vulnerability Type**: Inconsistent webhook configuration and destination handling **Risk Level**: Low ### Documented Configuration `INSTALL.md:11-17` instructs the user to configure a Feishu webhook: ```bash ### 1. 配置飞书 Webhook ```bash # 编辑 .env 文件 notepad "$env:USERPROFILE\.openclaw\.env" # 添加以下内容(替换为你的飞书机器人 Webhook) FEISHU_WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx" QQ_ENABLED="true" ``` ``` `SKILL.md:35-38` documents the same variable: ```bash # 飞书机器人 Webhook(必需) export FEISHU_WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx" ``` ### Executed Code The implementation instead reads `WECOM_WEBHOOK_URL` and sends a WeCom-formatted payload: ```python def send_to_wecom(message): """Send to WeCom (Enterprise WeChat)""" webhook_url = os.environ.get('WECOM_WEBHOOK_URL') if not webhook_url: print("[ERROR] WeCom Webhook URL not configured") return False try: import requests # 企业微信消息格式 payload = { "msgtype": "text", "text": { "content": message, "mentioned_list": ["@all"] # 可选:@所有人 } } response = requests.post(webhook_url, json=payload, timeout=10) ``` ### Technical Analysis The declared interface and executable behavior disagree: - Installation and Skill documentation require `FEISHU_WEBHOOK_URL`. - The executable code ignores `FEISHU_WEBHOOK_URL`. - The code reads `WECOM_WEBHOOK_URL` instead. - The payload is sent to the value of that variable without checking that it belongs to the intended WeCom webhook service. - QQ delivery is also described as operational, but `send_to_qq()` only prints that a message is ready. Opening `~/.openclaw/.env` for manual editing does not itself disclose credentials, and the script does no ...[truncated 2063 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Select one supported provider and use the same provider name and environment variable consistently in all files. 2. If Feishu is intended, implement a Feishu-specific sender that reads `FEISHU_WEBHOOK_URL` and uses the documented Feishu payload format. 3. If WeCom is intended, replace every Feishu reference with WeCom and document `WECOM_WEBHOOK_URL`. 4. Validate the destination before sending. For example, require HTTPS and verify that the normalized hostname exactly matches an approved provider hostname. 5. Do not allow arbitrary URL schemes, embedded credentials, fragments, or unrelated domains. 6. Remove `mentioned_list: ["@all"]` by default or require an explicit opt-in variable. 7. Use provider-specific secret storage rather than directing users to a shared environment file containing unrelated credentials. 8. Fail closed when configuration is inconsistent; do not silently fall back to another notification provider. 9. Implement actual QQ delivery or clearly label it as unsupported. 10. Add startup diagnostics that display the selected provider and sanitized destination hostname without printing webhook tokens. A Feishu implementation should follow this pattern: ```python from urllib.parse import urlparse import os import requests def send_to_feishu(message): webhook_url = os.environ.get("FEISHU_WEBHOOK_URL") if not webhook_url: print("[ERROR] Feishu webhook URL not configured") return False parsed = urlparse(webhook_url) if parsed.scheme != "https" or parsed.hostname != "open.feishu.cn": print("[ERROR] Invalid Feishu webhook destination") return False payload = { "msg_type": "text", "content": { "text": message } } response = requests.post(webhook_url, json=payload, timeout=10) response.raise_for_status() return True ``` ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (22)

Tainted flow: 'payload' from os.environ.get (line 70, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
"max_results": num_results
        }
        
        response = requests.post(TAVILY_API_URL, json=payload, timeout=20)
        
        if response.status_code == 200:
            data = response.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'webhook_url' from os.environ.get (line 286, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
}
        }
        
        response = requests.post(webhook_url, json=payload, timeout=10)
        
        if response.status_code == 200:
            result = response.json()
Confidence
90% confidence
Finding
The webhook destination is taken directly from an environment variable and used as the outbound URL without validation or allowlisting. If an attacker can influence environment configuration, they can redirect the full digest to an arbitrary endpoint, causing unintended data exfiltration and possible SSRF into internal network services.

Credential Access

High
Category
Privilege Escalation
Content
### 1. 配置飞书 Webhook

```bash
# 编辑 .env 文件
notepad "$env:USERPROFILE\.openclaw\.env"

# 添加以下内容(替换为你的飞书机器人 Webhook)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
```bash
# 编辑 .env 文件
notepad "$env:USERPROFILE\.openclaw\.env"

# 添加以下内容(替换为你的飞书机器人 Webhook)
FEISHU_WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx"
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
A description-behavior mismatch is security-relevant because users may approve the skill based on the stated behavior while the implementation actually sends to different services, gathers broader content, and relies on different sources than claimed. That breaks informed consent and can hide unexpected data flows or operational behavior behind misleading documentation.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The manifest says this skill sends the evening digest to QQ and Feishu, but the only real outbound delivery implemented here is `send_to_wecom`, which posts to an Enterprise WeChat webhook. `send_to_qq` is only a placeholder that logs a message and returns success, and there is no Feishu sender at all, so the runtime behavior materially diverges from the stated delivery targets.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The guide instructs users to place a Feishu webhook URL into a shared environment file but does not clearly label it as a secret or warn against exposing it in logs, screenshots, backups, or source control. Feishu bot webhooks often function as bearer-style credentials, so disclosure can let an attacker send arbitrary messages to the configured channel and abuse the bot integration.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The README states that the skill automatically pushes news digests to QQ and Feishu but does not clearly warn users that content will be sent to external messaging platforms. This creates a consent and data-sharing risk because users may trigger or configure the skill without understanding that information leaves the local agent context and is delivered through third-party services.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill documents network access and environment-variable use but does not declare any explicit tool scope or permissions boundary. That weakens reviewability and least-privilege enforcement, making it easier for a skill that can send outbound messages and read secrets to do more than users expect.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill is designed to automatically transmit generated content to external messaging platforms on a schedule, but the user-facing description does not prominently warn about that external disclosure. Automatic scheduled exfiltration to third-party services increases privacy and operational risk, especially if future prompts or generated summaries include unintended sensitive content.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
Documenting a webhook variable without an explicit warning obscures that all included message content will be posted to the configured Feishu bot endpoint, which is an external service boundary. Users may paste internal or sensitive content into a system that forwards it off-platform without realizing the privacy and retention implications.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The top-level docstring states the script uses a Multi-Source-Research skill and integrates Tavily API, multi-source search, and social media monitoring. In practice, `multi_source_search` only makes a single Tavily API call, and there is no separate implementation for social media monitoring, academic-source retrieval, or other claimed integrations.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The manifest description says the digest references the World Monitor data source, but this file's actual retrieval logic uses Tavily search requests and fallback inline article data. World Monitor only appears as a formatted footer link/string, not as an actual upstream source in the implemented data collection path.

External Transmission

Medium
Category
Data Exfiltration
Content
# Tavily API configuration
TAVILY_API_KEY = os.environ.get('TAVILYAPIKEY', '')
TAVILY_API_URL = 'https://api.tavily.com/search'

# Multi-Source-Research configuration
MULTI_SOURCE_ENABLED = os.environ.get('MULTI_SOURCE_ENABLED', 'true').lower() == 'true'
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
This function's docstring explicitly lists social media monitoring, academic sources, and news aggregators as integrated inputs. However, the body only constructs one Tavily search request and processes its results, so the documentation overstates and contradicts the implemented behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
"max_results": num_results
        }
        
        response = requests.post(TAVILY_API_URL, json=payload, timeout=20)
        
        if response.status_code == 200:
            data = response.json()
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The script forces Chinese-language queries, Chinese formatted digest text, and Shenzhen-specific weather content throughout the generated output. There is no opt-in, configuration, or documented justification for restricting language/locale, which matches the policy concern for forced language or locale behavior.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This code sends the full generated digest to a WeCom webhook via HTTP, which is a network transmission of collected content to an external service. While the module docstring says it "pushes to QQ and Feishu," there is no explicit user warning near the sending logic about outbound transmission, recipient scope, or that message contents will leave the local system.

External Transmission

Medium
Category
Data Exfiltration
Content
}
        }
        
        response = requests.post(webhook_url, json=payload, timeout=10)
        
        if response.status_code == 200:
            result = response.json()
Confidence
87% confidence
Finding
This code posts the full digest to a destination URL entirely controlled by environment configuration, with no validation of scheme, host, or network location. In a compromised or misconfigured deployment, that allows silent redirection of outbound content to attacker infrastructure or internal services.

Natural-Language Policy Violations

Low
Confidence
83% confidence
Finding
The natural-language instructions and usage examples are presented exclusively in Chinese, with no indication that other languages are supported or that the user can choose their preferred language. Under the stated policy, forcing a specific language without user opt-in is a natural-language policy concern.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The name, description, title, and sample behavior all indicate a Chinese-language evening digest, but the document does not state that language is configurable or optional. Under the policy, forcing a specific language without user opt-in can be a natural-language policy violation unless the locale constraint is clearly justified.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The script reads WECOM_WEBHOOK_URL from the environment and uses it as a credential-bearing destination for message delivery. Although missing configuration is logged, there is no warning or comment explaining that this value is sensitive and controls external message delivery.

Static analysis

No suspicious patterns detected.