Back to skill

Security audit

财经热点新闻爬取与话题归纳系统

Security checks for vulnerabilities and agentic risk

Overview

This finance-news skill has a coherent purpose, but it performs under-scoped high-impact actions such as automatic recursive cleanup, credential reuse, browser-profile reuse, shell command execution, and automatic opening of generated HTML reports.

Review before installing. Use only in an isolated workspace with dedicated output directories, a dedicated low-quota API key, and no personal browser profile. Do not run it on machines where configurable cleanup paths could point at important files, and avoid opening generated HTML reports from untrusted scraped/model content until escaping and URL validation are fixed.

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 (5)

T09 · Insecure Skill Coding Practices

Error
Location
module4_report_generator.py:421
Finding
Stored HTML Injection Through Unsanitized News and Model Content<![CDATA[ ## Vulnerability Details **File Location**: `module4_report_generator.py:421-499` **Supporting Locations**: `module4_report_generator.py:1454-1457`, `main.py:555-572`, `main.py:699-721` **Vulnerability Type**: Stored HTML injection **Risk Level**: High ### Vulnerable Code ```python hot_news_html = "" for news in hot_news: keywords_html = ', '.join(news.get('keywords', ['财经', '热点'])) hot_news_html += f""" <div class="hot-news-item"> <div class="hot-news-rank">{news['rank']}</div> <div class="hot-news-content"> <div class="hot-news-title">{news['title']}</div> <div class="hot-news-meta"> <span class="source">{news['source']}</span> <span class="heat-score">热度: {news['score']:.1f}分</span> <span class="publish-time">{news['publish_time']}</span> </div> <div class="hot-news-summary">{news['summary']}</div> <div class="hot-news-keywords"> <strong>关键词:</strong> {keywords_html} </div> """ if news.get('url'): hot_news_html += f""" <div class="hot-news-link"> <a href="{news['url']}" target="_blank">查看原文</a> </div> """ deep_reports_html = "" for report_data in deep_reports: analysis_text = report_data.get('analysis', '无分析内容') analysis_html = "" for line in analysis_text.split('\n'): if line.strip(): analysis_html += f"<p>{line}</p>" deep_reports_html += f""" <div class="deep-analysis-item"> <div class="deep-analysis-rank">TOP{report_data['rank']}</div> <div class="deep-analysis-content"> <div class="deep-analysis-title">{report_data['title']}</div> <div class="deep-analysis-meta"> <span class="source">{report_data['source']}</span> <span class="score">评分: {report_data.get('score', 0):.1f}</span> </div> ...[truncated 3169 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every untrusted text value with `html.escape(value, quote=True)` before inserting it into HTML. 2. Use a template engine with automatic escaping enabled rather than constructing HTML with f-strings. 3. Validate links with `urllib.parse.urlparse` and permit only approved `https` hostnames. 4. Reject dangerous schemes such as `javascript:`, `data:`, `file:`, and `vbscript:`. 5. If limited rich text is required, sanitize it with a strict allowlist-based HTML sanitizer. 6. Add a restrictive Content Security Policy, such as disabling inline scripts and limiting network destinations. 7. Disable automatic report opening by default and require explicit user action. 8. Treat model-generated content as untrusted input and apply the same escaping and validation rules used for scraped data. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
summarize_utils.py:172
Finding
PowerShell Command Injection Through Configurable Summarization Values<![CDATA[ ## Vulnerability Details **File Location**: `summarize_utils.py:172-180` **Supporting Locations**: `module2_summarize_filtered.py:394-432` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```python if self.summarize_path.endswith('.ps1'): # PowerShell script cmd = [ 'powershell', '-Command', f'$env:OPENAI_API_KEY="{self.default_config["api_key"]}"; ' f'$env:OPENAI_BASE_URL="{self.default_config["api_base_url"]}"; ' f'& "{self.summarize_path}" --model {self.default_config["model"]} ' f'--length {length} "{temp_file_path}"' ] else: cmd = [ self.summarize_path, "--model", self.default_config["model"], "--length", length, temp_file_path ] ``` A second summarization path constructs PowerShell source from configurable values and invokes a shell: ```python if summarize_cmd.endswith('.ps1'): cmd = [ 'powershell', '-Command', f'& "{summarize_cmd}" --model ' f'{self.summarize_settings.get("model", "deepseek-chat")} ' f'--format json --maxTokens 600 "{temp_file}"' ] else: cmd = [ summarize_cmd, temp_file, '--model', self.summarize_settings.get('model', 'deepseek-chat'), '--format', 'json', '--maxTokens', '600' ] result = subprocess.run( cmd, capture_output=True, text=True, encoding='utf-8', timeout=self.summarize_settings.get('timeout_seconds', 60), env=env, shell=True ) ``` ### Technical Analysis API keys, API base URLs, model names, executable paths, and other values derived from configuration are interpolated into PowerShell command text without PowerShell-aware escaping or validation. A malicious configuration value containing quote characters, semicolons, command separators, subexpressions, or other PowerShell syntax can terminate its intended argument and append another command. Supplying c ...[truncated 1338 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not construct PowerShell source code from configurable values. 2. Keep secrets exclusively in the child-process environment rather than embedding them in command text. 3. Use `shell=False` for all subprocess invocations. 4. Invoke a fixed, verified executable with an argument list. 5. Resolve the executable with `shutil.which()` and verify that the resolved path is in an approved installation directory. 6. Validate model and length values against strict allowlists. 7. Reject unexpected control characters, quotes, separators, and PowerShell expressions in configuration. 8. If a PowerShell script must be supported, pass values through named parameters rather than concatenating them into a `-Command` string. 9. Apply least-privilege filesystem and process permissions to the Skill runtime. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
module2_summarize_filtered.py:741
Finding
Undisclosed Reading and Delegation of OpenClaw API Credentials<![CDATA[ ## Vulnerability Details **File Location**: `module2_summarize_filtered.py:741-755` **Supporting Location**: `module2_summarize_filtered.py:832-847` **Vulnerability Type**: Excessive credential access and unsafe secret delegation **Risk Level**: Medium ### Vulnerable Code ```python openclaw_config_path = Path.home() / '.openclaw' / 'openclaw.json' deepseek_api_key = None deepseek_base_url = None if openclaw_config_path.exists(): try: with open(openclaw_config_path, 'r', encoding='utf-8') as f: config = json.load(f) deepseek_config = ( config.get('models', {}) .get('providers', {}) .get('deepseek-com', {}) ) deepseek_api_key = deepseek_config.get('apiKey') deepseek_base_url = deepseek_config.get('baseUrl') ``` The recovered credential is delegated to an external executable: ```python env = os.environ.copy() if deepseek_api_key: env['OPENAI_API_KEY'] = deepseek_api_key if deepseek_base_url: env['OPENAI_BASE_URL'] = deepseek_base_url print( f" 环境变量: OPENAI_API_KEY=" f"{deepseek_api_key[:10]}..." f"{deepseek_api_key[-4:] if deepseek_api_key else '未设置'}" ) result = subprocess.run( cmd, capture_output=True, text=True, encoding='utf-8', timeout=60, shell=True, env=env ) ``` ### Technical Analysis The Skill automatically searches the user's home directory for the private OpenClaw configuration and extracts a provider API key. This access is not required for basic news crawling and is not clearly disclosed in the Skill instructions. The key is placed in the environment of a separately discovered `summarize` executable. Any compromised, replaced, or unexpected executable selected from `PATH` can read and retain the credential. The code also prints portions of the key, increasing exposure through console capture, logs, screenshots, or support transcripts. Reading a broad Agent configuration file violates lea ...[truncated 914 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic access to `~/.openclaw/openclaw.json`. 2. Require an explicit, dedicated environment variable or narrowly scoped secret-provider entry. 3. Obtain clear user consent before accessing any Agent-level configuration or credential. 4. Never print full or partial API keys. 5. Verify the exact summarizer executable path before passing credentials to it. 6. Use a dedicated low-privilege API key with restricted quota and provider permissions. 7. Avoid inheriting the complete parent environment; construct a minimal child-process environment. 8. Document all credential access and external API transmission in `SKILL.md`. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
module3_news_flash.py:100
Finding
Automatic Reuse of a Full Local Browser Profile<![CDATA[ ## Vulnerability Details **File Location**: `module3_news_flash.py:100-130` **Supporting Location**: `module3_news_flash.py:556-578` **Vulnerability Type**: Excessive browser-profile access **Risk Level**: Medium ### Vulnerable Code ```python config_cookie_path = self.wallstreet_config.get('cookie_path') if ( config_cookie_path and config_cookie_path.strip() and config_cookie_path != "你的cookie地址" ): self.cookie_path = config_cookie_path.strip() elif cookie_path: self.cookie_path = cookie_path else: default_cookie_path = ( "C:/Users/13620/AppData/Local/Tencent/" "QQBrowser/User Data" ) if Path(default_cookie_path).exists(): self.cookie_path = default_cookie_path else: self.cookie_path = None if self.cookie_path: if Path(self.cookie_path).exists(): print(f"[INFO] Cookie路径验证成功: {self.cookie_path}") else: self.cookie_path = None ``` The complete directory is then launched as a persistent browser context: ```python if self.cookie_path: context = await p.chromium.launch_persistent_context( user_data_dir=self.cookie_path, headless=True, viewport={'width': 1200, 'height': 800} ) browser = context.browser page = ( context.pages[0] if context.pages else await context.new_page() ) else: browser = await p.chromium.launch(headless=True) context = await browser.new_context( viewport={'width': 1200, 'height': 800} ) page = await context.new_page() await page.goto(target_url, timeout=timeout_ms) ``` ### Technical Analysis The crawler automatically detects and reuses a complete QQ Browser user-data directory. A full browser profile can contain cookies, authenticated sessions, browsing state, extensions, cached data, and site permissions unrelated to the target news feed. The task only requires access to a public financial-news page, so launching the full profile exceeds least ...[truncated 1398 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic discovery and use of the hard-coded QQ Browser profile. 2. Use a new, isolated temporary browser context by default. 3. If authentication is required, export only site-specific Playwright storage state after explicit user consent. 4. Create a dedicated automation profile containing no unrelated browsing data. 5. Enforce an allowlist containing only the required HTTPS hostname. 6. Require explicit confirmation before loading any existing browser profile. 7. Prevent concurrent use of a profile by the user's normal browser and the automation process. 8. Document exactly which cookies or storage entries are required and why. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
main.py:597
Finding
Unrestricted Recursive Deletion Through Configurable Directories<![CDATA[ ## Vulnerability Details **File Location**: `main.py:597-653` **Supporting Location**: `main.py:699-721` **Vulnerability Type**: Arbitrary recursive file deletion **Risk Level**: High ### Vulnerable Code ```python temp_dirs = [] if self.config: system_settings = self.config.get('system_settings', {}) temp_dir = system_settings.get( 'temp_dir', 'c:/SelfData/claw_temp/temp' ) temp_dirs.append(Path(temp_dir)) reports_dir = system_settings.get( 'reports_dir', 'c:/SelfData/claw_temp/reports' ) temp_dirs.append(Path(reports_dir)) else: temp_dirs = [ Path("c:/SelfData/claw_temp/temp"), Path("c:/SelfData/claw_temp/reports"), ] deleted_count = 0 total_size = 0 for temp_dir in temp_dirs: if not temp_dir.exists(): continue for file_path in temp_dir.rglob("*"): if file_path.is_file(): try: mtime = datetime.fromtimestamp( file_path.stat().st_mtime ) if mtime < cutoff_time: file_size = file_path.stat().st_size total_size += file_size deleted_count += 1 file_path.unlink() except Exception as e: self.log( f"[WARNING] 无法处理文件 {file_path}: {e}", "WARNING" ) for temp_dir in temp_dirs: if temp_dir.exists(): for dir_path in sorted( temp_dir.rglob("*"), reverse=True ): if dir_path.is_dir() and not any(dir_path.iterdir()): try: dir_path.rmdir() except: pass ``` The cleanup runs automatically after successful report generation: ```python if module4_success: html_report_path = ( module4_result.get('metadata', {}) .get('html_report') ) if html_report_path and os.pat ...[truncated 1690 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict cleanup to fixed, application-owned subdirectories. 2. Resolve paths with `Path.resolve()` and verify that each target remains beneath a trusted root. 3. Explicitly reject filesystem roots, drive roots, home directories, and directories outside the Skill workspace. 4. Refuse to clean symlinked directories or paths that traverse through symlinks. 5. Require a Skill-specific marker file in every directory eligible for cleanup. 6. Change automatic deletion to an explicit opt-in operation. 7. Present a dry-run list and require confirmation before deleting files. 8. Retain only files matching strict Skill-owned filename patterns rather than deleting every old file. 9. Prefer moving files to a recoverable quarantine or recycle location before permanent deletion. 10. Log full canonical paths and preserve an auditable deletion manifest. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (78)

Missing User Warnings

High
Confidence
98% confidence
Finding
This code automatically triggers deletion of files older than 24 hours from temp and reports locations without confirmation. In the context of a news-crawling/reporting skill, destructive file operations are not essential and can lead to silent loss of user data or prior outputs if directory scope is broader than intended.

Context-Inappropriate Capability

High
Confidence
91% confidence
Finding
The module executes an external command-line scraping tool even though its documented purpose is just to crawl news sites. Spawning an external binary materially expands the execution surface: behavior depends on PATH resolution and the installed tool, which could be replaced, tampered with, or have unexpected capabilities beyond simple HTTP fetching.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
- impact: 影响分析(50-100字)
- outlook: 未来展望(50-100字)"""
        
        return prompt
    
    def summarize_with_cli(self, prompt: str) -> Optional[Dict[str, Any]]:
        """使用summarize CLI进行归纳(从配置文件读取API key和URL)"""
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
- impact: 影响分析(50-100字)
- outlook: 未来展望(50-100字)"""
        
        return prompt
    
    def summarize_with_cli(self, prompt: str) -> Optional[Dict[str, Any]]:
        """使用summarize CLI进行归纳(从配置文件读取API key和URL)"""
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# 检查summarize命令是否可用
            try:
                import subprocess
                result = subprocess.run(['summarize', '--version'], 
                                       capture_output=True, text=True, timeout=5, shell=True)
                summarize_available = result.returncode == 0
            except Exception:
Confidence
95% confidence
Finding
The tool invocation uses shell=True unnecessarily for a simple version check. In a skill context that may run on user systems, this increases the risk of executing an attacker-controlled binary from PATH or invoking the shell in ways that are hard to reason about.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
print(f"    环境变量: OPENAI_API_KEY={deepseek_api_key[:10]}...{deepseek_api_key[-4:] if deepseek_api_key else '未设置'}")
                    print(f"               OPENAI_BASE_URL={deepseek_base_url}")
                    
                    result = subprocess.run(
                        cmd,
                        capture_output=True,
                        text=True,
Confidence
96% confidence
Finding
The deep-analysis tool call accepts externally sourced URLs and runs an external summarizer with credential-bearing environment variables. In this skill context, that is more dangerous because article content can be attacker-influenced, causing untrusted data to reach a powerful external tool and remote service without clear user consent.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# 计算超时时间(秒),使用配置的超时时间
                timeout_seconds = timeout_ms / 1000 + 5  # 增加5秒缓冲
                
                result = subprocess.run(
                    cmd, 
                    shell=True, 
                    capture_output=True,
Confidence
98% confidence
Finding
Using subprocess.run with shell=True on a composed command string is a classic command-injection sink. In this skill context, the URL ultimately comes from configuration or inputs, so a maliciously crafted value could escape the intended command and execute arbitrary commands on the host.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
print(f"[INFO] 执行summarize命令: {' '.join(cmd[:3])}... [文件:{temp_file_path}]")
                
                # 添加API密钥环境变量
                env = os.environ.copy()
                env['OPENAI_API_KEY'] = self.default_config['api_key']
                env['OPENAI_BASE_URL'] = self.default_config['api_base_url']
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
print(f"[INFO] 执行summarize命令: {' '.join(cmd[:3])}... [文件:{temp_file_path}]")
                
                # 添加API密钥环境变量
                env = os.environ.copy()
                env['OPENAI_API_KEY'] = self.default_config['api_key']
                env['OPENAI_BASE_URL'] = self.default_config['api_base_url']
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
print(f"[INFO] 执行summarize命令: {' '.join(cmd[:3])}... [文件:{temp_file_path}]")
                
                # 添加API密钥环境变量
                env = os.environ.copy()
                env['OPENAI_API_KEY'] = self.default_config['api_key']
                env['OPENAI_BASE_URL'] = self.default_config['api_base_url']
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The title, trigger phrases, and primary invocation guidance are entirely in Chinese, and the trigger conditions require specific Chinese phrases. The file does not offer alternative language support or state that the skill is intentionally restricted to a Chinese-language or region-specific context, which can violate language/locale policy requirements.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README documents execution steps that perform live network scraping and explicitly references Playwright-based browser automation, but it does not present a prominent user-facing warning before execution that the skill will access external sites and may automatically launch a browser. In an agent-skill context, missing disclosure can lead to unexpected outbound network activity and UI side effects, reducing informed consent and increasing operational risk.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The documentation lists concrete output directories on the local filesystem where scraped data, summaries, logs, and reports are written, but it does not clearly warn users that running the skill will persist data locally. This can cause unanticipated storage of potentially sensitive browsing-derived content on disk, especially in shared or managed environments.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill advertises 'automatic cleanup of old data' but does not clearly disclose what files or directories may be deleted, under what conditions, or how the cleanup scope is constrained. In an agent-executed workflow that runs shell and Python commands, undocumented deletion behavior can cause unintended data loss, especially if configuration mistakes or path-handling bugs broaden the cleanup target.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 如果scrapling_util不可用,检查命令行版本
        try:
            result = subprocess.run(["scrapling", "--help"], capture_output=True, text=True, timeout=5)
            return result.returncode == 0 or "Usage:" in result.stdout or "Usage:" in result.stderr
        except:
            return False
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
tmp_file = tmp.name
            
            cmd = ['scrapling', 'extract', 'fetch', url, tmp_file, '--wait', str(wait_time), '--timeout', '15000']
            result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', timeout=30)
            
            if result.returncode == 0:
                with open(tmp_file, 'r', encoding='utf-8') as f:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 如果scrapling_util不可用,检查命令行版本
        try:
            result = subprocess.run(
                ["scrapling", "--help"],
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 如果scrapling_util不可用,检查命令行版本
        try:
            result = subprocess.run(
                ["scrapling", "--help"],
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The function makes outbound network requests to a third-party site and explicitly uses a stealth scraping mechanism, but the code does not provide a meaningful disclosure that external requests will be made or that request metadata may be transmitted. The top-level description states that scrapling is used, but it does not warn users about the network/privacy implications of contacting the remote site.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
            
            # 执行命令
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
            
            # 执行命令
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
When live scraping fails, the crawler silently returns fabricated sample articles that look like real output. This can mislead downstream systems or users into acting on false data, undermining integrity and potentially contaminating automation, analytics, or trading workflows that expect authentic JRJ news.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This code persistently saves scraped article contents, URLs, metadata, and generated reports to disk, but there is no confirmation prompt before writing and no warning in comments/docstrings that user data will be stored locally by default. For a code file, file writes should have some form of disclosure unless clearly covered by the skill description; here the docstring describes crawling but does not warn about saving outputs to local storage.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The file’s docstrings, log messages, CLI description, and user-facing output are entirely in Chinese, which imposes a language choice on users without opt-in or alternatives. The policy calls for flagging language or locale constraints unless the skill offers a choice or clearly justifies the restriction.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
The docstring for run_module3 states '只支持playwright模式' (only supports playwright mode), yet the top-level run method is declared with module3_mode='single' and forwards that value to run_module3 at L680. This is an active contradiction between the documented intent and actual callable behavior, even if CLI parsing later defaults to playwright.

Static analysis

No suspicious patterns detected.