Back to skill

Security audit

股票列表自动管理

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its stated stock-list purpose, but it uses hardcoded local paths, writes trading-list state by default, and has an unchecked image identifier that can read unintended JPG files.

Review before installing. Use it only if you are comfortable with it writing to the hardcoded stock-list path, and prefer validating or fixing the image UUID/path handling before using it with untrusted inputs.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (1)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/update_stock_list.py:17
Finding

Unrestricted Path Traversal in Image Selection

Content
View full analysis

Vulnerability Details

File Location: scripts/update_stock_list.py, lines 17–18
Vulnerability Type: Path traversal leading to unauthorized local file access
Risk Level: Medium

Vulnerable Code

python
def ocr_stocks(uuid: str) -> list:
    """Use RapidOCR to recognize stocks in an image."""
    try:
        from rapidocr_onnxruntime import RapidOCR
        import cv2
        
        img_path = f"/Users/wy/.openclaw/media/inbound/{uuid}.jpg"
        img = cv2.imread(img_path)
        if img is None:
            print(f"Image does not exist: {img_path}")
            return []

Technical Analysis

The uuid command-line value is interpolated directly into a filesystem path without format validation, path normalization, or a containment check. Although the application appends .jpg, an attacker can include ../ path segments in the value to escape the intended /Users/wy/.openclaw/media/inbound/ directory.

cv2.imread() consequently attempts to read the attacker-selected path using the permissions of the process running the Skill. The attack is limited to files that OpenCV can decode as images and whose resulting path ends in .jpg, but it still violates the intended directory boundary.

Attack Path

  1. An attacker or untrusted caller gains control of the positional uuid argument supplied to update_stock_list.py.
  2. The attacker supplies a traversal value such as ../../../../tmp/target.
  3. The application constructs a path similar to: /Users/wy/.openclaw/media/inbound/../../../../tmp/target.jpg.
  4. The operating system resolves the traversal segments, causing cv2.imread() to access /tmp/target.jpg rather than a file in the inbound media directory.
  5. RapidOCR processes the external image. Recognized six-digit strings are displayed as stock codes and may be written to manual_stock_list.json unless --dry-run is used.

Impact Assessment

The issue ...[truncated 548 chars]

Remediation
View remediation

Remediation Suggestions

  • Validate the argument as an actual UUID before constructing the path, rejecting path separators, traversal sequences, and malformed identifiers.
  • Resolve both the trusted base directory and candidate path, then verify that the candidate remains beneath the trusted directory.
  • Reject symlinks where the deployment model permits untrusted users to create files in the inbound directory.
  • Avoid exposing full local filesystem paths in user-facing error messages.
  • Add tests covering absolute paths, ../ traversal, encoded separators, malformed UUIDs, and symlink escapes.

Example hardened implementation:

python
from pathlib import Path
from uuid import UUID

INBOUND_DIR = Path("/Users/wy/.openclaw/media/inbound").resolve()

def trusted_image_path(value: str) -> Path:
    canonical_uuid = str(UUID(value))
    candidate = (INBOUND_DIR / f"{canonical_uuid}.jpg").resolve()

    try:
        candidate.relative_to(INBOUND_DIR)
    except ValueError:
        raise ValueError("Image path is outside the inbound directory")

    if not candidate.is_file() or candidate.is_symlink():
        raise ValueError("Image does not exist or is not an allowed file")

    return candidate
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding

代码整体主目的与声明大体一致:从图片OCR识别股票并加入股票池,包含去重、数量上限和保存JSON。但存在实质性描述不准确之处。最明显的是声明称会识别股票代码、名称、涨幅、价格,而代码实际上只识别6位数字代码和可能的名称,完全没有解析或存储涨幅、价格。此外,保存位置不是泛指的 manual_stock_list.json,而是一个特定用户目录下的绝对路径,这属于资源访问上的更具体且未声明的行为。最后,虽然实现了30只上限裁剪,但其FIFO逻辑并不严格,因为 add_time 只记录到分钟,并在排序时使用 code 作为辅助键,可能导致同一分钟内加入的股票移除顺序偏离真实加入顺序。因此应判定为描述与实际行为存在不匹配。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

代码的整体目的与声明大体相关,确实是在从图片OCR识别股票并写入股票池 JSON,同时实现了去重和数量上限控制。但声明中核心功能包含识别股票代码、名称、涨幅、价格,而实际代码只识别6位数字代码;中文名称部分仅有注释和占位逻辑,没有保存结果;涨幅和价格完全未处理。此外,FIFO 的描述和实现存在偏差:函数注释写“保留最新的”,但实际返回 stocks[:limit],在追加新股票到尾部的情况下会保留更早的条目而丢弃后加入的条目,不符合通常FIFO淘汰旧项的预期。因此描述对实际能力有明显夸大,构成不匹配。

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill performs file read/write operations on local paths but does not declare any tool scope or permissions boundary. This is dangerous because it obscures the skill's data access behavior, preventing proper review and increasing the chance of unintended modification of local files when the skill is invoked.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill describes automatically persisting extracted data to a local JSON file without warning the user that local state will be modified. In this context, the skill processes user-provided images and silently writes derived data into a stock list, which can alter downstream trading or analysis workflows without explicit user confirmation or audit visibility.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manifest explicitly says the skill recognizes stock code, name, percentage change, and price from images. In the implementation, ocr_stocks only looks for 6-digit numeric text as a stock code and optionally takes the next non-numeric OCR token as the name; it never parses or stores 涨幅 or 价格.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manifest describes recognition of multiple fields from the image, including stock names,涨幅, and price. In practice, the loop only appends entries when the OCR text is a 6-digit numeric code, while potential names are explicitly not processed and no logic exists for涨幅 or price extraction.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The docstring says '超过30只时保留最新的', implying older items should be discarded and newer additions retained. However, returning stocks[:limit] preserves the earliest items in the list, which contradicts the stated FIFO intent and likely drops newer stocks instead.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The visible natural-language description is entirely in Chinese and implies the skill is intended to operate in that language, but it does not offer a language choice or explain a necessary locale-specific constraint. This can violate language/locale policy when users are not given opt-in or alternatives.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code file contains natural-language strings entirely in Chinese in the module docstring and later user-facing messages, with no indication that the skill is region-specific or that users can choose another language. Under the policy rule for language/locale, forcing a specific language without opt-in is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The docstring and comments describe FIFO behavior, implying removal in insertion order. The code does not preserve insertion order directly; instead it sorts by add_time formatted only as %H:%M and then by stock code, which can reorder entries added on different days or within the same minute, contradicting the stated FIFO intent.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.