Back to skill

Security audit

Polymarket Real Estate Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Polymarket trading skill, but its live-trading safeguards and risk limits do not reliably match what it tells users.

Install only if you are comfortable reviewing and testing the trading code first. Keep it in paper mode until the live-client caching issue and the unenforced volume/open-position limits are fixed, and restrict SIMMER_API_KEY to the smallest possible trading permissions and funding limits.

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

Cached Live Client Can Be Reused During Paper-Mode Execution

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 return _client ``` The resulting client is subsequently used to submit trades: ```python r = client.trade( market_id=m.id, side=side, amount=size, source=TRADE_SOURCE, skill_slug=SKILL_SLUG, reasoning=reasoning, ) ``` ### Technical Analysis The module stores one `SimmerClient` instance in the global `_client` variable. The selected venue is determined only when `_client` is initially created. Later calls return that same instance without verifying that its venue agrees with the current `live` argument. Consequently, after `get_client(live=True)` creates a client for the live Polymarket venue, a later call to `run(live=False)` in the same Python process receives the previously cached live client. The application prints that it is operating in paper mode, but trade requests can still be routed through the live client. This breaks the documented safety boundary that real trades require a ...[truncated 1034 chars]
Remediation
View remediation
SimmerClient: venue = "polymarket" if live else "sim" if venue not in _clients: client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) client.live = live _clients[venue] = client client = _clients[venue] if bool(getattr(client, "live", False)) != live: raise RuntimeError("Client execution mode does not match requested mode") return client ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:166
Finding

Configured Minimum Market Volume Safeguard Is Not Enforced

Content
View full analysis
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 = macro_cycle_bias(q) ``` The corresponding configuration advertises this value as a minimum-volume filter: ```json { "env": "SIMMER_MIN_VOLUME", "default": 8000, "range": [ 0, 200000 ], "step": 1000, "label": "Min market volume (USD)" } ``` ### Technical Analysis `MIN_VOLUME` is initialized, reloaded after applying skill configuration, and displayed in the runtime banner, but no market-volume property is checked in `compute_signal()`, `find_markets()`, or before `client.trade()`. This creates a false safety guarantee: administrators can configure a minimum-volume threshold and observe it in logs, while it has no effect on order eligibility. A market only needs to pass the probability, spread, resolution-time, and context checks. Low-volume prediction markets can be more susceptible to price manipulation, ...[truncated 1039 chars]
Remediation
View remediation
tuple[str | None, float, str]: try: volume = float(market.volume) except (AttributeError, TypeError, ValueError): return None, 0, "Market volume is unavailable or invalid" if volume < MIN_VOLUME: return None, 0, f"Volume ${volume:,.2f} < ${MIN_VOLUME:,.2f}" # Continue with spread, resolution, and signal checks. ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:224
Finding

Maximum Open Position Limit Only Counts Orders Placed in 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 ``` The configuration describes the parameter as follows: ```json { "env": "SIMMER_MAX_POSITIONS", "default": 6, "range": [ 1, 20 ], "step": 1, "label": "Max open positions" } ``` ### Technical Analysis The local `placed` counter starts at zero every time `run()` executes. It counts successful orders submitted during that invocation, not the number of positions already open in the account. The code does not query the current portfolio, subtract existing open positions from the configured limit, detect duplicate exposure to a market, or determine whether an order increases an existing position. Therefore, the implemented behavior is “maximum successful orders per run,” while the configuration and documentation represent it as “maximum concurrent open positions.” In scheduled, repeated, or manually invoked operation, every run receives a fresh allowance and can continue accumulating exposure. ### Attack Path 1. The account already has one or more open positions. 2. The skill sta ...[truncated 789 chars]
Remediation
View remediation
= remaining: break if market.id in open_market_ids: continue # Validate and submit the order. ``` ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:2
Finding

Security-Sensitive Trading SDK Is Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation
" ] ``` For stronger integrity, install from a hash-locked requirements file: ```text simmer-sdk== --hash=sha256: ``` ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill references use of an environment credential (SIMMER_API_KEY) and describes live trading behavior, but it does not declare an explicit tool scope or permissions boundary for environment access. That creates an avoidable least-privilege gap: a host agent may expose broader env access than necessary, increasing the risk of secret misuse or unintended access paths if the skill is extended or interpreted permissively.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest requires an API credential and is explicitly configured for a trading automaton, but it does not disclose to users that the skill can place automated trades or consume a live credential. In a financial trading context, missing disclosure increases the risk of unintended account activity, user misunderstanding, and unsafe deployment with real funds.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The compute_signal() docstring says to 'feed CME FedWatch implied probability directly into p' and describes trading divergence between the professional futures market and Polymarket retail. In implementation, p is always taken from market.current_probability and no CME FedWatch or other external macro data source is fetched or incorporated anywhere in the file.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.