Back to skill

Security audit

Polymarket Whale Momentum Trader

Security checks for vulnerabilities and agentic risk

Overview

This skill is not clearly malicious, but it needs review because its live trading safeguards can fail or exceed the user's intended limits.

Review carefully before installing for live use. Keep it in paper mode unless you accept the trading risks, use a dedicated low-limit API key, pin and review simmer-sdk, and do not rely on the documented max-position, min-volume, market-matching, slippage, or flip-flop controls until they are fixed.

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

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:432
Finding

Concurrent position limit only counts orders placed during the current run

Content
View full analysis
= MAX_POSITIONS: break safe_print( f" [signal] {direction.upper()} x{whale_count} whales " f"vol=${volume:.0f} — {title[:60]}" ) # Find matching market in Simmer market = find_matching_market(client, title) if not market: safe_print(f" [skip] No matching Simmer market found") continue # Compute whale-boosted signal side, size, reasoning = compute_whale_boosted_signal(market, direction, whale_count) if not side: safe_print(f" [skip] {reasoning}") continue # Context safety checks ok, why = context_ok(client, market.id) if not ok: safe_print(f" [skip] {why}") continue # Place trade 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[:70]}") if r.success: placed += 1 ``` ### Technical Analysis `MAX_POSITIONS` is documented as a maximum number of concurrent positions, but the implementation initializes `placed` to zero on every invocation. It counts only successful orders submitted during that invocation and never retrieves existing positions from the trading account. Consequently, the control is a per-run order limit rather than a concurrent-position limit. It also does not prevent repeated exposure to the same mar ...[truncated 816 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:29
Finding

Configured minimum market-volume safeguard is never enforced

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:365
Finding

Flip-flop and slippage safeguards fail open when context retrieval fails

Content
View full analysis
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", []): safe_print(f" [warn] {w}") except Exception as e: safe_print(f" [ctx] {market_id}: {e}") return True, "ok" ``` ### Technical Analysis The function treats absent context as safe and catches every exception before returning approval. Network failures, authentication errors, malformed SDK responses, schema changes, and service outages therefore bypass both the flip-flop and slippage controls. These checks are safety prerequisites for live orders. Returning `True` when they cannot be evaluated is a fail-open design. ### Attack Path 1. A market reaches the context-validation stage. 2. Context retrieval fails because of a network interruption, SDK error, malformed response, or unavailable service. 3. The broad `except Exception` handler logs the error but does not reject the trade. 4. `context_ok` returns `(True, "ok")`. 5. The caller proceeds to `client.trade`. 6. A live order may execute despite unknown or excessive slippage or a detected-but-unavailable reversal condition. ### Impact Assessment An attacker capable of disrupting the context endpoint—or ordinary service failure—can cause safeguards to be bypassed. T ...[truncated 187 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:247
Finding

Fuzzy market lookup blindly accepts the first search result

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:329
Finding

Minimum trade setting can override the maximum position cap

Content
View full analysis
= NO_THRESHOLD and whale_direction == "no": base_conviction = (p - NO_THRESHOLD) / (1 - NO_THRESHOLD) conviction = min(1.0, base_conviction + conviction_boost) size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) edge = p - NO_THRESHOLD return "no", size, ( f"NO YES={p:.0%} edge={edge:.0%} whales={whale_count} " f"boost=+{conviction_boost:.0%} size=${size} — {q[:55]}" ) ``` The metadata permits conflicting values: ```json {"env": "SIMMER_MAX_POSITION", "type": "number", "default": 40, "range": [1, 200], "step": 1, "label": "Max position size (USD)"}, {"env": "SIMMER_MIN_TRADE", "type": "number", "default": 5, "range": [1, 100], "step": 1, "label": "Min trade size (USD)"} ``` ### Technical Analysis The final size is calculated with `max(MIN_TRADE, calculated_size)`. No validation requires `MIN_TRADE <= MAX_POSITION`. Because the metadata allows a minimum trade of up to $100 and a maximum position as low as $1, a permitted configuration can produce an order far above the advertised maximum. For example, with `MAX_POSITION=1` and `MIN_TRADE=100`, every qualifying signal is sized at $100. ### Attack Path 1. Set `SIMMER_MAX_POSITION=1`. 2. Set `SIMMER_MIN_TRADE=100`; both values are allowed by the declared metadata ranges. 3. Run the Skill and allow a qualifying aligned signal to reach position sizing. 4. ...[truncated 492 chars]
Remediation
View remediation
MAX_POSITION`. - Apply the maximum cap after the minimum floor, for example: ```python if MIN_TRADE > MAX_POSITION: raise ValueError("SIMMER_MIN_TRADE must not exceed SIMMER_MAX_POSITION") size = min(MAX_POSITION, max(MIN_TRADE, calculated_size)) ``` - Align metadata ranges so the user interface cannot produce contradictory values. - Validate all environment-derived numeric values for finiteness, positivity, and logical relationships. - Add tests for boundary configurations and assert that no returned size exceeds `MAX_POSITION`. ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:3
Finding

Security-sensitive trading SDK dependency is not version-pinned

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • 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 (3)

Tainted flow: 'req' from os.environ.get (line 96, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · trader.py (reported line 100)May include surrounding context.

python
f"{LEADERBOARD_URL}?limit={LEADERBOARD_LIMIT}",
            headers={"User-Agent": "polymarket-whale-momentum-trader/1.0"},
        )
        with urlopen(req, timeout=15) as resp:
            data = json.loads(resp.read().decode())
        # API may return list directly or nested under a key
        if isinstance(data, list):

Tainted flow: 'req' from os.environ.get (line 96, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · trader.py (reported line 142)May include surrounding context.

python
f"{LEADERBOARD_URL}?limit={LEADERBOARD_LIMIT}",
            headers={"User-Agent": "polymarket-whale-momentum-trader/1.0"},
        )
        with urlopen(req, timeout=15) as resp:
            data = json.loads(resp.read().decode())
        # API may return list directly or nested under a key
        if isinstance(data, list):

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill documentation describes network access to external APIs and environment-variable driven behavior, but it does not declare a corresponding tool scope such as permissions or allowed-tools. That mismatch weakens reviewability and sandbox enforcement because operators cannot clearly see or restrict the skill's expected capabilities before execution.

Content

No source excerpt is available for this finding.