Back to skill

Security audit

Polymarket 48h Sports Line Curve Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed trading skill, but its live mode can place real-money trades while several advertised risk controls are weak or not enforced.

Treat this as suitable for review or paper trading only until the live safeguards are hardened. Before using --live, verify min-volume enforcement, fail-closed context checks, position and pending-order accounting, a pinned dependency or lockfile, and a SIMMER_API_KEY scoped to the minimum trading authority you can accept.

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:300
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" ``` ### Technical Analysis The `SIMMER_MIN_VOLUME` setting is loaded into `MIN_VOLUME` and documented as a minimum market-volume filter, but `valid_market()` never reads or validates a market's volume. Consequently, the configured threshold has no effect. This is a missing security and financial-risk control rather than a direct code-execution vulnerability. A low-liquidity market can pass validation as long as its probability, spread, and resolution-time checks pass. Low-volume markets are generally easier to manipulate and may have inadequate executable liquidity even where a displayed spread appears acceptable. ### Attack Path 1. An attacker identifies or creates activity in a low-volume over/under market. 2. The attacker moves its quoted probability enough to produce an apparent curve violation. 3. The skill discovers the market and constructs a trading opportunity. 4. `valid ...[truncated 709 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:426
Finding

Maximum-Position Limit Ignores Existing and Pending Positions

Content
View full analysis
= 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 `MAX_POSITIONS` is described as a maximum number of concurrent open positions, but the implementation initializes `placed` to zero on every invocation and increments it only for successful orders during that invocation. The code does not query existing open positions or pending orders. It also provides no cross-process locking or atomic reservation mechanism. Therefore, repeated, scheduled, or overlapping executions can each place up to `MAX_POSITIONS` additional orders while the previously opened positions remain active. ### Attack Path 1. The account already has open positions from a previous execution. 2. The skill starts again, and `placed` ...[truncated 868 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:356
Finding

Trading Context and Slippage Checks Fail Open on Errors

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 is intended to block trades when recent behavior indicates a flip-flop or when estimated slippage exceeds 15%. It nevertheless authorizes trading when: - `get_market_context()` returns no context; - the context request raises an exception; - the response has an unexpected structure that raises an exception; or - relevant data is missing and defaults to a safe-looking value. The broad exception handler logs the error and then returns `(True, "ok")`. This is a fail-open design for a safety-critical live-trading control. ### Attack Path 1. A market produces a qualifying curve-violation signal. 2. The context service is unavailable, times out, or returns malformed/unexpected data. 3. `context_ok()` catches the resulting exception and only prints a diagnostic message. 4. Execution continues with `True, "ok"`. 5. The skill submits a live order without confirming the flip-flop and slippage safeguards. 6. If an attacker can influence the context respons ...[truncated 567 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
clawhub.json:6
Finding

Security-Critical Third-Party Dependency Is Unpinned

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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 (2)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill references and relies on an environment credential (SIMMER_API_KEY) but does not declare an explicit tool scope such as permissions or allowed-tools. That omission weakens least-privilege controls and can allow broader-than-necessary environment access by the agent runtime, increasing the chance that high-value credentials are exposed to components or prompts that do not need them.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest enables managed automated trading via an entrypoint without any explicit user-facing disclosure that the skill can place trades or affect a funded account. In a financial trading context, this omission is risky because users may start or install the skill without understanding that it can autonomously execute market actions, creating monetary loss and account-exposure risk.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.