Back to skill

Security audit

A股模拟账户交易

Security checks for vulnerabilities and agentic risk

Overview

The skill coherently implements a local A-share paper-trading simulator with disclosed local storage, service controls, and optional startup persistence.

Install only if you want a local paper-trading service. Keep it bound to 127.0.0.1, avoid exposing the port to a network, and use the uninstall-launchd/stop commands if you no longer want it running at login or in the background.

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/paper_trading/service.py:64
Finding

Unauthenticated Trading API Can Be Exposed on Non-Loopback Interfaces

Content
View full analysis
None: parser = argparse.ArgumentParser(description="Paper trading backend service") parser.add_argument("--host", default=DEFAULT_HOST) parser.add_argument("--port", type=int, default=DEFAULT_PORT) parser.add_argument("--db-path", default=str(get_default_db_path())) parser.add_argument("--match-interval", type=int, default=5) parser.add_argument("--valuation-interval", type=int, default=10) parser.add_argument("--idle-valuation-interval", type=int, default=300) args = parser.parse_args() ensure_runtime_dir() engine = PaperTradingEngine(db_path=args.db_path) print(f"paper trading service listening on http://{args.host}:{args.port} db={args.db_path}") run_server(args.host, args.port, engine, match_interval=args.match_interval, valuation_interval=args.valuation_interval, idle_valuation_interval=args.idle_valuation_interval) ``` ```python def do_POST(self) -> None: try: payload = self._payload() path = urlparse(self.path).path if path == "/accounts": return self._json({"status": "success", "data": self.engine.create_account(payload["account_id"], float(payload.get("initial_cash", 100000.0)))}, code=201) if path.startswith("/accounts/") and path.endswith("/reset"): return self._json({"status": "success", "data": self.engine.reset_account(path.split("/")[2], payload.get("initial_cash"))}) if path == "/accounts/default": return self._json({"status": "success", "data": self.engine.set_default_account(payload["account_id"])}) if path.startswith("/accounts/") and path.endswith("/cash-adjust"): account_id = path.split("/")[2] re ...[truncated 4756 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/paper_trading/service.py:143
Finding

Unbounded HTTP Request Bodies Permit Resource Exhaustion

Content
View full analysis
Dict[str, Any]: length = int(self.headers.get("Content-Length", "0") or "0") raw = self.rfile.read(length) if length else b"{}" return json.loads(raw.decode("utf-8")) if raw else {} ``` ### Technical Analysis The server directly converts the client-controlled `Content-Length` header to an integer and attempts to read that many bytes. No maximum request size is enforced. Because the service uses `ThreadingHTTPServer`, multiple clients can cause several worker threads to wait for or allocate large request bodies concurrently. A client may either transmit large bodies to consume memory and JSON parsing resources or declare a large body and transmit it slowly to retain worker threads. The handler does not establish an application-level request-body timeout or concurrency limit. The risk is primarily local under the default loopback configuration, but it becomes remotely exploitable when the configurable host is bound to a reachable interface. ### Attack Path 1. The attacker obtains network access to the service. 2. The attacker opens multiple concurrent connections. 3. Each connection sends a POST request with an excessively large `Content-Length`. 4. The attacker either sends large JSON bodies or transmits them slowly. 5. Server threads block in `self.rfile.read(length)` or allocate and parse attacker-controlled data. 6. Memory, CPU, file descriptors, or worker capacity is exhausted, degrading or terminating the service. ### Impact Assessment Successful exploitation can make the paper-trading service slow or unavailable and can interrupt matching, valuation snapshots, CLI operations, and backtests. Severe memory pressure may terminate the Python process. The vulnerability does not grant access to operating-system privi ...[truncated 99 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/paper_trading_ctl.py:100
Finding

Unverified PID File Can Cause Termination of an Unrelated Process

Content
View full analysis
bool: pid = read_pid(pid_path) stopped = False if pid and is_pid_alive(pid): os.kill(pid, signal.SIGTERM) deadline = time.time() + 8 while time.time() < deadline and is_pid_alive(pid): time.sleep(0.2) if is_pid_alive(pid): os.kill(pid, signal.SIGKILL) stopped = True if pid_path.exists(): pid_path.unlink() return stopped or not healthcheck(host, port) ``` ### Technical Analysis The controller assumes that a live PID stored in the PID file belongs to the paper-trading service. It verifies only that some process with that PID exists by using `os.kill(pid, 0)`. It does not verify the executable path, command line, process start time, ownership beyond operating-system signal enforcement, or a service-specific process fingerprint. If the service exits without cleaning up its PID file, the operating system may later reuse that PID for an unrelated process. The next `stop` invocation then sends SIGTERM and potentially SIGKILL to that process. A local actor who can modify a user-selected or insufficiently protected PID file could produce the same outcome. ### Attack Path 1. The paper-trading service terminates unexpectedly and leaves a stale PID file, or a local actor modifies the PID file. 2. The PID is assigned to another process owned by the same user. 3. The user executes `paper_trading_ctl.py stop`. 4. `is_pid_alive` confirms only that the PID exists. 5. The controller sends SIGTERM to the unrelated process. 6. If the process remains alive for eight seconds, the controller sends SIGKILL. ### Impact Assessment The controller can terminate an unrelated process that the invoking user has permission ...[truncated 372 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (38)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The README explicitly instructs users to run a long-lived local service, persist trading data to a user-level SQLite database, and optionally install an auto-start launchd job, but it does not clearly warn that these actions modify the user's system state and create background persistence. In an agent-skill context, this is security-relevant because users may treat setup steps as harmless documentation while unknowingly enabling a persistent local daemon and startup entry.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill instructs the agent to run local Python services, use shell commands, access local files for SQLite state, and expose an HTTP service, but it does not declare any explicit tool/permission scope. That mismatch weakens sandboxing and review because a caller may not realize the skill can execute commands, read/write user data directories, and open a local network listener.

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 · scripts/full_function_smoke_check.py (reported line 182)May include surrounding context.

python
"--idle-valuation-interval",
            "1",
        ]
        proc = subprocess.Popen(service_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        try:
            deadline = time.time() + 15
            while time.time() < deadline:

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 · scripts/full_function_smoke_check.py (reported line 197)May include surrounding context.

python
raise AssertionError("service did not start")

            cli = [sys.executable, str(SCRIPT_DIR / "paper_trade_cli.py"), "--base-url", base]
            subprocess.run(cli + ["create-account", "svc", "--cash", "200000"], check=True, capture_output=True, text=True)
            subprocess.run(cli + ["set-default-account", "svc"], check=True, capture_output=True, text=True)
            default_out = subprocess.run(cli + ["show-default-account"], check=True, capture_output=True, text=True).stdout
            _assert("svc" in default_out, "default account CLI failed")

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 · scripts/full_function_smoke_check.py (reported line 198)May include surrounding context.

python
cli = [sys.executable, str(SCRIPT_DIR / "paper_trade_cli.py"), "--base-url", base]
            subprocess.run(cli + ["create-account", "svc", "--cash", "200000"], check=True, capture_output=True, text=True)
            subprocess.run(cli + ["set-default-account", "svc"], check=True, capture_output=True, text=True)
            default_out = subprocess.run(cli + ["show-default-account"], check=True, capture_output=True, text=True).stdout
            _assert("svc" in default_out, "default account CLI failed")
            subprocess.run(cli + ["add-cash", "svc", "10000"], check=True, capture_output=True, text=True)

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 · scripts/full_function_smoke_check.py (reported line 199)May include surrounding context.

python
cli = [sys.executable, str(SCRIPT_DIR / "paper_trade_cli.py"), "--base-url", base]
            subprocess.run(cli + ["create-account", "svc", "--cash", "200000"], check=True, capture_output=True, text=True)
            subprocess.run(cli + ["set-default-account", "svc"], check=True, capture_output=True, text=True)
            default_out = subprocess.run(cli + ["show-default-account"], check=True, capture_output=True, text=True).stdout
            _assert("svc" in default_out, "default account CLI failed")
            subprocess.run(cli + ["add-cash", "svc", "10000"], check=True, capture_output=True, text=True)
            account_out = subprocess.run(cli + ["show-account", "svc"], check=True, capture_output=True, text=True).stdout

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 · scripts/full_function_smoke_check.py (reported line 201)May include surrounding context.

python
subprocess.run(cli + ["set-default-account", "svc"], check=True, capture_output=True, text=True)
            default_out = subprocess.run(cli + ["show-default-account"], check=True, capture_output=True, text=True).stdout
            _assert("svc" in default_out, "default account CLI failed")
            subprocess.run(cli + ["add-cash", "svc", "10000"], check=True, capture_output=True, text=True)
            account_out = subprocess.run(cli + ["show-account", "svc"], check=True, capture_output=True, text=True).stdout
            _assert("210000" in account_out.replace(",", ""), "show-account CLI failed")
            list_out = subprocess.run(cli + ["list-accounts"], check=True, capture_output=True, text=True).stdout

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 · scripts/full_function_smoke_check.py (reported line 202)May include surrounding context.

python
default_out = subprocess.run(cli + ["show-default-account"], check=True, capture_output=True, text=True).stdout
            _assert("svc" in default_out, "default account CLI failed")
            subprocess.run(cli + ["add-cash", "svc", "10000"], check=True, capture_output=True, text=True)
            account_out = subprocess.run(cli + ["show-account", "svc"], check=True, capture_output=True, text=True).stdout
            _assert("210000" in account_out.replace(",", ""), "show-account CLI failed")
            list_out = subprocess.run(cli + ["list-accounts"], check=True, capture_output=True, text=True).stdout
            _assert("svc" in list_out, "list-accounts CLI failed")

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 · scripts/full_function_smoke_check.py (reported line 204)May include surrounding context.

python
subprocess.run(cli + ["add-cash", "svc", "10000"], check=True, capture_output=True, text=True)
            account_out = subprocess.run(cli + ["show-account", "svc"], check=True, capture_output=True, text=True).stdout
            _assert("210000" in account_out.replace(",", ""), "show-account CLI failed")
            list_out = subprocess.run(cli + ["list-accounts"], check=True, capture_output=True, text=True).stdout
            _assert("svc" in list_out, "list-accounts CLI failed")
        finally:
            proc.terminate()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code exposes multiple state-changing actions such as reset-account, buy, sell, cancel, add-cash, deduct-cash, process-orders, run-snapshots, and backtest, all of which immediately send HTTP POST requests. Although the module docstring names this as a paper trading CLI, these individual operations lack any inline confirmation prompt or user-facing disclosure before execution.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

The file embeds market hours and A-share-specific trading constraints, including references to Shanghai symbols and A-share lot sizing, which effectively force one market locale's rules. There is no natural-language documentation in this file indicating that the skill is intentionally region-specific or that users can choose another locale/market.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The reset_account method performs irreversible deletions of orders, trades, position lots, and account snapshots from the SQLite database. Although the file has a high-level module docstring, this specific destructive behavior has no confirmation prompt, warning comment, or user-facing disclosure near the operation.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

The module docstring states 'fully local dependencies', but the implementation clearly performs outbound HTTP requests to Sina, Tencent, and Akshare-backed remote sources. This mismatch can mislead operators, reviewers, or higher-level agents into treating the skill as offline-only, which weakens trust boundaries and can cause unapproved data egress or reliance on external services.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code file makes HTTP GET requests to third-party services to retrieve market data, and similar external requests recur throughout the file. There is no confirmation prompt, logging/print statement, or comment/docstring near these calls informing users that symbol queries and request metadata will be sent off-host to external providers.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This code exposes multiple POST routes that mutate account state or trigger potentially impactful operations, but the handler provides no confirmation prompt, logging, print statement, or explanatory comment/docstring warning users about those actions. For a code-file audit under SQP-2, these safety-relevant operations lack visible disclosure despite affecting trading state and account data.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · scripts/paper_trading_ctl.py (reported line 8)May include surrounding context.

python
import argparse
import os
import plistlib
import signal
import subprocess
import sys

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · scripts/paper_trading_ctl.py (reported line 138)May include surrounding context.

python
import argparse
import os
import plistlib
import signal
import subprocess
import sys

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · scripts/paper_trading_ctl.py (reported line 148)May include surrounding context.

python
import argparse
import os
import plistlib
import signal
import subprocess
import sys

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · scripts/paper_trading_ctl.py (reported line 154)May include surrounding context.

python
import argparse
import os
import plistlib
import signal
import subprocess
import sys

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · scripts/paper_trading_ctl.py (reported line 155)May include surrounding context.

python
import argparse
import os
import plistlib
import signal
import subprocess
import sys

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · scripts/paper_trading_ctl.py (reported line 156)May include surrounding context.

python
import argparse
import os
import plistlib
import signal
import subprocess
import sys

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · scripts/paper_trading_ctl.py (reported line 198)May include surrounding context.

python
import argparse
import os
import plistlib
import signal
import subprocess
import sys

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · scripts/paper_trading_ctl.py (reported line 201)May include surrounding context.

python
import argparse
import os
import plistlib
import signal
import subprocess
import sys

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% 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 · scripts/paper_trading_ctl.py (reported line 202)May include surrounding context.

python
import argparse
import os
import plistlib
import signal
import subprocess
import sys

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 · scripts/paper_trading_ctl.py (reported line 80)May include surrounding context.

python
if is_pid_alive(existing_pid):
        raise RuntimeError(f"service already appears running with pid={existing_pid}, but healthcheck failed")
    with log_path.open("a", encoding="utf-8") as log_file:
        proc = subprocess.Popen(
            [
                sys.executable,
                str(SERVICE_SCRIPT),

Static analysis

No suspicious patterns detected.