Back to skill

Security audit

量化策略助手

Security checks across malware telemetry and agentic risk

Overview

This quant-trading skill is disclosed as a backtesting and QMT trading assistant, but it has real financial and host-control effects that need careful review before use.

Install only in an isolated trading environment you control, preferably with a paper/simulation account first. Review generated strategy files before execution, provide tokens explicitly rather than relying on local .env discovery, and assume the skill can start background services, read trading/data credentials, place and cancel a live probe order, and terminate processes on configured monitor ports.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (38)

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
vt_symbols = [s for s in vt_symbols if s not in _bad]
        p(f"  [warn] 已过滤{len(_bad)}个不支持的标的,剩余{len(vt_symbols)}个")
    mod = importlib.import_module(args.strategy)
    if hasattr(mod, args.cls): strategy_cls = getattr(mod, args.cls)
    else: # 大小写模糊匹配(agent常把Ma写成MA)
        low = args.cls.lower(); hit = [n for n in dir(mod) if n.lower() == low]
        if hit: p(f"  [warn] 类名修正: {args.cls} -> {hit[0]}"); strategy_cls = getattr(mod, hit[0])
Confidence
86% confidence
Finding
if hasattr(mod, args.cls): strategy_cls = getattr(mod, args.cls)

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
if hasattr(mod, args.cls): strategy_cls = getattr(mod, args.cls)
    else: # 大小写模糊匹配(agent常把Ma写成MA)
        low = args.cls.lower(); hit = [n for n in dir(mod) if n.lower() == low]
        if hit: p(f"  [warn] 类名修正: {args.cls} -> {hit[0]}"); strategy_cls = getattr(mod, hit[0])
        else: raise AttributeError(f"模块 {args.strategy} 中找不到类 {args.cls},可用: {[n for n in dir(mod) if n[0].isupper() and 'Strategy' in n]}")

    _dry = args.max_bars > 0
Confidence
84% confidence
Finding
if hit: p(f" [warn] 类名修正: {args.cls} -> {hit[0]}"); strategy_cls = getattr(mod, hit[0])

subprocess module call

Medium
Category
Dangerous Code Execution
Content
for _ in range(3):
        try:
            if is_win:
                subprocess.run(["powershell", "-NoProfile", "-Command",
                    f"(Get-NetTCPConnection -LocalPort {port} -State Listen -ErrorAction SilentlyContinue).OwningProcess | Sort-Object -Unique | ForEach-Object {{ Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }}"],
                    timeout=8, capture_output=True, text=True)
            else:
Confidence
88% confidence
Finding
subprocess.run(["powershell", "-NoProfile", "-Command", f"(Get-NetTCPConnection -LocalPort {port} -State Listen -ErrorAction SilentlyContinue).OwningProcess | Sort-

subprocess module call

Medium
Category
Dangerous Code Execution
Content
f"(Get-NetTCPConnection -LocalPort {port} -State Listen -ErrorAction SilentlyContinue).OwningProcess | Sort-Object -Unique | ForEach-Object {{ Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }}"],
                    timeout=8, capture_output=True, text=True)
            else:
                subprocess.run(f"lsof -ti:{port} | xargs -r kill -9 2>/dev/null || fuser -k {port}/tcp 2>/dev/null", shell=True, timeout=5, capture_output=True, text=True)
        except Exception: pass
        time.sleep(1.5 if is_win else 0.5)
        try:
Confidence
98% confidence
Finding
subprocess.run(f"lsof -ti:{port} | xargs -r kill -9 2>/dev/null || fuser -k {port}/tcp 2>/dev/null", shell=True, timeout=5, capture_output=True, text=True)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _kill_port_process(port: int) -> bool:
    if not _is_port_busy(port): return True
    for _ in range(3):
        try: subprocess.run(f"lsof -ti:{int(port)} | xargs -r kill -9 2>/dev/null || fuser -k {int(port)}/tcp 2>/dev/null", shell=True, timeout=5, capture_output=True, text=True)
        except Exception: pass
        time.sleep(0.4)
        if not _is_port_busy(port): return True
