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<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:3-9` **Vulnerability Type**: Unpinned third-party dependency with access to trading credentials **Risk Level**: Medium ### Vulnerable Code ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] }, ``` ### Technical Analysis The project installs `simmer-sdk` without an exact version or package hash. This SDK receives `SIMMER_API_KEY` at runtime and controls market discovery and trade execution. Consequently, a future compromised or malicious package release could execute arbitrary installation or runtime code with the privileges of the process. Because no lockfile, exact version, or integrity hash is supplied, repeated installations can resolve to different dependency versions after the audited source code remains unchanged. ### Attack Path 1. An attacker compromises the `simmer-sdk` distribution account or publishes a malicious future release. 2. The project installation resolves the unpinned dependency to that release. 3. Package installation hooks or imported module code execute on the host. 4. The malicious package reads `SIMMER_API_KEY` from the process environment. 5. The attacker exfiltrates the credential, submits unauthorized trades, or performs other actions available to the host process. ### Impact Assessment A successful supply-chain compromise could expose the trading API key and permit unauthorized activity within the credential's scope, including real-money order submission. Arbitrary package code would also run with the operating-system privileges of the user executing the skill, potentially affecting other data and credentials accessible to that account. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `simmer-sdk` to an exact, reviewed version rather than accepting any available release. - Use a lockfile and require cryptographic package hashes during installation. - Verify the package's publisher, source repository, and release provenance. - Review upgrades before changing the pinned version. - Run the skill in an isolated environment with minimal filesystem and network permissions. - Scope and rotate `SIMMER_API_KEY`; where supported, restrict it to only the required venue, account, and trading limits. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:174
Finding
Declared Minimum-Volume Safeguard Is Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:174-218` **Vulnerability Type**: Missing liquidity validation before financial trade execution **Risk Level**: Medium ### Vulnerable Code ```python def compute_signal(market) -> 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
<![CDATA[ ## Remediation Suggestions - Determine the authoritative volume attribute exposed by the SDK and validate it before computing or submitting a signal. - Reject markets with missing, malformed, non-finite, or lower-than-required volume data. - Perform the check server-side or immediately before `client.trade()` as defense in depth. - Log the observed volume and configured threshold for every liquidity-related rejection. - Add tests proving that markets below `MIN_VOLUME`, and markets with absent volume data, cannot reach the trade call. - Confirm that the SDK's volume units and time window match the documented USD-volume interpretation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:204
Finding
Minimum-Trade Floor Can Exceed the Maximum-Position Limit<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:204-213` **Vulnerability Type**: Financial limit bypass caused by unsafe order-size calculation **Risk Level**: Medium ### Vulnerable Code ```python if p <= YES_THRESHOLD: # conviction=0 at threshold boundary, conviction=1 at p=0 — scaled by precedent bias 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} — {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
<![CDATA[ ## Remediation Suggestions - Enforce the cap after applying the floor, for example: ```python if MIN_TRADE > 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<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:47-71` **Vulnerability Type**: Trading-mode confusion caused by unsafe global client caching **Risk Level**: High ### Vulnerable Code ```python def get_client(live: bool = False) -> 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
<![CDATA[ ## Remediation Suggestions - Do not share one client across different venues. Maintain separate clients keyed by mode or create a new client for each run. - Store the selected venue alongside the cached client and reject or recreate the client whenever the requested venue differs. - Avoid mutating an SDK-internal `live` property as the primary safety boundary; construct the client explicitly with the required venue. - Before every order, assert that the client's effective venue is `sim` unless the current run explicitly requested live execution. - Include the effective client venue, rather than only the requested mode, in operational logs. - Add regression tests for `live → paper`, `paper → live`, and repeated same-mode transitions in one process. - Consider requiring a second explicit confirmation or narrowly scoped credential for live trading. ]]>
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)

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Static analysis

No suspicious patterns detected.