Back to skill

Security audit

Polymarket Candle Marubozu Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Polymarket trading bot, but its live/paper mode handling and risk controls could mislead users and expose them to unintended real trades.

Review carefully before installing. Use a limited, separate credential, keep autostart disabled until tested, run only in paper mode in a fresh process, and do not enable live trading until the client-mode cache bug, minimum-volume enforcement, interval-adjacency validation, and dependency pinning are fixed.

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:55
Finding
Cached Client Can Retain Live-Trading Mode During a Paper-Trading Run<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:37, 55-76` **Vulnerability Type**: `T09: Insecure Skill Coding Practices` **Risk Level**: High ### Vulnerable Code ```python _client: SimmerClient | None = None 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 global YES_THRESHOLD, NO_THRESHOLD, MIN_TRADE, MARU_THRESHOLD 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 # 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))) MARU_THRESHOLD = float(os.environ.get("SIMMER_MARU_THRESHOLD", str(MARU_THRESHOLD))) return _client ``` ### Technical Analysis The module stores a single `SimmerClient` instance in the global `_client` variable ...[truncated 1842 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not use one unqualified singleton for both venues. Maintain separate clients indexed by mode or venue: ```python _clients: dict[str, SimmerClient] = {} def get_client(live: bool = False) -> SimmerClient: venue = "polymarket" if live else "sim" if venue not in _clients: _clients[venue] = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) client = _clients[venue] if getattr(client, "venue", venue) != venue: raise RuntimeError("Trading client venue does not match requested mode") return client ``` - Alternatively, recreate the client whenever the requested mode differs from the cached client's mode. - Immediately before every call to `client.trade()`, enforce that the client's effective venue matches the requested run mode. - Avoid relying on a mutable SDK-specific `live` attribute as the only safety control. - Add regression tests that execute a live initialization followed by a paper run in the same process and verify that the second run cannot use the live venue. - Make the mode part of the immutable execution context and include the verified venue in trade logs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:25
Finding
Configured Minimum-Volume Trading Safeguard Is Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:25, 214-245, 289-342`; related declaration in `clawhub.json:34-45` **Vulnerability Type**: `T09: Insecure Skill Coding Practices` **Risk Level**: Medium ### Vulnerable Code The minimum-volume setting is loaded: ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "3000")) ``` The market discovery path accepts matching markets without checking volume: ```python def find_markets(client: SimmerClient) -> list: """Find active crypto interval markets via keyword search + get_markets fallback.""" seen, unique = set(), [] # 1. Keyword search for kw in KEYWORDS: try: for m in client.find_markets(query=kw): if m.id not in seen: seen.add(m.id) unique.append(m) except Exception as e: safe_print(f"[search] {kw!r}: {e}") # 2. Fallback: scan broad market list for interval matches try: for m in client.get_markets(limit=200): mid = getattr(m, "id", None) q = getattr(m, "question", "") if mid and mid not in seen and _INTERVAL_RE.match(q.strip()): seen.add(mid) unique.append(m) except Exception as e: safe_print(f"[fallback] get_markets: {e}") return unique ``` The execution path proceeds from signal evaluation to trade placement without applying `MIN_VOLUME`: ```python for m, maru_dir, maru_str in maru_targets: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_signal(m, maru_dir, maru_str) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, m.id) if not ok: safe_print(f" [skip] {why}") continue try: r = client.trade( market_id=m.id, side=side, amount=size, source=TRADE_SOURCE, skill_slug=SKILL_SLUG, ...[truncated 1767 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Add an explicit volume gate before signal evaluation or trade placement. - Read volume from a documented SDK field and normalize its units before comparison: ```python volume = getattr(market, "volume", None) if volume is None: return None, 0, "Market volume unavailable" if float(volume) < MIN_VOLUME: return None, 0, f"Volume ${float(volume):,.2f} below minimum ${MIN_VOLUME:,.2f}" ``` - Fail closed when volume is absent, malformed, stale, or reported in unknown units. - If the SDK distinguishes total volume, recent volume, and liquidity, select and document the metric appropriate for five-minute markets. - Apply the check as close as possible to `client.trade()` so alternate discovery or signal paths cannot bypass it. - Add tests for volume below, equal to, and above the threshold, as well as missing and nonnumeric values. - Log the validated volume and configured threshold with every accepted or rejected opportunity. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:157
Finding
Continuation Logic Treats Any Subsequent Listed Market as the Next Five-Minute Interval<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:157-167` **Vulnerability Type**: `T09: Insecure Skill Coding Practices` **Risk Level**: Medium ### Vulnerable Code ```python # Sort each group by time for key in groups: groups[key].sort(key=lambda x: x[0]) maru_targets = [] for (coin, date_str), intervals in groups.items(): n = len(intervals) if n < 2: continue for i in range(n - 1): _, maru_mkt = intervals[i] _, next_mkt = intervals[i + 1] p_maru = maru_mkt.current_probability p_next = next_mkt.current_probability ``` ### Technical Analysis The strategy depends on trading the immediately following five-minute interval. The implementation sorts all available intervals for a coin and date, then assumes adjacent list entries represent consecutive five-minute periods. It does not compare their parsed start times. If discovery omits one or more markets, list-adjacent entries may be separated by ten minutes, hours, or another arbitrary duration. The later market is nevertheless treated as the immediate continuation target. The resulting signal no longer implements the documented strategy and may rely on stale or unrelated price behavior. The parser also groups by unnormalized free-form `date_str`, but the directly exploitable correctness issue is the absence of a required five-minute difference between `start_min` values. ### Attack Path 1. Market discovery returns an incomplete sequence, such as intervals beginning at 10:50 and 11:05 while omitting 10:55 and 11:00. 2. Both records share the same parsed coin and date and are placed in the same group. 3. Sorting makes the 11:05 market immediately follow the 10:50 market in the list. 4. The 10:50 market satisfies a bullish or bearish marubozu condition. 5. The 11:05 market satisfies the continuation probability threshold. 6. The code treats 11:05 as the next interval and may place a live order based on an invalid continuation relationship ...[truncated 550 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Preserve both start times and require exact adjacency before evaluating a continuation: ```python start_min, maru_mkt = intervals[i] next_start_min, next_mkt = intervals[i + 1] if next_start_min - start_min != 5: continue ``` - Parse the market date into a normalized date and combine it with the start time into a timezone-aware timestamp. - Handle midnight transitions explicitly rather than relying only on minutes since midnight and a free-form date string. - Validate that the first interval's declared end time equals the second interval's start time. - Reject duplicate start times and ambiguous or malformed dates. - Add tests covering missing intervals, duplicates, out-of-order results, midnight boundaries, and different textual representations of the same date. ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:7
Finding
Privileged Trading SDK Dependency Is Not Version-Pinned<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:7-9` **Vulnerability Type**: `T08: Insecure Dependencies` **Risk Level**: Medium ### Vulnerable Code ```json "pip": [ "simmer-sdk" ] ``` ### Technical Analysis The project declares `simmer-sdk` without an exact version or integrity hash. Package resolution can therefore install whichever release satisfies the package manager at installation time. Builds are not reproducible, and future package updates enter the execution environment without review of this skill. This dependency is security-sensitive: `trader.py` passes `SIMMER_API_KEY` directly to `SimmerClient`, and the SDK controls market queries and order submission. A compromised package release, compromised publisher account, malicious transitive dependency, or unexpectedly incompatible update would execute with the same process privileges and receive access to the trading credential. The audit found no evidence that the currently intended `simmer-sdk` package is malicious. The finding concerns unsafe supply-chain configuration rather than a confirmed malicious dependency. ### Attack Path 1. An attacker compromises the upstream package, its publisher account, distribution pipeline, or a transitive dependency. 2. A malicious or altered `simmer-sdk` release is published under the package name used by the project. 3. A subsequent installation resolves the unpinned dependency to that release. 4. Python imports the installed package when `trader.py` starts. 5. The package executes in-process and receives `SIMMER_API_KEY` through the `SimmerClient` constructor. 6. Malicious dependency code could exfiltrate the credential, modify market data returned to the strategy, alter trade parameters, or submit unauthorized orders. ### Impact Assessment A compromised dependency would run with the privileges of the skill process. It could access environment variables available to that process, including the high-value trading API key, communicate ...[truncated 227 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `simmer-sdk` to an exact, reviewed version rather than using an unconstrained package name. - Use a lock file or hash-checked requirements file with package integrity hashes, for example through `pip --require-hashes`. - Pin and review transitive dependencies as well as the direct SDK dependency. - Obtain packages only from the expected authenticated package index and disable unintended extra indexes to reduce dependency-confusion risk. - Review release notes and source changes before upgrading the pinned version. - Run the trading process with minimal operating-system and network privileges. - Restrict the API key to only the required trading operations and impose account-side spending or position limits where supported. - Rotate the API key promptly if dependency compromise is suspected. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (2)

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill declares access to high-value environment credentials such as `SIMMER_API_KEY` and describes live trading capability, but it does not define an explicit tool/permission scope like `permissions` or `allowed-tools`. That creates an overprivileged execution model where an agent or runtime may expose environment access more broadly than intended, increasing the chance of credential misuse or unintended live-trade actions if the surrounding platform grants default capabilities.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The manifest declares a required API key for a managed automated trading skill, but the manifest itself provides no user-facing disclosure about what the credential will be used for or that the skill can autonomously place trades. In the context of a live trading bot, this increases the risk of users supplying sensitive credentials without understanding financial exposure, making accidental loss or misuse more likely.

Static analysis

No suspicious patterns detected.