Back to skill

Security audit

Polymarket Candle Cross Asset Divergence Trader

Security checks for vulnerabilities and agentic risk

Overview

The skill is transparent about being a trading bot, but some documented risk controls are not actually enforced and its trading dependency is unpinned.

Review carefully before installing for live use. Paper mode is the default, but do not run with --live unless you are comfortable with the missing volume filter, the per-run position limit, and an unpinned dependency having access to SIMMER_API_KEY. Prefer a restricted/revocable API key and verify account-level trading limits outside this skill.

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 (3)

T08 · Insecure Dependencies

Warning
Location
clawhub.json:6
Finding
Unpinned Third-Party Trading Dependency<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:6-9` **Vulnerability Type**: Unpinned supply-chain dependency **Risk Level**: Medium ### Evidence ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] } ``` ### Technical Analysis The project declares `simmer-sdk` without a version constraint or package integrity hash. Consequently, installation can retrieve a future version different from the version reviewed or tested by the project author. This dependency operates within the trader's process, receives the `SIMMER_API_KEY`, discovers markets, and submits simulated or live trades. A compromised upstream release, malicious maintainer update, or incompatible release would therefore execute with the same environment access and trading authority as the Skill. There is no evidence in the audited files that the current package is malicious. The vulnerability is the absence of reproducible dependency pinning and integrity verification for a security-sensitive trading component. ### Attack Path 1. An attacker compromises the upstream package distribution account, repository, build pipeline, or a future package release. 2. The attacker publishes a modified `simmer-sdk` release under the expected package name. 3. A user or automation environment installs the Skill's dependencies without a version lock. 4. The package resolver downloads the attacker-controlled release. 5. The malicious dependency executes when `trader.py` imports or instantiates `SimmerClient`. 6. It can access the process environment, including `SIMMER_API_KEY`, and manipulate or redirect trading operations. ### Impact Assessment Successful exploitation would run code with the privileges of the user or automation account executing the Skill. It could expose the Simmer API credential, alter market data, falsify transaction results, or submit unauthorized trades when live trading is enabled. Files and other environment variables accessible ...[truncated 42 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to an explicitly reviewed version, for example: ```json "pip": [ "simmer-sdk==<reviewed-version>" ] ``` 2. Use a lock file that records transitive dependency versions. 3. Require package hashes during installation, such as through a hash-locked requirements file and `pip --require-hashes`. 4. Review release provenance and verify that packages originate from the intended publisher. 5. Run the dependency with the minimum necessary environment and filesystem access. 6. Use a restricted API credential with transaction limits, revocation support, and no unrelated account privileges. 7. Re-audit the dependency before changing the pinned version. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:29
Finding
Documented Minimum-Volume Safeguard Is Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:29, 70-79, 330-361` **Vulnerability Type**: Missing financial risk-control validation **Risk Level**: Medium ### Evidence The minimum-volume value is loaded initially: ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "3000")) ``` It is reloaded after applying the remote Skill configuration: ```python # 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))) DIV_THRESHOLD = float(os.environ.get("SIMMER_DIV_THRESHOLD", str(DIV_THRESHOLD))) ``` The trading loop proceeds from signal validation to order submission without comparing market volume against `MIN_VOLUME`: ```python placed = 0 for follower_mkt, btc_dir, divergence, coin, btc_p, follower_p in div_targets: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_signal( follower_mkt, btc_dir, divergence, coin, btc_p, follower_p ) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, follower_mkt.id) if not ok: safe_print(f" [skip] {why}") continue try: r = client.trade( market_id=follower_mkt.id, side=side, amount=size, source=TRADE_SOURCE, skill_slug=SKILL_SLUG, reasoning= ...[truncated 1526 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Read a validated market-volume field before signal generation or order submission. 2. Reject markets with unavailable, malformed, stale, or insufficient volume data rather than treating missing data as acceptable. 3. Add an explicit check such as: ```python volume = getattr(follower_mkt, "volume", None) if volume is None: safe_print(" [skip] Missing market volume") continue if float(volume) < MIN_VOLUME: safe_print( f" [skip] Volume ${float(volume):,.2f} below " f"minimum ${MIN_VOLUME:,.2f}" ) continue ``` 4. Confirm the SDK field's units and whether it represents total, recent, or available executable volume. 5. Revalidate volume immediately before live order submission if market objects can become stale. 6. Add automated tests proving that markets below the threshold never reach `client.trade`. 7. Update documentation if volume filtering cannot be reliably supported. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:334
Finding
Concurrent Position Limit Only Counts Orders Placed During the Current Run<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:334-366` **Vulnerability Type**: Ineffective exposure and position-limit enforcement **Risk Level**: Medium ### Evidence ```python placed = 0 for follower_mkt, btc_dir, divergence, coin, btc_p, follower_p in div_targets: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_signal( follower_mkt, btc_dir, divergence, coin, btc_p, follower_p ) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, follower_mkt.id) if not ok: safe_print(f" [skip] {why}") continue try: r = client.trade( market_id=follower_mkt.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[:70]}") if r.success: placed += 1 ``` ### Technical Analysis `MAX_POSITIONS` is described as the maximum number of concurrent open positions. The implementation does not query current account positions or outstanding orders. Instead, it initializes `placed` to zero on every invocation and increments it only for successful orders submitted during that invocation. As a result, the control is a per-run successful-order limit, not a concurrent-position limit. Repeated manual execution or managed automation can accumulate significantly more open positions than configured. Concurrent invocations also have no shared state or atomic reservation mechanism, so each process can independently submit up to the full limit. ### Attack Path 1. The account already has open positions from an earlier execution. 2. The Skill is executed again, manually or through managed automation. 3. `placed` i ...[truncated 907 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query all current open positions and relevant outstanding orders before entering the trading loop. 2. Compute remaining capacity from actual account state: ```python open_positions = client.get_open_positions() pending_orders = client.get_open_orders() current_count = count_relevant_exposure(open_positions, pending_orders) remaining = max(0, MAX_POSITIONS - current_count) ``` 3. Stop immediately when `remaining` is zero, and decrement it only after a confirmed order. 4. Define whether multiple orders in the same market constitute one position or multiple positions, then enforce that definition consistently. 5. Recheck account state immediately before each live trade to reduce race conditions. 6. Prevent concurrent executions with an account-scoped lock or use an atomic server-side risk limit. 7. Prefer server-side position and notional caps because local checks alone cannot safely coordinate multiple clients. 8. Add tests covering existing positions, pending orders, repeated runs, failed orders, and concurrent invocations. ]]>
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
91% confidence
Finding
The skill references a high-value credential (`SIMMER_API_KEY`) and implies environment-based access, but it does not declare an explicit tool scope such as `permissions` or `allowed-tools`. That creates an authorization ambiguity where an agent/runtime may grant broader environment access than necessary, increasing the chance of unintended secret exposure or misuse if the skill is executed in a permissive host.

Static analysis

No suspicious patterns detected.