Back to skill

Security audit

Polymarket Legal Regulatory Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed trading automaton, but its live-trading safeguards have gaps that could cause trades outside the user's stated safety expectations.

Install only if you are comfortable granting a Simmer/Polymarket trading key to this skill and can contain it with account-side limits. Before using live mode, pin and review simmer-sdk, verify the effective venue before every order, enforce the minimum-volume and maximum-position checks in code, and use a narrowly scoped, low-balance credential.

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)

T08 · Insecure Dependencies

Warning
Location
clawhub.json:3
Finding

Unpinned Privileged Trading SDK Dependency

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:174
Finding

Declared Minimum-Volume Safeguard Is Not Enforced

Content
View full analysis
tuple[str | None, float, str]: """ Returns (side, size, reasoning) or (None, 0, skip_reason). Conviction-based sizing with legal precedent adjustment: - Base conviction scales linearly with distance from threshold - precedent_bias() encodes documented institutional base rates — DOJ ~97% conviction, class actions ~90% settle, SCOTUS ~70% reverse - Result capped at 1.0 so size never exceeds MAX_POSITION - MIN_TRADE floor prevents trivially small orders near the boundary """ 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 = precedent_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_TRADE, round(conviction * MAX_POSITION, 2)) edge = p - NO_THRESHOLD return "no", size, f"NO YES={p:.0%} edge={edge:.0%} bias={bias:.2f}x size=${size} — { ...[truncated 1490 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:204
Finding

Minimum-Trade Floor Can Exceed the Maximum-Position Limit

Content
View full analysis
= NO_THRESHOLD: conviction = min(1.0, (p - NO_THRESHOLD) / (1 - NO_THRESHOLD) * bias) size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) edge = p - NO_THRESHOLD return "no", size, f"NO YES={p:.0%} edge={edge:.0%} bias={bias:.2f}x size=${size} — {q[:65]}" ``` ### Technical Analysis The sizing formula applies a lower bound using `max(MIN_TRADE, calculated_size)` but never applies `MAX_POSITION` as a final upper bound. Although conviction is capped at `1.0`, that cap only constrains the calculated component. If `MIN_TRADE` exceeds `MAX_POSITION`, the selected size is `MIN_TRADE`, contradicting the documented maximum-position guarantee. The relevant values are accepted from environment variables without validating their relationship. This makes the issue reachable through misconfiguration or an automaton-managed configuration source that can modify those variables. ### Attack Path 1. Set `SIMMER_MIN_TRADE` to a value greater than `SIMMER_MAX_POSITION`. 2. Start the skill so that the environment-backed values are loaded or reloaded. 3. Discover a market whose probability reaches either configured signal threshold. 4. The sizing expression selects `MIN_TRADE`. 5. A live run submits an order larger than the intended `MAX_POSITION` limit. ### Impact Assessment This bypass can cause individual live orders to exceed the operator's intended per-trade loss limit ...[truncated 236 chars]
Remediation
View remediation
MAX_POSITION: raise ValueError("SIMMER_MIN_TRADE must not exceed SIMMER_MAX_POSITION") size = min( MAX_POSITION, max(MIN_TRADE, round(conviction * MAX_POSITION, 2)), ) ``` - Validate all numeric configuration values at startup and again after `apply_skill_config()`. - Reject negative, non-finite, or out-of-range values. - Keep `SIMMER_MIN_TRADE` in the managed configuration schema and define a range compatible with `MAX_POSITION`. - Add unit tests for equal, lower, and higher `MIN_TRADE` values and assert that no computed size exceeds `MAX_POSITION`. - Apply an independent final order-size assertion immediately before `client.trade()`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:47
Finding

Cached Live Client Can Be Reused During a Paper-Mode Run

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 a single `SimmerClient` in the global `_client`. The requested `live` mode is considered only when `_client` is initially `None`. ...[truncated 1575 chars]
Remediation
View remediation
Vulnerability Patterns
  • 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
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.