Back to skill

Security audit

Polymarket Bundle Tennis Set Match Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Polymarket trading skill, but it should go to Review because some live-trading safeguards are under-enforced while it uses a real trading credential.

Install only if you understand and accept live Polymarket trading risk. Keep it in paper mode unless you have reviewed the strategy, pinned and vetted simmer-sdk, scoped SIMMER_API_KEY tightly, and confirmed account-level limits for volume, open positions, pending orders, and total exposure are enforced outside this skill.

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

T08 · Insecure Dependencies

Error
Location
clawhub.json:3
Finding
Unpinned Trading SDK Creates a Supply-Chain Execution Risk<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:3-9`, `trader.py:21`, `trader.py:62-67` **Vulnerability Type**: Unpinned privileged third-party dependency **Risk Level**: High ### Vulnerable Code ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] } ``` ```python from simmer_sdk import SimmerClient ``` ```python _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) ``` ### Technical Analysis The project declares `simmer-sdk` without an exact version or an integrity hash. Consequently, installation may resolve to any release accepted by the package manager at installation time. The package is imported directly into the Python process. Python package initialization code executes during import, before the Skill's trading safeguards are applied. The dependency is also explicitly given `SIMMER_API_KEY` when the client is constructed and controls all market-context, market-discovery, and trade API interactions. No evidence was found that the current package is malicious. The vulnerability is the absence of dependency pinning and integrity verification around a dependency operating in a privileged financial context. ### Attack Path 1. An attacker compromises the upstream package account, release pipeline, distribution artifact, or a dependency in its transitive dependency tree. 2. The attacker publishes a new malicious or compromised release under the same package name. 3. A deployment installs `simmer-sdk` without an exact version or required artifact hash. 4. Malicious initialization code executes when `trader.py` imports `simmer_sdk`. 5. The code can read the process environment, including `SIMMER_API_KEY`, or alter the behavior of `SimmerClient`. 6. The compromised client can disclose the credential, falsify market data, or submit unauthorized trades using the application's trading authority. ### Impact Assessment Successful exploitation would execute code with the ...[truncated 512 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to an exact, reviewed version rather than accepting the latest available release. 2. Use a lockfile or requirements file containing cryptographic hashes, and install with hash verification enabled. 3. Review and pin transitive dependencies as well as the direct SDK dependency. 4. Retrieve packages only from an explicitly configured, trusted package index. 5. Run the Skill under a dedicated, least-privileged operating-system identity. 6. Scope `SIMMER_API_KEY` to the minimum required venue, account, order size, and trading permissions. 7. Rotate the key if package integrity is ever in doubt. 8. Consider isolating the SDK in a restricted environment with outbound network access limited to required Simmer or Polymarket endpoints. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:427
Finding
Configured Minimum Market Volume Safeguard Is Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:33-42`, `trader.py:427-507` **Vulnerability Type**: Missing financial risk-control enforcement **Risk Level**: Medium ### Vulnerable Code The minimum-volume setting is loaded: ```python MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", "40")) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "3000")) MAX_SPREAD = float(os.environ.get("SIMMER_MAX_SPREAD", "0.08")) MIN_DAYS = int(os.environ.get( "SIMMER_MIN_DAYS", "1")) 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")) MIN_VIOLATION = float(os.environ.get("SIMMER_MIN_VIOLATION", "0.03")) ``` However, the execution path submits trades without validating volume: ```python # Execute trades on best violations 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 except Exception as e: safe_print(f" [error] {market_id}: {e}") ``` ### Technical Analysis `SIMMER_MIN ...[truncated 1898 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Read the market's authoritative volume field and require it to be a finite numeric value. 2. Reject markets with missing or unparseable volume rather than treating missing data as acceptable. 3. Enforce `volume >= MIN_VOLUME` during discovery and repeat the check immediately before live execution. 4. Validate executable order-book depth for the proposed amount, not only aggregate historical volume. 5. Distinguish between total volume, recent volume, and available depth, and document which metric the threshold controls. 6. Add unit tests proving that markets below the threshold and markets with missing volume cannot reach `client.trade()`. 7. Record the observed volume and depth in the trade reasoning or audit log. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:477
Finding
Maximum Position Count Ignores Existing Positions and Pending Orders<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:477-505` **Vulnerability Type**: Incomplete enforcement of account exposure limits **Risk Level**: High ### Vulnerable Code ```python # Execute trades on best violations 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 except Exception as e: safe_print(f" [error] {market_id}: {e}") ``` ### Technical Analysis The documented `MAX_POSITIONS` control is described as a limit on concurrent open positions. The implementation instead initializes `placed` to zero on each process invocation and increments it only for successful orders submitted during that invocation. The code does not query: - Existing open positions; - Existing pending orders; - Prior positions in the same markets; - Orders concurrently submitted by another Skill instance. As a result, the check limits orders per run rather than concurrent account positions. Repeated or concurrent invocations can exceed the configured risk boundary. The check is also not atomic, so two processes can independently observe capacity and submit orders simultaneously. ### Attack Path 1. The account already has up to `MAX_POSITIONS ...[truncated 1220 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query authoritative open positions and pending orders before entering the execution loop. 2. Calculate remaining capacity as the configured limit minus existing positions and reserved pending orders. 3. Recheck capacity immediately before every order. 4. Use an account-level atomic reservation, transaction, or distributed lock to prevent concurrent processes from exceeding the limit. 5. Decide whether multiple positions in the same market count separately or as one net position, and enforce that definition consistently. 6. Add a maximum total notional-exposure limit in addition to the position-count limit. 7. Make the live trade fail closed if position data cannot be retrieved or is stale. 8. Add tests for repeated runs, pre-existing positions, pending orders, and concurrent invocations. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:298
Finding
Invalid Handicap Probability Model Can Trigger Unsupported Live Trades<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:188-194`, `trader.py:298-328` **Vulnerability Type**: Incorrect security-sensitive financial decision logic **Risk Level**: High ### Vulnerable Code The parser accepts any first-player handicap value: ```python # Try Set Handicap m = _SET_HANDICAP.search(question) if m: p1 = m.group(1).strip() p2 = m.group(3).strip() key = normalize_match_key(f"{p1} vs {p2}") handicap = float(m.group(2)) return (key, "set_handicap", handicap) ``` The decision logic then treats the aggregate probability of any straight-sets result as the probability that the specific handicap outcome succeeds: ```python # Constraint 2: Total Sets O/U 2.5 vs Set Handicap consistency if total_sets is not None and set_handicap is not None: ts_price = total_sets.price sh_price = set_handicap.price # If Total Sets O/U 2.5 OVER > 50% => likely 3 sets => underdog # wins at least one set => set handicap -1.5 favourite should be # lower (harder to cover -1.5 when match likely goes 3 sets) if ts_price > 0.50: # 3-set match likely: handicap -1.5 should be < (1 - ts_price) # because covering -1.5 means winning 2-0 in sets expected_straight_sets = 1.0 - ts_price # P(2-0 finish) if sh_price > expected_straight_sets + MIN_VIOLATION: violation = sh_price - expected_straight_sets opportunities.append(( set_handicap.market, "no", violation, f"\U0001F3BE\U0001F517 Set Hcap -1.5={sh_price:.1%} but " f"P(straight sets)={expected_straight_sets:.1%} from " f"Total Sets O/U 2.5={ts_price:.1%} | " f"violation={violation:.1%} -- {set_handicap.market.question[:55]}" )) elif ts_price < 0.50: # 2-set match likely: handicap -1.5 should be high expected_straight_sets = 1.0 - ts_price if sh_price < expected_straight_sets - MIN_VIOLATION: ...[truncated 3105 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require an exact supported handicap line, such as `line_value == -1.5`, before applying a corresponding model. 2. Parse and retain the player associated with the priced outcome, the opposing player, the handicap line, and the meaning of YES and NO. 3. Do not equate the aggregate probability of any straight-sets outcome with a particular player's probability of winning in straight sets. 4. Derive only mathematically valid upper or lower bounds, or combine the total-sets market with player-specific winner probabilities from appropriately matched markets. 5. Verify the match format before applying a `2.5`-set model; best-of-five and other formats require different event relationships. 6. Validate market resolution rules to ensure that the compared propositions use compatible settlement semantics. 7. Disable live handicap execution until the player and outcome mappings are unambiguous. 8. Add unit tests covering both possible straight-set winners, non-`-1.5` handicap lines, reversed player order, YES/NO outcome orientation, and best-of-five matches. ]]>
Vulnerability Patterns
  • 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
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill declares access to environment-based credentials via `SIMMER_API_KEY` but does not define any explicit tool scope such as `permissions` or `allowed-tools`. In an agent framework, missing scope declarations can cause overbroad runtime access or make it unclear which components may read sensitive environment variables, increasing the risk of credential exposure or unintended privileged actions.

Static analysis

No suspicious patterns detected.