Back to skill

Security audit

Polymarket 48h Price Curve Arb Trader

Security checks for vulnerabilities and agentic risk

Overview

This trading skill is transparent about needing a trading API key, but its live-trading safeguards are materially weaker than the documentation suggests.

Review before installing for any account with live trading enabled. Paper mode is disclosed as the default, but do not rely on the documented volume or concurrent-position limits as hard protections, and treat --live as capable of placing under-hedged or repeated positions. Use a limited API key and test in simulation first.

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)

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:303
Finding

Declared Minimum-Volume Safeguard Is Not Enforced

Content
View full analysis
tuple[bool, str]: 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" ``` Market discovery likewise adds markets without enforcing the configured volume threshold: ```python def find_markets(client: SimmerClient) -> list: """Find active crypto price-threshold markets, deduplicated. Filters out non-threshold markets (e.g. 'Up or Down' coin-flips).""" seen, unique = set(), [] for kw in KEYWORDS: try: for m in client.find_markets(query=kw): market_id = getattr(m, "id", None) if market_id and market_id not in seen: q = getattr(m, "question", "").lower() if any(w in q for w in ("above", "between", "reach", "hit", "dip", "below", "exceed")): seen.add(market_id) unique.append(m) e ...[truncated 1641 chars]
Remediation
View remediation
tuple[bool, str]: p = getattr(market, "current_probability", None) if not isinstance(p, (int, float)): return False, "missing probability" volume = getattr(market, "volume", None) if not isinstance(volume, (int, float)): return False, "missing market volume" if volume < MIN_VOLUME: return False, f"Volume ${volume:,.2f} < ${MIN_VOLUME:,.2f}" # Existing spread and resolution checks follow. ``` Use the exact SDK field representing executable or recent volume rather than assuming the field name. Prefer checking order-book depth at the intended order size in addition to aggregate volume. Further hardening should include: - Fail closed when volume or liquidity information is unavailable. - Validate effective execution price immediately before submission. - Set an order-specific maximum slippage. - Add tests proving that markets below `SIMMER_MIN_VOLUME` cannot reach `client.trade()`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:431
Finding

Position Limit Resets on Every Invocation

Content
View full analysis
= 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 `MAX_POSITIONS` is presented as a maximum number of concurrent open positions. The implementation only compares it with `placed`, a local counter initialized to zero for every process invocation. The code does not: - Query existing open positions. - Account for positions opened by previous runs. - Detect whether the account already has exposure to the selected market. - Reserve capacity against concurrent instances. - Include orders placed by another strategy using the same account. Therefore, the limit restricts successful orders during one invocation rather than total concurrent exposure. Repeated or overlapping execution can exceed the configured limit by an arbitrary amount. ### Attack Path 1. Live mode is invoked while qualifying violations are present. 2. The program initializes `placed = ...[truncated 809 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:239
Finding

Multi-Leg Arbitrage Is Executed as Independent Non-Atomic Trades

Content
View full analysis
MIN_VIOLATION: # Higher strike is overpriced OR lower strike is underpriced # Trade: sell NO on the higher-strike (overpriced), buy YES on the lower-strike (underpriced) opportunities.append(( hi_pt.market, "no", violation, f"Monotonicity break: P(>{hi_strike:,})={hi_pt.price:.1%} > P(>{lo_strike:,})={lo_pt.price:.1%} | " f"violation={violation:.1%} — {hi_pt.market.question[:55]}" )) opportunities.append(( lo_pt.market, "yes", violation, f"Monotonicity break: P(>{lo_strike:,})={lo_pt.price:.1%} < P(>{hi_strike:,})={hi_pt.price:.1%} | " f"violation={violation:.1%} — {lo_pt.market.question[:55]}" )) ``` The related legs are then flattened by market ID: ```python all_opps: dict[str, tuple] = {} for curve_key, points in curves.items(): if len(points) < 2: continue violations = find_violations(points) for market, side, mag, reason in violations: mid = getattr(market, "id", None) if not mid: continue existing = all_opps.get(mid) if existing is None or mag > existing[2]: all_opps[mid] = (market, side, mag, reason) ``` Each surviving opportunity is independently validated and submitted: ```python 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( ...[truncated 2399 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
clawhub.json:7
Finding

Privileged Trading SDK Dependency Is Unpinned

Content
View full analysis
Remediation
View remediation
" ] ``` Additional supply-chain controls should include: - Maintain a lock file or constraints file with cryptographic hashes. - Verify package provenance and release signatures where available. - Review dependency changes before upgrading. - Run the SDK with the least-privileged trading credential possible. - Restrict outbound network access to approved API destinations. - Rotate `SIMMER_API_KEY` after any suspected dependency compromise. - Test upgrades in paper mode before allowing live execution. ]]>
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)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill describes use of a high-value credential (SIMMER_API_KEY) and trading behavior, but it does not declare an explicit tool scope such as permissions or allowed-tools. That mismatch weakens least-privilege controls because an agent runtime may expose environment access more broadly than intended, increasing the chance of credential access or misuse if the skill is invoked in an unsafe context.

Content

No source excerpt is available for this finding.