Back to skill

Security audit

A Stock Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed A-share paper-trading skill with local data storage and scripts, but it has implementation weaknesses users should understand before relying on results.

Install only if you are comfortable with a China A-share paper-trading tool that fetches market data from East Money and writes a local SQLite database under ~/.openclaw/workspace/a-stock/. Treat outputs as educational, not investment advice, and be aware that malformed CLI inputs or tampered HTTP market data can corrupt or mislead the simulated results.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/backtest.py:26
Finding
SQL Injection in Stock Data Lookup<![CDATA[ ## Vulnerability Details **File Location**: `scripts/backtest.py`, lines 26-29 **Vulnerability Type**: SQL injection through direct string interpolation **Risk Level**: Medium ### Vulnerable Code ```python df = pd.read_sql( f"SELECT * FROM daily_data WHERE code='{code}' ORDER BY date", conn ) ``` ### Technical Analysis The `code` value originates from the command-line `--stock` argument and is interpolated directly into an SQL statement. No parameter binding or strict stock-code validation is applied. An input containing SQL syntax can alter the `WHERE` clause. For example, a value equivalent to `' OR '1'='1` can cause the query to return records for every stock rather than only the requested stock. More advanced expressions may use `UNION` clauses to read other compatible data from the same SQLite database, subject to SQLite and result-column constraints. The database API may prevent execution of multiple statements, which limits some destructive payloads, but it does not prevent modification of the semantics of the existing `SELECT` statement. ### Attack Path 1. An attacker or untrusted caller obtains the ability to invoke `backtest.py` with a controlled `--stock` value. 2. The supplied value is passed to `run_backtest()` and then `load_data()`. 3. `load_data()` places the value directly inside the SQL query. 4. SQLite evaluates the injected syntax as part of the query. 5. Records outside the intended stock selection may be returned and processed by the backtest. 6. The resulting report can expose unintended database content or present misleading trading results. Example invocation: ```bash python scripts/backtest.py --stock "' OR '1'='1" ``` ### Impact Assessment Exploitation occurs with the privileges of the local process and is limited to the SQLite database at `~/.openclaw/workspace/a-stock/data.db`. A successful attacker may: - Read records for stocks other than the requested stock. - Potentially retrieve data from other d ...[truncated 337 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use a parameterized query rather than embedding the value in SQL: ```python df = pd.read_sql( "SELECT * FROM daily_data WHERE code=? ORDER BY date", conn, params=(code,), ) ``` Apply defense-in-depth validation before accessing the database: ```python import re if not re.fullmatch(r"\d{6}", code): raise ValueError("Stock code must contain exactly six digits") ``` Additional hardening measures include: 1. Centralize stock-code normalization and validation so all scripts use identical rules. 2. Return a controlled error for invalid input rather than forwarding it to SQLite. 3. Add automated tests using quotes, SQL comments, Boolean expressions, and `UNION` payloads. 4. Grant the process only the filesystem permissions required to access its own database. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/simulate.py:75
Finding
Negative Transaction Values Can Corrupt Simulated Account State<![CDATA[ ## Vulnerability Details **File Location**: `scripts/simulate.py`, lines 75-151 and 207-210 **Vulnerability Type**: Missing numeric range validation and transaction integrity enforcement **Risk Level**: Medium ### Vulnerable Code ```python def buy(conn, code, name, price, shares): c = conn.cursor() cost = price * shares c.execute("SELECT cash FROM account WHERE id=1") cash = c.fetchone()[0] if cash < cost: print(f"资金不足! 需要 {cost:.2f}, 当前 {cash:.2f} / Insufficient funds! Need {cost:.2f}, have {cash:.2f}") return False # 扣款 / Deduct cash c.execute("UPDATE account SET cash = cash - ? WHERE id=1", (cost,)) # 更新持仓 / Update position c.execute("SELECT * FROM positions WHERE code=?", (code,)) pos = c.fetchone() if pos: # 追加持仓 / Add to position old_shares, old_cost = pos[2], pos[3] new_shares = old_shares + shares new_cost = (old_shares * old_cost + cost) / new_shares c.execute("UPDATE positions SET shares=?, cost=? WHERE code=?", (new_shares, new_cost, code)) else: # 新建持仓 / New position c.execute("INSERT INTO positions (code, name, shares, cost, buy_date) VALUES (?, ?, ?, ?, ?)", (code, name, shares, price, datetime.now().strftime("%Y-%m-%d"))) ``` ```python def sell(conn, code, price, shares): c = conn.cursor() c.execute("SELECT shares, cost, name FROM positions WHERE code=?", (code,)) pos = c.fetchone() if not pos or pos[0] < shares: print(f"持仓不足! / Insufficient position!") return False # 更新持仓 / Update position new_shares = pos[0] - shares if new_shares == 0: c.execute("DELETE FROM positions WHERE code=?", (code,)) else: c.execute("UPDATE positions SET shares=? WHERE code=?", (new_shares, code)) # 收款 / Add revenue revenue = price * shares c.execute("UPDATE account SET cash = cash + ...[truncated 2862 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Validate all transaction inputs before reading or modifying database state: ```python import math def validate_order(price, shares): if not isinstance(shares, int) or shares <= 0: raise ValueError("Shares must be a positive integer") if not isinstance(price, (int, float)) or not math.isfinite(price) or price <= 0: raise ValueError("Price must be a positive finite number") if shares % 100 != 0: raise ValueError("Shares must comply with the required trading-lot size") ``` Call this validation at the start of both `buy()` and `sell()`. Also: 1. Reject values that exceed documented transaction and account limits. 2. Replace truthiness-based CLI checks with explicit `is not None` checks followed by validation. 3. Wrap each operation in an explicit transaction and roll it back on any validation or arithmetic error. 4. Confirm that `new_shares` is positive and that calculated cash, cost, revenue, and PnL values are finite. 5. Add database constraints such as: ```sql CHECK (shares > 0) CHECK (cost > 0) CHECK (cash >= 0) ``` 6. Use fixed-point decimal arithmetic or integer currency units instead of binary floating-point values for financial calculations. 7. Add regression tests covering zero, negative, non-finite, extremely large, and non-lot-compliant values. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/fetch_daily.py:58
Finding
Market Data Retrieved over Unencrypted HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_daily.py`, lines 58-70 **Vulnerability Type**: Cleartext transport without server authentication or response integrity **Risk Level**: Low ### Vulnerable Code ```python base_url = f"http://push2his.eastmoney.com/api/qt/stock/kline/get" params = { "secid": f"1.{code}", # 1=上海 0=深圳 / 1=Shanghai 0=Shenzhen "fields1": "f1,f2,f3,f4,f5,f6", "fields2": "f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61", "klt": "101", # 日K / Daily K "fqt": "1", # 前复权 / Forward adjusted "end": "20500101", "lmt": days, } try: resp = requests.get(base_url, params=params, timeout=10) data = resp.json() ``` ### Technical Analysis The East Money API is accessed using plaintext HTTP. HTTP does not authenticate the remote server and does not protect the response against modification in transit. A network-positioned attacker, compromised proxy, or malicious access point can intercept the request and replace the JSON response. The script immediately parses the response and, when expected keys are present, stores the supplied K-line values in SQLite. It does not call `raise_for_status()` or perform robust schema, date, ordering, or numeric-range validation. No credentials are transmitted by this request, so the primary concern is data integrity rather than secret disclosure. ### Attack Path 1. A user runs `fetch_daily.py` on a network controlled or observable by an attacker. 2. The script sends an HTTP request to the market-data host. 3. The attacker intercepts or redirects the cleartext connection. 4. The attacker returns syntactically valid JSON containing manipulated K-line records. 5. `resp.json()` and `parse_klines()` accept the forged values. 6. `save_data()` persists the poisoned market data to the local SQLite database. 7. Subsequent backtests use the manipulated prices and generate misleading trading results. ### Impact Assessment A successful network attacker can: - Poiso ...[truncated 516 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use the provider's verified HTTPS endpoint: ```python base_url = "https://push2his.eastmoney.com/api/qt/stock/kline/get" resp = requests.get(base_url, params=params, timeout=10) resp.raise_for_status() data = resp.json() ``` Do not disable TLS certificate verification. In addition: 1. Restrict redirects or verify that the final destination remains an approved HTTPS host. 2. Validate the response content type and impose a reasonable response-size limit. 3. Verify the complete response schema before processing records. 4. Require the expected number of fields in each K-line entry. 5. Validate dates and enforce sensible numeric ranges and invariants, such as `low <= open/close <= high`. 6. Reject non-finite numeric values and implausible record counts. 7. Perform database writes only after the entire response has passed validation. 8. Log validation failures without persisting partial or untrusted data. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The skill metadata and content describe a broad trading system with fetching, backtesting, and persistent account state, but the manifest does not clearly bind those behaviors to declared resources or permissions. This mismatch makes review and enforcement harder and can hide undeclared side effects such as local state mutation or external network access under an innocuous documentation layer.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill describes network-based data collection from external finance sites and local SQLite persistence, but it does not declare any explicit tool scope or permissions. Undeclared network and storage capabilities reduce transparency and prevent effective policy enforcement, which can allow a skill to access external data or write local state in ways the host did not authorize.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The `code` parameter comes from CLI input and is interpolated directly into the SQL string with an f-string, making the query vulnerable to SQL injection. An attacker can manipulate the WHERE clause or otherwise alter query behavior, which is especially relevant because this script reads from a local SQLite database in a trading workflow where integrity of backtest inputs matters.

Session Persistence

Medium
Category
Rogue Agent
Content
def save_data(conn, data):
    """
    保存数据到数据库 / Save data to database
    使用INSERT OR REPLACE防止重复 / Use INSERT OR REPLACE to avoid duplicates
    """
    c = conn.cursor()
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.

Natural-Language Policy Violations

Low
Confidence
85% confidence
Finding
A language or locale policy violation applies to all file types when the skill effectively forces a specific language without offering a choice or documenting the constraint. This README presents all usage instructions only in Chinese, with no bilingual option or note that the skill is region/language-specific.

Missing User Warnings

Low
Confidence
77% confidence
Finding
This is a markdown file, so missing user-facing warnings about actions that could affect user data or system integrity should be flagged when omitted. The section provides buy and sell commands for the simulator but does not explicitly warn that these commands change simulated holdings/balance or clarify that they are non-production paper trades.

Natural-Language Policy Violations

Low
Confidence
70% confidence
Finding
The skill name and description are entirely in Chinese and target A-share trading, but the metadata does not explicitly state that the skill is intended only for Chinese-speaking users or offer any language choice. This can create a locale-policy concern if the broader environment expects skills not to force a language without opt-in or documented regional scope.

Vague Triggers

Low
Confidence
85% confidence
Finding
The manifest description lists broad functions like data scraping, storage, backtesting, and simulated trading, but does not define when the skill should be invoked or what specific requests should trigger it. In a manifest file, the absence of explicit trigger boundaries or examples can make activation overly broad and increase the chance of unintended invocation.

Natural-Language Policy Violations

Low
Confidence
83% confidence
Finding
Natural-language content throughout the file is primarily Chinese, with English only as secondary inline translation, and there is no indication that users can select a preferred language or that the skill is limited to a Chinese-speaking context. This can conflict with organizational language/locale policy when a skill implicitly forces one language without opt-in.

Natural-Language Policy Violations

Low
Confidence
77% confidence
Finding
The script's user-facing description and runtime messages are bilingual but centered on a China-specific stock market workflow ('A股' / A-share, East Money) without offering any language or locale selection. Under the stated policy, forcing a specific language or locale without opt-in can be a policy concern when exposed as a general-purpose skill.

Missing User Warnings

Low
Confidence
81% confidence
Finding
This code initializes a database under the user's home directory and saves fetched records into it. Although the behavior is visible in docstrings and a post-action print statement reports saved records, there is no clear pre-action user disclosure at the point of execution that the script will create or modify ~/.openclaw/workspace/a-stock/data.db.

Static analysis

No suspicious patterns detected.