Back to skill

Security audit

Polymarket Equity Markets Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Polymarket trading skill, but its live-trading safeguards are not reliable enough for the financial authority it requests.

Review carefully before installing. Use only with a tightly scoped Simmer/Polymarket credential, spending limits, and an isolated environment; avoid live mode until the volume filter, true account-wide position limits, and live-versus-paper client separation are fixed. The artifact does not show hidden persistence or exfiltration, but it can place real trades when run with live authority.

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:332
Finding

Minimum Market Volume Safeguard Is Not Enforced

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, ) ``` ### Technical Analysis `SIMMER_MIN_VOLUME` is documented as a minimum market-volume filter and is loaded into `MIN_VOLUME`, but it is only printed in the startup status message. Neither `find_markets()` nor `compute_signal()` nor the final trading loop enforces it. Consequently, a market can reach `client.trade()` solely because its probability, spread, and resolution date pass the implemented checks. The intended liquidity control provides no protection. This is especially dangerous in live mode because low-volume prediction markets can have fragile order books, unreliable displayed prices, high price impact, and elevated manipulation risk. ### Attack Path 1. An attacker creates, promotes, or influences a low-volume market whose question matches ...[truncated 809 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:336
Finding

Concurrent Position Limit Counts Only Orders Placed During the Current Run

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 documented `SIMMER_MAX_POSITIONS` setting is described as a maximum number of concurrent open positions. The implementation instead initializes `placed` to zero on every invocation and increments it only for successful orders submitted during that invocation. The code never queries the account's existing open positions. It also does not reserve capacity atomically before placing orders. Therefore, the configured limit is a per-run order count, not a concurrent-position limit. This defect is relevant even though automatic execution is disabled by default. Operators can manually invoke the trader repeatedly, configure external automation, or call `run()` from another program. Concurrent invocations can also race because each process independently sees a local count of zero. ### Attack Path 1. The account already has open positions, potentially including positions created by an earlier execution of this skill. 2. The opera ...[truncated 834 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:57
Finding

Cached Live Client Can Be Reused for a Later Paper-Mode Invocation

Content
View full analysis
SimmerClient: """ live=False → venue="sim" (paper trades — safe default). live=True → venue="polymarket" (real trades, only with --live flag). """ global _client, MAX_POSITION, MIN_VOLUME, MAX_SPREAD, MIN_DAYS, MAX_POSITIONS, YES_THRESHOLD, NO_THRESHOLD, MIN_TRADE if _client is None: venue = "polymarket" if live else "sim" _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) # Load tunable overrides set via the Simmer UI (SIMMER_* vars only). if live: _client.live = True try: _client.apply_skill_config(SKILL_SLUG) except AttributeError: pass # apply_skill_config only available in Simmer runtime # Re-read params in case apply_skill_config updated os.environ. MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", str(MAX_POSITION))) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", str(MIN_VOLUME))) MAX_SPREAD = float(os.environ.get("SIMMER_MAX_SPREAD", str(MAX_SPREAD))) MIN_DAYS = int(os.environ.get( "SIMMER_MIN_DAYS", str(MIN_DAYS))) MAX_POSITIONS = int(os.environ.get( "SIMMER_MAX_POSITIONS", str(MAX_POSITIONS))) YES_THRESHOLD = float(os.environ.get("SIMMER_YES_THRESHOLD", str(YES_THRESHOLD))) NO_THRESHOLD = float(os.environ.get("SIMMER_NO_THRESHOLD", str(NO_THRESHOLD))) MIN_TRADE = float(os.environ.get("SIMMER_MIN_TRADE", str(MIN_TRADE))) return _client ``` ### Technical Analysis The global `_client` is initialized only once. After initialization, `get_client()` returns the same object withou ...[truncated 1702 chars]
Remediation
View remediation
SimmerClient: venue = "polymarket" if live else "sim" if venue not in _clients: _clients[venue] = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) client = _clients[venue] if not live and getattr(client, "venue", venue) != "sim": raise RuntimeError("Refusing to trade: paper mode has a non-sim client") return client ``` ]]>

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
  • 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 declares access to sensitive environment-based credentials (SIMMER_API_KEY) but does not define any explicit tool scope or permission boundary. In an agent ecosystem, that omission can let the skill be invoked without clear least-privilege constraints, increasing the chance that secrets are exposed to components or prompts that should not access trading authority.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The keyword list is broad and contains many generic finance terms like IPO, earnings, inflation, recession, and volatility, which could cause the skill to activate for ordinary financial conversations rather than explicit trading requests. Because this skill can ultimately influence or initiate trading workflows, accidental invocation raises the risk of unintended market discovery, decision support, or even live-trading preparation in the wrong context.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.