Back to skill

Security audit

Polymarket 24h Geopolitics Cluster Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed automated trading skill, but its implementation can undermine paper-mode and risk-limit expectations for real-money trading.

Review before installing, especially for any live use. Treat the API key as real-money trading authority, avoid running live and paper modes in the same long-lived process, and do not rely on the advertised open-position or volume limits unless the implementation is fixed or enforced server-side.

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:52
Finding
Cached live trading client can bypass subsequent paper-mode requests<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:52-81` **Vulnerability Type**: Unsafe global client state and execution-mode confusion **Risk Level**: High ### Vulnerable Code ```python def get_client(live: bool = False) -> SimmerClient: global _client, MAX_POSITION, MIN_VOLUME, MAX_SPREAD, MIN_DAYS, MAX_POSITIONS global YES_THRESHOLD, NO_THRESHOLD, MIN_TRADE, MIN_VIOLATION if _client is None: venue = "polymarket" if live else "sim" _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) if live: _client.live = True try: _client.apply_skill_config(SKILL_SLUG) except AttributeError: pass # apply_skill_config only available in Simmer runtime 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))) MIN_VIOLATION = float(os.environ.get("SIMMER_MIN_VIOLATION", str(MIN_VIOLATION))) return _client ``` ### Technical Analysis The module stores a single `SimmerClient` in the global `_client` variable. The requested `live` mode is used only when that client is first created. Later calls return the existing client without confirming that its venue and live-trading state match the new request. If a long-lived Python process first i ...[truncated 1506 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the process-global singleton and create an explicitly configured client for each run. - Alternatively, cache clients separately by immutable venue, such as `{"sim": sim_client, "polymarket": live_client}`. - Before every trade, verify that the client's effective venue and live flag exactly match the current run's requested mode. - Fail closed if the SDK does not expose a verifiable execution mode. - Pass the expected mode directly into the execution function instead of relying on mutable module state. - Add regression tests that invoke live and simulation runs in both orders within the same process and assert that no live trade can occur during a simulation request. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:551
Finding
Position limit counts only orders placed during the current run<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:551-582` **Vulnerability Type**: Ineffective financial exposure control **Risk Level**: High ### Vulnerable Code ```python # Execute trades on best violations placed = 0 for market_id, opp in sorted(all_opps.items(), key=lambda x: -x[1][2]): if placed >= MAX_POSITIONS: break market, side_hint, violation, reason = opp side, size, reasoning = compute_signal(market, side_hint, violation, reason) if not side: print(f" [skip] {reasoning}") continue ok, why = context_ok(client, market_id) if not ok: print(f" [skip] {why}") continue try: r = client.trade( market_id=market_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}" safe_print(f" [trade] {side.upper()} ${size} {tag} {status} -- {reasoning[:110]}") if r.success: placed += 1 except Exception as e: print(f" [error] {market_id}: {e}") ``` ### Technical Analysis `MAX_POSITIONS` is described in `SKILL.md` and `clawhub.json` as the maximum number of concurrent open positions. The implementation does not query the account's current positions. Instead, it initializes `placed` to zero for every invocation and increments it only when an order succeeds during that invocation. Consequently, the control limits successful orders per run rather than concurrent account positions. Existing positions, orders from prior runs, and positions created by other processes are omitted. Repeated execution can therefore increase the actual position count far beyond the configured maximum. ### Attack Pat ...[truncated 795 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Query the authoritative account position API before submitting any orders. - Calculate remaining capacity as `MAX_POSITIONS - current_open_position_count`. - Count unique open market positions according to a clearly documented definition. - Include pending orders where they may create new positions after the check. - Re-query positions after each successful order, or use an atomic server-side risk limit where supported. - Prevent duplicate exposure to a market unless explicitly allowed by the strategy. - Coordinate concurrent skill processes through an account-level lock or server-enforced limit. - Rename the setting to “maximum orders per run” only if that is the intended behavior; do not represent it as a concurrent-position safeguard. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:353
Finding
Configured minimum market volume is never enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:353-374` **Vulnerability Type**: Missing liquidity validation **Risk Level**: Medium The unused safeguard is declared at `trader.py:35`: ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "5000")) ``` The market validation function does not inspect market volume: ```python def valid_market(market) -> tuple[bool, str]: """Check spread and days-to-resolution gates.""" p = getattr(market, "current_probability", None) if not isinstance(p, (int, float)): return False, "missing probability" spread_cents = getattr(market, "spread_cents", None) if isinstance(spread_cents, (int, float)) and spread_cents / 100 > MAX_SPREAD: return False, f"Spread {spread_cents/100:.1%} > {MAX_SPREAD:.1%}" resolves_at = getattr(market, "resolves_at", None) if resolves_at: try: resolves = datetime.fromisoformat(resolves_at.replace("Z", "+00:00")) days = (resolves - datetime.now(timezone.utc)).days if days < MIN_DAYS: return False, f"Only {days} days to resolve" except Exception: pass return True, "ok" ``` ### Technical Analysis The project documents `SIMMER_MIN_VOLUME` as a minimum market-volume filter and loads it into `MIN_VOLUME`. However, neither `valid_market()` nor market discovery compares a market's volume with this threshold. Any parseable market can proceed to signal generation and order submission regardless of its trading volume. The discrepancy gives operators a false expectation that low-liquidity markets are excluded. Low-volume prediction markets are more susceptible to price manipulation, poor depth, and large execution impact. The existing spread check does not replace a volume or depth check, particularly when volume metadata is absent or stale. ### Attack Path 1. Identify or create a low-volume geopolitical market that matches the keyword and ...[truncated 727 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Read the authoritative volume field exposed by the SDK and compare it against `MIN_VOLUME`. - Reject markets when volume is missing, malformed, stale, or below the configured threshold. - Clarify whether the limit applies to lifetime volume, rolling 24-hour volume, or available order-book depth. - Prefer executable depth near the intended order price over aggregate historical volume when the SDK provides it. - Apply the check both during discovery and immediately before order submission. - Add tests proving that markets below the threshold cannot reach `client.trade()`. - Log the observed volume and rejection reason to make enforcement auditable. ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:6
Finding
Trading SDK dependency is installed without a version or integrity pin<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:6-9` **Vulnerability Type**: Unpinned third-party dependency **Risk Level**: Medium ### Vulnerable Code ```json "pip": [ "simmer-sdk" ] ``` The dependency is imported into the trading process at `trader.py:24`: ```python from simmer_sdk import SimmerClient ``` ### Technical Analysis The project requests `simmer-sdk` without an exact version or cryptographic integrity hash. Each installation may consequently resolve to a different release than the one reviewed. Because the SDK is imported directly and receives the `SIMMER_API_KEY`, a compromised or unexpectedly changed package release would execute within the skill process and could access the credential, market data, and trade-submission channel. No evidence shows that the current package is malicious; the finding concerns the absence of dependency immutability and supply-chain controls. ### Attack Path 1. A future release of `simmer-sdk` is compromised, maliciously modified, or introduces unsafe behavior. 2. The skill is installed or rebuilt after that release becomes the resolver's selected version. 3. The package manager installs the changed dependency because no exact version or hash is required. 4. Python imports the package when `trader.py` starts. 5. Dependency code executes in the process and can interact with the API key and trading operations. ### Impact Assessment A compromised dependency could potentially read and exfiltrate `SIMMER_API_KEY`, alter market data, redirect or fabricate API operations, or submit unauthorized trades within the credential's permissions. The scope is the trading account and any additional process resources accessible to the skill runtime. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `simmer-sdk` to an exact reviewed version. - Use a lockfile or requirements file with cryptographic hashes, such as pip's `--require-hashes` mode. - Retrieve dependencies only from an explicitly configured trusted package index. - Review release notes and source changes before updating the pinned version. - Generate and retain a software bill of materials for deployed builds. - Run the dependency with only the minimum credential permissions and runtime access required. - Use reproducible builds and automated dependency-integrity verification in deployment. ]]>
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
88% confidence
Finding
The skill references use of an environment credential (`SIMMER_API_KEY`) and describes trading/execution behavior, but it does not declare any explicit tool scope or permissions boundary. That mismatch can cause the runtime or reviewers to underestimate the skill’s access to sensitive secrets and execution capabilities, increasing the chance of over-privileged deployment or accidental secret exposure.

Static analysis

No suspicious patterns detected.