Back to skill

Security audit

Polymarket 24h Nba Game Structure Trader

Security checks for vulnerabilities and agentic risk

Overview

The skill is clearly a trading bot and is not deceptive, but its live-trading safeguards are under-enforced in ways that could expose more funds than users expect.

Review this carefully before installing for live use. It is acceptable for paper trading, but live trading should only be used with a tightly limited API key, small balances, dependency pinning, and fixes for the volume, position-count, and fail-closed context checks.

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:6
Finding
Unpinned Third-Party Trading Dependency<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:6-9`, `trader.py:42`, `SKILL.md:135-139` **Vulnerability Type**: Supply-chain exposure through an unpinned privileged dependency **Risk Level**: High ### Vulnerable Code ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] } ``` The dependency is imported directly by the trading application: ```python from simmer_sdk import SimmerClient ``` ### Technical Analysis The project installs `simmer-sdk` without an exact version constraint, lock file, or integrity hash. Package resolution can consequently select a different release on future installations. This dependency operates in a security-sensitive context: it is imported into the main process, receives `SIMMER_API_KEY`, communicates with market services, and submits simulated or live orders. Python package initialization code also executes during import. A compromised package release, compromised publisher account, or unsafe package-index configuration could therefore introduce arbitrary code with the same operating-system privileges as the trader. This finding does not establish that the current `simmer-sdk` release is malicious. The vulnerability is the absence of controls that guarantee future installations use the reviewed dependency artifact. ### Attack Path 1. An attacker compromises the package publisher, distribution account, package index, or release pipeline for `simmer-sdk`. 2. The attacker publishes a modified release containing malicious initialization or client code. 3. A new skill installation resolves the unconstrained dependency to the compromised release. 4. `trader.py` imports the package, executing its code inside the trader process. 5. The malicious dependency reads the API key passed to `SimmerClient`, accesses other process-readable data, or modifies trading requests. 6. The attacker may exfiltrate credentials or submit unauthorized orders under the victim's trading authority ...[truncated 384 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to an exact, reviewed version rather than using an unconstrained package name. 2. Generate a lock file and require cryptographic hashes for all direct and transitive dependencies, such as with `pip-compile --generate-hashes`. 3. Install packages with hash verification enabled and from an explicitly trusted index. 4. Verify the package publisher, source repository, release signatures, and correspondence between source and distributed artifacts. 5. Run the trader in a restricted environment with minimal filesystem and network permissions. 6. Use a trading credential with the least authority possible, including venue, order-size, and account-level restrictions where supported. 7. Establish dependency update review and vulnerability scanning before accepting newer SDK releases. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:439
Finding
Declared Minimum-Volume Trading Safeguard Is Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:55`, `trader.py:439-461`; configuration declared at `clawhub.json:31-42` **Vulnerability Type**: Missing enforcement of a documented financial risk control **Risk Level**: High ### Vulnerable Code The minimum-volume setting is loaded: ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "5000")) ``` However, the complete market-validation function never checks market volume: ```python def valid_market(market) -> tuple[bool, str]: """Check basic market quality gates.""" p = getattr(market, "current_probability", None) if not isinstance(p, (int, float)): return False, "missing probability" spread_cents = getattr(market, "spread_cents", None) if isinstance(spread_cents, (int, float)) and spread_cents / 100 > MAX_SPREAD: return False, f"Spread {spread_cents/100:.1%} > {MAX_SPREAD:.1%}" resolves_at = getattr(market, "resolves_at", None) if resolves_at: try: resolves = datetime.fromisoformat(resolves_at.replace("Z", "+00:00")) days = (resolves - datetime.now(timezone.utc)).days if days < MIN_DAYS: return False, f"Only {days} days to resolve" except Exception: pass return True, "ok" ``` ### Technical Analysis `SIMMER_MIN_VOLUME` is exposed as a tunable and documented as the minimum acceptable market volume. Despite being parsed into `MIN_VOLUME`, that value is not referenced by `valid_market`, signal generation, or order execution. As a result, users and operators may reasonably believe that markets below the configured liquidity threshold are rejected when no such protection exists. A low-volume market can pass validation as long as its probability, reported spread, and resolution date pass the implemented checks. Low-liquidity prediction markets are more susceptible to manipulation, stale prices, excessive execution slippage, and an inability to unwind ...[truncated 1301 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Retrieve volume from an authoritative SDK field and enforce it in `valid_market`: ```python volume = getattr(market, "volume", None) if not isinstance(volume, (int, float)): return False, "missing market volume" if float(volume) < MIN_VOLUME: return False, f"Volume ${volume:,.2f} below minimum ${MIN_VOLUME:,.2f}" ``` 2. Confirm the SDK's volume unit and whether the value represents lifetime, recent, or executable liquidity. 3. Fail closed when volume is missing or malformed in live mode. 4. Consider checking order-book depth at the intended order size rather than relying only on historical volume. 5. Revalidate volume, spread, and executable price immediately before order submission. 6. Add automated tests proving that markets below the configured threshold cannot reach `client.trade`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:561
Finding
Concurrent Position Limit Resets on Every Invocation<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:561-589`; setting loaded at `trader.py:58` and declared at `clawhub.json:64-75` **Vulnerability Type**: Incorrect enforcement of the maximum-open-positions control **Risk Level**: High ### Vulnerable Code ```python # Execute trades on the most inconsistent legs 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: print(f" [skip] {reasoning}") continue ok, why = context_ok(client, market_id) if not ok: 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}" print(f" [trade] {side.upper()} ${size} {tag} {status} -- {reasoning[:110]}") if r.success: placed += 1 except Exception as e: print(f" [error] {market_id}: {e}") ``` ### Technical Analysis `SIMMER_MAX_POSITIONS` is described as the maximum number of concurrent open positions. The implementation instead initializes `placed` to zero on every run and increments it only for successful orders submitted during that invocation. The skill never queries the account's existing open positions. Therefore, the limit is a per-run order-count limit rather than a concurrent-position limit. Existing positions, positions opened by previous invocations, and potentially positions opened by other tools using the same account are excluded. Repeated live execution can consequently accumulate substantially more exposure than the documented control permits. ### Attack Path 1. The account already has ope ...[truncated 983 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query all current open positions from the venue before evaluating new opportunities. 2. Calculate remaining capacity as: ```python remaining = max(0, MAX_POSITIONS - len(open_positions)) ``` 3. Stop trading when no capacity remains and decrement capacity only after confirmed position creation. 4. Define whether multiple orders in the same market count as one position and implement that definition consistently. 5. Account for pending and partially filled orders to prevent races. 6. Use an account-level or distributed lock when multiple trader instances can run concurrently. 7. Recheck the position count immediately before each live order. 8. Rename the setting if the intended behavior is only a per-run order limit; otherwise, update the implementation to match the documented concurrent-position semantics. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:496
Finding
Trading Context Safeguards Fail Open on Errors<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:496-513`, used before execution at `trader.py:575-579` **Vulnerability Type**: Fail-open validation of slippage and trading-discipline safeguards **Risk Level**: High ### Vulnerable Code ```python def context_ok(client: SimmerClient, market_id: str) -> tuple[bool, str]: """Check flip-flop and slippage safeguards.""" try: ctx = client.get_market_context(market_id) if not ctx: return True, "no context" if ctx.get("discipline", {}).get("is_flip_flop"): reason = ctx["discipline"].get("flip_flop_reason", "recent reversal") return False, f"Flip-flop: {reason}" slip = ctx.get("slippage", {}) if isinstance(slip, dict) and slip.get("slippage_pct", 0) > 0.15: return False, f"Slippage {slip['slippage_pct']:.1%}" for w in ctx.get("warnings", []): print(f" [warn] {w}") except Exception as e: print(f" [ctx] {market_id}: {e}") return True, "ok" ``` The approving result is subsequently used to permit trading: ```python ok, why = context_ok(client, market_id) if not ok: 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, ) ``` ### Technical Analysis The function is intended to reject flip-flop behavior and excessive slippage. However, both an empty context and any exception result in approval: - `if not ctx: return True, "no context"` - The exception handler logs the error and falls through to `return True, "ok"` Exceptions may arise from network failures, authentication errors, timeouts, malformed responses, unexpected SDK return types, or changes to the response schema. For example, a non-dictionary `ctx` can fail at `ctx.get`, while a nonnumeric `slippage_pct` can fail during comparison or forma ...[truncated 1302 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed on context errors in live mode: ```python except Exception as e: return False, f"Unable to validate market context: {e}" ``` 2. Treat missing or empty context as a rejection for live orders unless the absence has an explicitly documented safe meaning. 3. Validate the complete response schema and numeric ranges before using context fields. 4. Add bounded retries with timeouts for transient context-service failures. 5. Permit fail-open behavior only in simulation mode, if desired, and make that distinction explicit in the function signature. 6. Recheck executable slippage directly against the current order book immediately before submitting the order. 7. Add tests for timeouts, authentication failures, malformed dictionaries, nonnumeric slippage values, and missing context to ensure none can authorize a live trade. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (2)

Lp3

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding
The skill references a high-value credential (`SIMMER_API_KEY`) and describes trading behavior, but it does not declare an explicit tool/permission scope such as allowed tools or environment access boundaries. That omission weakens least-privilege guarantees: an agent runtime may expose environment access more broadly than intended, increasing the chance that credentials are read or misused by skill logic or adjacent components.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This manifest requires the SIMMER_API_KEY environment variable, which indicates the skill depends on sensitive credentials. In this file there is no accompanying disclosure about credential handling, storage, or transmission, which can leave users unaware that a secret is needed and may be used by the skill.

Static analysis

No suspicious patterns detected.