Back to skill

Security audit

Polymarket Candle Morning Star Trader

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed trading bot, but some promised live-trading risk controls are not actually enforced, so it needs review before use with real funds.

Install only for paper trading unless you have reviewed and fixed the live-risk controls. Before using --live, confirm the Simmer API key is narrowly scoped, pin simmer-sdk to a reviewed version, and enforce volume, position, and interval-continuity checks in code or through external trading limits.

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:8
Finding
Unpinned Privileged Runtime Dependency<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:8-10` **Vulnerability Type**: T08: Insecure Dependencies **Risk Level**: Medium ### Complete Code Snippet ```json "pip": [ "simmer-sdk" ] ``` ### Technical Analysis The project installs `simmer-sdk` without an exact version constraint, lockfile, or package-integrity hash. This dependency is imported by `trader.py`, receives `SIMMER_API_KEY`, and provides the API used to submit simulated or live trades. Because dependency resolution is unconstrained, future installations may retrieve a newer package release whose code differs from the release originally reviewed. If the upstream package, maintainer account, or distribution channel is compromised, malicious code could execute during installation or import. No evidence indicates that the current `simmer-sdk` package is malicious. The finding concerns the lack of reproducible and integrity-protected dependency resolution. ### Attack Path 1. An attacker compromises the upstream package, its publisher account, or its distribution process. 2. The attacker publishes a malicious or backdoored `simmer-sdk` release. 3. The skill is installed or rebuilt without an exact version constraint. 4. The package manager resolves and installs the attacker-controlled release. 5. `trader.py` imports `SimmerClient` from the compromised package. 6. The package executes with the skill process's privileges and receives the trading API key when the client is initialized. 7. The compromised dependency can exfiltrate the key, manipulate market data, or alter trade requests. ### Impact Assessment A compromised dependency would execute with the same operating-system privileges as the skill. It could access environment variables available to the process, including `SIMMER_API_KEY`, and could potentially submit or modify trades within the permissions granted to that credential. The impact could include credential compromise, unauthorized financial transactions, fal ...[truncated 108 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to an explicitly reviewed version, for example with an exact `==` constraint. 2. Use a lockfile or requirements file containing cryptographic hashes. 3. Install dependencies only from an approved package index over authenticated TLS. 4. Review release notes and source changes before upgrading. 5. Run the skill under a minimally privileged operating-system account. 6. Restrict outbound network access to the endpoints needed for market discovery and trade execution. 7. Use a narrowly scoped API key and rotate it if dependency compromise is suspected. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:387
Finding
Declared Minimum-Volume Trading Safeguard Is Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:33-34, 75-76, 387-402` **Vulnerability Type**: T09: Insecure Skill Coding Practices **Risk Level**: High ### Complete Code Snippet The minimum-volume setting is loaded: ```python MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", "40")) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "3000")) ``` It is reloaded after applying runtime configuration: ```python MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", str(MAX_POSITION))) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", str(MIN_VOLUME))) ``` However, the trade path checks the signal and market context, then submits the order without validating market volume: ```python side, size, reasoning = compute_pattern_signal(pattern, target) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, target.market.id) if not ok: safe_print(f" [skip] {why}") continue try: r = client.trade( market_id=target.market.id, side=side, amount=size, source=TRADE_SOURCE, skill_slug=SKILL_SLUG, reasoning=reasoning, ) ``` ### Technical Analysis `SIMMER_MIN_VOLUME` is documented as a minimum market-volume filter and exposed as a user-adjustable risk parameter. The implementation reads the value but never compares it with an authoritative market-volume field before placing a trade. This creates a fail-open control: users may reasonably expect markets below the configured liquidity threshold to be rejected, while the actual execution path permits them whenever the pattern, spread, and context checks pass. An illiquid market may still report an acceptable quoted spread while lacking sufficient depth to execute the intended order near the displayed probability. Therefore, the existing spread check does not replace a volume or depth check. ### Attack Path 1. A low-volume crypto interval market is returned by ...[truncated 948 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Retrieve the authoritative volume or liquidity value for every target market. 2. Immediately before trade submission, reject the market when its volume is below `MIN_VOLUME`. 3. Fail closed when the volume field is missing, stale, malformed, or cannot be fetched. 4. Validate order-book depth for the intended order size rather than relying solely on aggregate volume. 5. Recheck volume, spread, and estimated slippage immediately before submitting a live order. 6. Add automated tests proving that markets below the configured threshold never reach `client.trade`. 7. Log the observed volume, configured threshold, data timestamp, and rejection reason for auditability. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:367
Finding
Concurrent Position Limit Only Counts Orders Placed During the Current Run<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:367-408` **Vulnerability Type**: T09: Insecure Skill Coding Practices **Risk Level**: High ### Complete Code Snippet ```python # Detect patterns across all groups placed = 0 for key, ivs in by_group.items(): if placed >= MAX_POSITIONS: break patterns = detect_patterns(ivs) if not patterns: safe_print(f" [{key}] no morning-star/evening-star patterns") continue for pattern, target in patterns: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_pattern_signal(pattern, target) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, target.market.id) if not ok: safe_print(f" [skip] {why}") continue try: r = client.trade( market_id=target.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[:100]}") if r.success: placed += 1 ``` ### Technical Analysis `MAX_POSITIONS` is documented as the maximum number of concurrent open positions. The implementation initializes `placed` to zero on every invocation and increments it only for successful orders placed during that invocation. The code does not query existing open positions or pending orders. Consequently, `MAX_POSITIONS` is an order-count limit per execution rather than a concurrent portfolio limit. Repeated scheduled or manual runs can continue adding positions while previous positions remain open. The control is particularly misleading because its name and ...[truncated 1089 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query authoritative open positions and pending orders before evaluating new trades. 2. Compute remaining capacity as the configured maximum minus existing positions and relevant pending orders. 3. Stop immediately when no capacity remains. 4. Update the local capacity after every successful order and reconcile it with the remote account state. 5. Define whether multiple positions in the same market count separately or as one net position, then enforce that definition consistently. 6. Prevent duplicate orders for a market that already has an open position or pending order unless explicitly permitted. 7. Use server-side or transactional risk controls where available to prevent races between concurrent skill instances. 8. Add tests covering repeated invocations, pre-existing positions, pending orders, and simultaneous runs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:190
Finding
Pattern Detection Does Not Verify Consecutive Five-Minute Intervals<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:190-229` **Vulnerability Type**: T09: Insecure Skill Coding Practices **Risk Level**: Medium ### Complete Code Snippet ```python def detect_patterns(intervals: list[IntervalMarket]) -> list[tuple[str, IntervalMarket]]: """ Walk sorted intervals and find Morning Star or Evening Star patterns. Requires 4 consecutive intervals: 3 for the pattern + 1 target to trade. Returns list of (pattern_type, next_interval_to_trade). pattern_type is "morning_star" or "evening_star". """ if len(intervals) < 4: return [] sorted_ivs = sorted(intervals, key=lambda iv: iv.sort_key) # Thresholds derived from STAR_BODY and STAR_DOJI strong_down = 0.5 - STAR_BODY # e.g. 0.43 strong_up = 0.5 + STAR_BODY # e.g. 0.57 doji_low = 0.5 - STAR_DOJI # e.g. 0.47 doji_high = 0.5 + STAR_DOJI # e.g. 0.53 opportunities = [] for i in range(3, len(sorted_ivs)): p1 = sorted_ivs[i - 3].p p2 = sorted_ivs[i - 2].p p3 = sorted_ivs[i - 1].p target = sorted_ivs[i] # Morning Star: DOWN -> doji -> UP (bottom reversal, bullish) if p1 < strong_down and doji_low <= p2 <= doji_high and p3 > strong_up: # 4th interval hasn't fully priced reversal upward if target.p < 0.55: opportunities.append(("morning_star", target)) # Evening Star: UP -> doji -> DOWN (top reversal, bearish) elif p1 > strong_up and doji_low <= p2 <= doji_high and p3 < strong_down: # 4th interval hasn't fully priced reversal downward if target.p > 0.45: opportunities.append(("evening_star", target)) ``` ### Technical Analysis The function documentation states that four consecutive intervals are required. The implementation only sorts records by their start-time key and treats adjacent list elements as consecutive candles. It does not verify that: - E ...[truncated 1401 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse each interval into a full timezone-aware start and end timestamp. 2. Require every interval to have an exact five-minute duration. 3. Before evaluating a four-record window, verify that each record's start timestamp equals the preceding record's end timestamp. 4. Reject duplicate, overlapping, malformed, or out-of-order intervals. 5. Include the year and authoritative market timezone when constructing timestamps, including daylight-saving-time handling. 6. Deduplicate records by coin, start timestamp, and end timestamp rather than only by market ID. 7. Add tests for missing intervals, midnight transitions, duplicate windows, malformed dates, and non-five-minute durations. ]]>
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
92% confidence
Finding
The skill requires a high-value credential (`SIMMER_API_KEY`) and describes trading/execution behavior, but it does not declare any explicit tool scope such as `permissions` or `allowed-tools`. That mismatch weakens least-privilege controls because an agent runtime may grant broader environment or tool access than the skill actually needs, increasing the blast radius if the skill is modified, misused, or composed with other capabilities.

Missing User Warnings

Medium
Confidence
81% confidence
Finding
This JSON manifest requires the SIMMER_API_KEY environment variable, indicating the skill uses sensitive credentials. The manifest provides no accompanying disclosure about credential handling, external service access, or the implications of supplying the key, which is a missing user warning for a manifest/markdown-adjacent skill descriptor.

Static analysis

No suspicious patterns detected.