Back to skill

Security audit

Polymarket Real Estate Trader

Security checks for vulnerabilities and agentic risk

Overview

This real-money trading skill is not malicious, but it needs Review because several advertised trading safeguards are incomplete or can be bypassed in normal use.

Review this carefully before installing with a funded or live-enabled Simmer/Polymarket credential. Use a limited key and low account balance, keep it in paper mode unless you intentionally pass --live, and do not rely on the advertised min-volume or max-open-position safeguards until those checks are fixed and tested. Pin the trading SDK before using it in a serious environment.

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

Execution Mode Confusion Can Reuse a Live Trading Client in Paper Mode

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 module stores one `SimmerClient` in the global `_client` variable. The client is initialized onl ...[truncated 1892 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:215
Finding

Configured Minimum Market Volume Safeguard Is Not Enforced

Content
View full analysis
None: mode = "LIVE" if live else "PAPER (sim)" print(f"[polymarket-real-estate-trader] mode={mode} max_pos=${MAX_POSITION} min_vol=${MIN_VOLUME} max_spread={MAX_SPREAD:.0%} min_days={MIN_DAYS}") client = get_client(live=live) markets = find_markets(client) print(f"[polymarket-real-estate-trader] {len(markets)} candidate markets") placed = 0 for m in markets: if placed >= 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}") ``` The safeguard is configured earlier at line 31: ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "8000")) ``` ### Technical Analysis `MIN_VOLUME` is loaded from the environment, displayed in the run banner, declared as a tunable, and documented as the minimum market-volume filter. However, neither `run()` nor `compute_signal()` compares a market's volume with `MIN_VOLUME`. The code therefore creates the appea ...[truncated 1374 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:221
Finding

Maximum Concurrent Position Limit Only Counts Orders 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 ``` ### Technical Analysis The documented `MAX_POSITIONS` control is described as limiting concurrent open positions. The implementation instead initializes `placed` to zero at the beginning of every run and increments it only for successful orders placed during that invocation. No account or portfolio query determines how many positions are already open. The counter also measures successful orders rather than unique open markets, so it is not semantically equivalent to a concurrent-position count. Repeated executions can each place up to `MAX_POSITIONS` additional orders regardless of existing exposure. Concurrent instances can make the problem worse because each process independently sees a local counter of zero and there is no shared synchronization or atomic portfolio-limit enforcement. ### Attack Path 1. The account already has open positions from a prior run. 2. A new invocatio ...[truncated 814 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
clawhub.json:3
Finding

Trading SDK Dependency Is Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
79% confidence
Finding

The skill references environment-based credentials (SIMMER_API_KEY) and trading execution behavior, but it does not declare an explicit tool/permission scope such as permissions or allowed-tools. That creates ambiguity about what runtime capabilities the agent is expected to use and weakens least-privilege controls, increasing the chance of unintended environment access or misuse of high-value trading credentials.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This JSON manifest defines execution settings such as autostart and entrypoint, but it does not describe what user request or context should activate the skill. For manifest files, missing specificity on trigger scope can lead to ambiguous or overly broad invocation behavior if the surrounding platform relies on manifest metadata to route skills.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
34% confidence
Finding

The manifest requires a specific API credential, but there is no natural-language language or locale policy present in this file. This does not clearly establish a policy violation, so confidence is low.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.