Back to skill

Security audit

Polymarket Global Elections Trader

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed Polymarket trading tool, but its live-trading risk controls are weaker than documented and its trading SDK dependency is unpinned.

Install only if you are comfortable reviewing the trading logic and running it with tightly scoped Simmer/Polymarket credentials. Use paper mode first, pin or review the `simmer-sdk` version, and do not rely on the documented minimum-volume or maximum-open-position safeguards 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 (3)

T08 · Insecure Dependencies

Warning
Location
clawhub.json:7
Finding

Unpinned Third-Party Trading SDK Creates Supply-Chain Risk

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:86
Finding

Documented Minimum-Volume Trading Safeguard Is Not Enforced

Content
View full analysis
list: """Find active markets matching strategy keywords, deduplicated.""" seen, unique = set(), [] for kw in KEYWORDS: try: for m in client.find_markets(query=kw): if m.id not in seen: seen.add(m.id) unique.append(m) except Exception as e: print(f"[search] {kw!r}: {e}") return unique ``` Signal validation checks spread and resolution time, but not volume: ```python def compute_signal(market) -> tuple[str | None, float, str]: p = market.current_probability q = market.question # Spread gate if market.spread_cents is not None and market.spread_cents / 100 > MAX_SPREAD: return None, 0, f"Spread {market.spread_cents/100:.1%} > {MAX_SPREAD:.1%}" # Days-to-resolution gate if market.resolves_at: try: resolves = datetime.fromisoformat(market.resolves_at.replace("Z", "+00:00")) days = (resolves - datetime.now(timezone.utc)).days if days < MIN_DAYS: return None, 0, f"Only {days} days to resolve" except Exception: pass bias = election_bias(q) if p <= YES_THRESHOLD: conviction = min(1.0, (YES_THRESHOLD - p) / YES_THRESHOLD * bias) size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) edge = YES_THRESHOLD - p return "yes", size, f"YES {p:.0%} edge={edge:.0%} bias={bias:.2f}x size=${size} — {q[:65]}" if p >= NO_THRESHOLD: conviction = min(1.0, (p - NO_THRESHOLD) / (1 - NO_THRESHOLD) * bias) size = max(MIN_TRA ...[truncated 1908 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:341
Finding

Maximum Open-Position Limit Is Implemented Only as a Per-Run Order Counter

Content
View full analysis
= MAX_POSITIONS: break side, size, reasoning = compute_signal(m) if not side: print(f" [skip] {reasoning}") continue ok, why = context_ok(client, m.id) if not ok: print(f" [skip] {why}") continue try: r = client.trade( market_id=m.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[:70]}") if r.success: placed += 1 except Exception as e: print(f" [error] {m.id}: {e}") ``` ### Technical Analysis The configuration and documentation describe `MAX_POSITIONS` as a maximum number of concurrent open positions. The implementation never queries the portfolio for existing open positions. Instead, it initializes `placed` to zero on every invocation and limits only the number of successful orders submitted during that invocation. As a result, the configured limit is reset whenever the process restarts. It also counts orders rather than confirmed distinct open positions, so multiple orders involving the same market may not correspond to the documented portfolio-level concept. ### Attack Path 1. The account already has open positions, potentially equal to `MAX_POSITIONS`. 2. A new execution starts and initializes `placed = 0`. 3. The code does not retrieve or count the existing positions. 4. Up to `MAX_POSITIONS` additional successful orders can be submitted. 5. Each subsequent manual, cron, or external automaton invocation reset ...[truncated 683 chars]
Remediation
View remediation
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)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill declares use of a high-value environment credential (SIMMER_API_KEY) but does not define any explicit tool scope or permission boundaries. In a trading skill, missing scope declarations increase the chance that an agent runtime can access environment data more broadly than intended, which raises the risk of credential exposure or misuse if the skill is modified, composed with other skills, or executed in a permissive environment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This manifest requires the environment variable SIMMER_API_KEY, which indicates the skill accesses sensitive credentials. In this file there is no accompanying warning, description, or disclosure explaining that the skill will use an API key or interact with an external service, so users are not informed about the credential-dependent behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.