Confidence
96% confidence
Finding
try: subprocess.run(f"lsof -ti:{int(port)} | xargs -r kill -9 2>/dev/null || fuser -k {int(port)}/tcp 2>/dev/null", shell=True, timeout=5, capture_output=True, text=True)

Tainted flow: 'path' from os.getenv (line 769, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
bnav = bench.reindex(nav.index).ffill().dropna().astype(float)
        data["bench"] = [round(float(v), 6) for v in (bnav / float(bnav.iloc[0])).values]
    path = os.path.join(BASE, f"{output_name}_report_data.json")
    with open(path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False)
    p(f"[report_data] {path}")

# ========== 净值曲线文件 ==========
Confidence
94% confidence
Finding
with open(path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False)

Tainted flow: 'spath' from os.getenv (line 957, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
release_lock()
    summary = {"run_id": RUN_ID, "status": "done", "output": args.output, "stats": {k: (float(v) if isinstance(v, (int, float)) else str(v)) for k, v in (stats or {}).items()}}
    spath = os.path.join(BASE, f"{args.output}_summary.json")
    with open(spath, "w", encoding="utf-8") as f: json.dump(summary, f, ensure_ascii=False, indent=2, default=str)
    p(f"[summary] {spath}")
    _report("/api/done")
    p("[完成] 回测结束")
Confidence
95% confidence
Finding
with open(spath, "w", encoding="utf-8") as f: json.dump(summary, f, ensure_ascii=False, indent=2, default=str)

Tainted flow: 'PROJECT_ROOT' from os.getenv (line 33, credential/environment) → subprocess.Popen (code execution)

Medium
Category
Data Flow
Content
def start_process(command: list[str], log_path: Path, env: Optional[Dict[str, str]] = None) -> subprocess.Popen:
    log_path.parent.mkdir(parents=True, exist_ok=True)
    f = log_path.open("a", encoding="utf-8")
    return subprocess.Popen(
        command,
        cwd=str(PROJECT_ROOT),
        env=env or os.environ.copy(),
Confidence
94% confidence
Finding
return subprocess.Popen( command, cwd=str(PROJECT_ROOT), env=env or os.environ.copy(), stdout=f, stderr=subprocess.STDOUT, start_new_session=True,

Tainted flow: 'port' from os.getenv (line 138, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def _kill_port_process(port: int) -> bool:
    if not _is_port_busy(port): return True
    for _ in range(3):
        try: subprocess.run(f"lsof -ti:{int(port)} | xargs -r kill -9 2>/dev/null || fuser -k {int(port)}/tcp 2>/dev/null", shell=True, timeout=5, capture_output=True, text=True)
        except Exception: pass
        time.sleep(0.4)
        if not _is_port_busy(port): return True
Confidence
97% confidence
Finding
try: subprocess.run(f"lsof -ti:{int(port)} | xargs -r kill -9 2>/dev/null || fuser -k {int(port)}/tcp 2>/dev/null", shell=True, timeout=5, capture_output=True, text=True)

Tainted flow: 'source' from os.getenv (line 1730, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
source = strategy_file_path.read_text(encoding="utf-8")
            bt_mode = parsed.get("mode", "cta")
            source, import_fixes = _autofix_imports(source, bt_mode)
            if import_fixes: strategy_file_path.write_text(source, encoding="utf-8"); print(f"[autofix] import修正: {'; '.join(import_fixes)}")
            subprocess.run([payload["python_bin"], "-m", "py_compile", str(strategy_file_path)], check=True, cwd=str(PROJECT_ROOT))
            _lint_strategy(source, strategy_file_path.name, bt_mode)
            _preflight_strategy_import(module_name, payload["python_bin"])
Confidence
88% confidence
Finding
if import_fixes: strategy_file_path.write_text(source, encoding="utf-8"); print(f"[autofix] import修正: {'; '.join(import_fixes)}")

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
This runner intentionally imports and executes arbitrary local strategy modules and classes based on CLI input, which means anyone controlling those arguments can run arbitrary Python code with the runner's privileges. In a backtesting assistant that may be core functionality, but in a multi-tenant agent or automation context it becomes a code-execution boundary and should be treated as dangerous, not as a harmless helper feature.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The helper performs broader-than-necessary credential discovery by scanning multiple local .env locations for QGDATA_TOKEN during a requirement preflight check. Even though it does not exfiltrate the token, silently reading secrets from project, home-directory, and /opt files expands trust boundaries and can cause the process to use credentials the operator did not explicitly provide.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The monitor server proactively kills any process listening on its configured port before starting. In a trading assistant context, this is especially risky because it may terminate unrelated local services, dashboards, or other trading components, causing outages or loss of monitoring during sensitive workflows.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
This file is labeled and positioned as a backtest/orchestration pipeline, but it also exposes commands to start live or simulated trading, probe brokerage connectivity, and stop trading sessions. In an agent skill context, combining strategy generation with execution materially raises risk because a natural-language workflow can move from analysis into operational trading without a strong trust boundary.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The orchestrator can reclaim ports by killing any local process listening on them after only a superficial HTTP state check. That is a destructive host-level capability inappropriate for an agent-facing orchestration component, and it can be abused to terminate unrelated services or security tooling.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The file hardcodes a usable shared external-service token as the default value for QGDATA_SHARED_TOKEN. Embedding credentials in source code is a real secret-management flaw: anyone with repository or package access can extract and reuse the token, consume quota, impersonate the application to the upstream service, or pivot into related integrations. In this skill context, which supports strategy generation/backtesting and potentially simulated or live trading workflows, bundling a working token lowers the barrier for unreviewed outbound service use and increases supply-chain and abuse risk.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
The module presents itself as shared constants/utilities for qgdata, but it also silently provisions a functioning shared token for external access. This mismatch is dangerous because it conceals credentialed network capability inside an innocuous helper module, making security review, dependency trust decisions, and user consent harder; the hidden token is the underlying vulnerability, and the misleading framing increases the chance it will go unnoticed.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The QMT auto-discovery routine inspects environment variables, queries running processes via WMIC, and scans multiple drive roots for installation artifacts. In a trading assistant context this may be intended for convenience, but it expands host reconnaissance beyond what is strictly needed and leaks sensitive local environment details if abused or logged.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
load_strategy_class() uses importlib to load and execute arbitrary Python from a user-supplied file path, and spec.loader.exec_module(mod) runs that code immediately. In this skill context, where strategies may be generated or supplied dynamically, this creates a direct arbitrary code execution path on the host running the trading agent.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
probe_order() sends a live buy order and then cancels it as a connectivity test, introducing real trading side effects including accidental execution, partial fills, fees, and position changes. This is especially dangerous in a quant trading assistant because users may expect diagnostics or simulation, not undisclosed live market interaction during setup or probe mode.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
The function claims to confirm order placement and cancellation, but it only sleeps for fixed intervals and assumes success without checking actual order status events. This can leave users with a false sense of safety while an order remains working or gets filled, increasing operational and financial risk in a live-trading context.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The preflight routine accesses local secrets by reading QGDATA_TOKEN from both environment variables and .env files during a generic readiness check. Even though it does not exfiltrate the token in this snippet, credential access expands the skill's trust boundary and normalizes secret harvesting behavior unrelated to minimal preflight validation.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The engine discovery logic performs breadth-first filesystem scanning from broad roots such as /opt, the user's home directory, and multiple Windows drive roots. This exceeds what is necessary for a strategy assistant's preflight and can expose sensitive directory structure information while causing unexpected traversal of user and system locations.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
Reading QGDATA_TOKEN from multiple local .env files without user-facing disclosure is a security-relevant privacy issue because the program may unexpectedly consume credentials from sensitive locations. In an agent/skill context, hidden credential sourcing is more dangerous because users may not realize the skill is probing local files outside the immediate workspace.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The code issues immediate destructive termination of processes on a port without any user-facing warning or confirmation. In operational systems, especially those adjacent to trading, silent forced shutdowns can cause data loss, service disruption, and difficult incident response.

VirusTotal

65/65 vendors flagged this skill as clean.

View on VirusTotal

Static analysis

No suspicious patterns detected.