Back to skill

Security audit

Polymarket Bundle Dota2 Bo3 Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed paper-by-default trading skill, but live mode can place real-money trades while important documented risk limits are not actually enforced.

Review before installing for any live account. Keep it in paper mode unless you are prepared for real USDC loss, restrict the API key and account limits, monitor positions externally, and do not rely on SIMMER_MIN_VOLUME or SIMMER_MAX_POSITIONS as documented until those controls are fixed. Pin and review simmer-sdk before production use.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:38
Finding
Minimum Market Volume Safeguard Is Declared but Never Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:38`, `trader.py:75`, and trade execution flow at `trader.py:469-531` **Vulnerability Type**: Missing enforcement of a declared financial risk control **Risk Level**: High The skill documentation identifies `SIMMER_MIN_VOLUME` as the minimum market-volume filter, and the program loads this setting: ```python MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", "40")) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "5000")) MAX_SPREAD = float(os.environ.get("SIMMER_MAX_SPREAD", "0.08")) MIN_DAYS = int(os.environ.get( "SIMMER_MIN_DAYS", "0")) MAX_POSITIONS = int(os.environ.get( "SIMMER_MAX_POSITIONS", "8")) YES_THRESHOLD = float(os.environ.get("SIMMER_YES_THRESHOLD", "0.38")) NO_THRESHOLD = float(os.environ.get("SIMMER_NO_THRESHOLD", "0.62")) MIN_TRADE = float(os.environ.get("SIMMER_MIN_TRADE", "5")) ``` It is reloaded after application of the skill configuration: ```python MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", str(MAX_POSITION))) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", str(MIN_VOLUME))) MAX_SPREAD = float(os.environ.get("SIMMER_MAX_SPREAD", str(MAX_SPREAD))) MIN_DAYS = int(os.environ.get( "SIMMER_MIN_DAYS", str(MIN_DAYS))) MAX_POSITIONS = int(os.environ.get( "SIMMER_MAX_POSITIONS", str(MAX_POSITIONS))) YES_THRESHOLD = float(os.environ.get("SIMMER_YES_THRESHOLD", str(YES_THRESHOLD))) NO_THRESHOLD = float(os.environ.get("SIMMER_NO_THRESHOLD", str(NO_THRESHOLD))) MIN_TRADE = float(os.environ.get("SIMMER_MIN_TRADE", str(MIN_TRADE))) MIN_INCONSISTENCY = float(os.environ.get("SIMMER_MIN_INCONSISTENCY", str(MIN_INCONSISTENCY))) ``` However, no volume check occurs before the execution flow reaches `client.trade()`: ```python side, size, reasoning = compute_signal(market, opp) if not side: safe_print(f" [ski ...[truncated 1998 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Enforce the volume threshold before signal generation and again immediately before trading. - Retrieve the canonical numeric volume field from the SDK and reject markets whose volume is missing, malformed, non-finite, or below `MIN_VOLUME`. - Prefer fail-closed behavior when the SDK does not provide reliable volume data. - Re-fetch market data immediately before a live order to reduce time-of-check/time-of-use exposure. - Add unit and integration tests proving that a market below `MIN_VOLUME` cannot reach `client.trade()`. - Log the observed volume and configured threshold for every rejection. For example: ```python volume = getattr(market, "volume", None) if not isinstance(volume, (int, float)) or volume < MIN_VOLUME: return None, 0, ( f"Volume unavailable or below minimum: " f"{volume!r} < {MIN_VOLUME}" ) ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:504
Finding
Maximum Concurrent Position Limit Resets on Every Execution<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:504-531` **Vulnerability Type**: Incorrect enforcement of portfolio exposure limit **Risk Level**: High The execution loop uses a local counter that starts at zero for every process invocation: ```python # Execute trades on best inconsistencies placed = 0 for market_id, opp in sorted(all_opps.items(), key=lambda x: -x[1][2]): if placed >= MAX_POSITIONS: break market = opp[0] side, size, reasoning = compute_signal(market, opp) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, market_id) if not ok: safe_print(f" [skip] {why}") continue try: r = client.trade( market_id=market_id, side=side, amount=size, source=TRADE_SOURCE, skill_slug=SKILL_SLUG, reasoning=reasoning, ) tag = "(sim)" if r.simulated else "(live)" status = "OK" if r.success else f"FAIL:{r.error}" safe_print(f" [trade] {side.upper()} ${size} {tag} {status} -- {reasoning[:110]}") if r.success: placed += 1 ``` The documentation and configuration label `SIMMER_MAX_POSITIONS` as the maximum number of concurrent open positions, but the implementation limits only successful orders placed during the current run. It does not retrieve or count positions opened by earlier runs. ### Technical Analysis A concurrent-position limit must be calculated from persistent portfolio state. The local `placed` variable is ephemeral and does not represent current account exposure. Every manual, scheduled, or concurrent invocation starts with a full allowance of `MAX_POSITIONS`. The implementation also lacks atomicity. Even if an initial position count were added without server-side coordination, two simultaneous processes could observe the same available capacity and both submit orders, exceeding the limit. ### Attac ...[truncated 1053 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Query the account's current open positions before submitting any order. - Calculate available capacity as `MAX_POSITIONS - current_open_positions`. - Define whether multiple positions in the same market count separately and enforce that definition consistently. - Count pending and partially filled orders where they can create additional exposure. - Enforce the limit atomically on the trading service or through a transactional reservation mechanism; a client-side check alone cannot prevent concurrent-run races. - Recheck capacity immediately before each order. - Add a process lock if only one local instance should execute, while retaining server-side enforcement for distributed or crashed processes. - Add tests covering existing positions, pending orders, repeated runs, and simultaneous processes. - Rename the setting to “maximum orders per run” only if that is the intended behavior; otherwise, implement the documented concurrent-position semantics. ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:3
Finding
Unpinned Third-Party SDK Receives a High-Value Trading Credential<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:3-10`; credential use at `trader.py:20` and `trader.py:63-66` **Vulnerability Type**: Unpinned security-sensitive dependency **Risk Level**: Medium The package metadata installs `simmer-sdk` without an exact version or integrity constraint: ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] }, ``` The dependency is imported into the process and receives the trading API key: ```python from simmer_sdk import SimmerClient ``` ```python _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) ``` ### Technical Analysis An unpinned dependency can resolve to different package content across installations. The reviewed project does not provide an exact audited version, lockfile, or artifact hash. Consequently, a future compromised, malicious, or behaviorally incompatible release could be installed without any project source change. This dependency is security-sensitive because imported Python package code executes with the privileges of the skill process. The SDK is also explicitly given `SIMMER_API_KEY` and controls market retrieval and trade submission. A compromised dependency would therefore be positioned to access the credential, alter trade parameters, make unauthorized API requests, or transmit sensitive information. No evidence was found that the currently intended `simmer-sdk` package is malicious. The confirmed issue is the absence of version and integrity pinning around a dependency with direct access to trading authority. ### Attack Path 1. An attacker compromises the upstream package account, distribution pipeline, or a future package release. 2. A new malicious version is published under the same package name. 3. A fresh deployment installs `simmer-sdk` without an exact version or hash. 4. Python imports the package, executing its initialization code with the skill process's permissions. 5. The program passes ...[truncated 731 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `simmer-sdk` to an exact, reviewed version rather than accepting any available release. - Use a lockfile and cryptographic hashes for reproducible installation. - Install only from an authenticated, trusted package index and verify package provenance. - Review release signatures, source repository ownership, and build provenance before upgrades. - Perform dependency vulnerability and integrity scanning in CI. - Upgrade through an explicit review process rather than automatically accepting new versions. - Restrict `SIMMER_API_KEY` to the minimum required permissions, venues, spending limits, and account scope. - Rotate the credential if dependency compromise is suspected. - Run the skill in a sandbox with limited filesystem and network access to reduce the impact of compromised package code. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • 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 (2)

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill declares access to a high-value credential (`SIMMER_API_KEY`) and describes trading execution, but it does not define any explicit tool scope such as `permissions` or `allowed-tools`. In practice, that means the runtime may grant broader environment or tool access than is necessary, violating least privilege and increasing the blast radius if the skill is modified, misused, or composed with other agent behaviors.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The manifest describes an automated trading skill with autostart disabled but explicit position-sizing and trade-threshold controls, which indicates it can place financial trades once run. There is no user-facing disclosure in the manifest about autonomous execution, market risk, loss potential, or the fact that configuration values can materially affect exposure, increasing the chance of uninformed or unsafe deployment.

Static analysis

No suspicious patterns detected.