Back to skill

Security audit

Polymarket 48h Equity Strike Trader

Security checks for vulnerabilities and agentic risk

Overview

This skill openly automates Polymarket trading, but its live-trading safeguards and dependency controls need review before use.

Treat this as a review-before-live-trading skill. Use a tightly limited API key, avoid enabling --live until the volume and position-limit safeguards are fixed, pin the SDK dependency, and rely on account-level trading limits rather than this skill alone.

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

Warning
Location
clawhub.json:6
Finding

Unpinned Third-Party Trading SDK Creates a Supply-Chain Risk

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:383
Finding

Configured Minimum Market 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" ``` The documentation describes the setting as an active safeguard: ```markdown | `SIMMER_MIN_VOLUME` | `5000` | Min market volume filter (USD) | ``` ### Technical Analysis `MIN_VOLUME` is initialized and refreshed from configuration, but it is never compared with any market-volume field. A market can therefore pass `valid_market()` regardless of whether its volume is below the configured minimum. Volume is a material safety property for this strategy. The strategy detects relative probability differences across related markets, but a thin order book can display prices that are stale, easily manipulated, or unavailable at the quoted size. Ignoring volume makes apparent curve violations more likely to be art ...[truncated 1182 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:516
Finding

Maximum Concurrent Position Limit Ignores Existing Account Positions

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}") ``` The setting is documented as: ```markdown | `SIMMER_MAX_POSITIONS` | `8` | Max concurrent open positions | ``` ### Technical Analysis The implementation initializes `placed` to zero on every process invocation and increments it only for successful orders submitted during that invocation. It does not query existing open positions, pending orders, or positions created by earlier runs. As a result, `MAX_POSITIONS` is a per-run successful-order limit rather than the documented maximum number of concurrent open positions. Repeated manual invocations or managed executions can each add up to the configured number of positions while previous positions remain open. A race condition is also possible if multiple Skill instances run concurrently. Every process begins with a zero local ...[truncated 1093 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
trader.py:428
Finding

Minimum Trade Setting Can Override the Maximum Position Limit

Content
View full analysis
YES_THRESHOLD: return None, 0, f"YES blocked at {p:.1%}; threshold is {YES_THRESHOLD:.0%}" conviction = min(1.0, max(0.0, (violation_mag - MIN_VIOLATION) / max(0.01, YES_THRESHOLD))) size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) return "yes", size, reason if side == "no": if p < NO_THRESHOLD: return None, 0, f"NO blocked at {p:.1%}; threshold is {NO_THRESHOLD:.0%}" conviction = min(1.0, max(0.0, (violation_mag - MIN_VIOLATION) / max(0.01, 1 - NO_THRESHOLD))) size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) return "no", size, reason ``` The configuration permits `SIMMER_MAX_POSITION` values as low as 1 and `SIMMER_MIN_TRADE` values as high as 100: ```json { "env": "SIMMER_MAX_POSITION", "type": "number", "default": 40, "range": [ 1, 200 ] }, { "env": "SIMMER_MIN_TRADE", "type": "number", "default": 5, "range": [ 1, 100 ] } ``` ### Technical Analysis The final order size is calculated with `max(MIN_TRADE, conviction * MAX_POSITION)`. If `MIN_TRADE` is greater than `MAX_POSITION`, the minimum trade value wins and the submitted amount exceeds the configured maximum position size. The declared ranges explicitly permit this contradictory state. For example, `MAX_POSITION=10` and `MIN_TRADE=100` cause an accepted signal to create a USD 100 order even though the maximum is configured as USD 10. No startup validation rejects this relationship, and the result is not clamped back to `MAX_POSITION`. ### Attack Path 1. Configuration is set so that `SIMMER_MIN_TRADE` exceeds `SIMMER_MAX_POSITION`, whether through operator error, external configuration management, or unauthorized environme ...[truncated 642 chars]
Remediation
View remediation
MAX_POSITION: raise ValueError("SIMMER_MIN_TRADE must not exceed SIMMER_MAX_POSITION") ``` 2. Clamp the final size to the maximum as a defense-in-depth measure: ```python raw_size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) size = min(MAX_POSITION, raw_size) ``` 3. Change the tunable schema or UI validation so that the minimum trade cannot be set above the selected maximum position. 4. Validate that both values are finite, positive numbers and reject `NaN`, infinity, negative values, and malformed environment input. 5. Add tests for equal values, minimum above maximum, boundary values, and runtime configuration updates. 6. Log the validated effective limits before live trading begins. ]]>
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 references environment-based credentials (SIMMER_API_KEY) and describes trading/execution behavior, but it does not declare an explicit tool/permission scope such as allowed tools or permissions. That creates an authorization ambiguity: an agent runtime may expose environment access more broadly than intended, increasing the chance that high-value credentials are available to the skill without clear least-privilege boundaries. In a trading skill, this is more sensitive because access to a live API key can directly enable financial actions.

Content

No source excerpt is available for this finding.