Back to skill

Security audit

Polymarket Music Entertainment Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed trading bot, but its live-trading risk limits are partly inaccurate or unenforced and it gives a trading API key to an unpinned dependency.

Use paper mode first. Before live use, pin and review simmer-sdk, use a narrowly scoped revocable key with no withdrawal authority, set server-side trade and loss limits, and fix or verify the advertised risk controls for volume, per-trade size, and open-position exposure.

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:2
Finding
Unpinned Third-Party Dependency Receives Trading Credentials and Controls Trade Execution<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:2-9`; credential handoff occurs at `trader.py:9, 51-55` **Vulnerability Type**: Unpinned security-sensitive dependency **Risk Level**: Medium ### Complete Code Snippet ```json { "emoji": "🎵", "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] }, ``` The dependency is imported and given the credential as follows: ```python from simmer_sdk import SimmerClient _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) ``` ### Technical Analysis The project declares `simmer-sdk` without an exact version constraint, lockfile, or package integrity hash. Consequently, installation may retrieve a different package release from the one that was originally reviewed. This dependency is security-sensitive: imported package code executes inside the process, receives `SIMMER_API_KEY`, performs market queries, applies remotely managed configuration, and controls trade submission. A compromised publisher account, malicious future release, or upstream package compromise could therefore affect both credential confidentiality and transaction integrity. The repository itself does not contain evidence that the current dependency is malicious. The vulnerability is the absence of reproducible and integrity-verified dependency resolution around a component entrusted with live trading authority. ### Attack Path 1. An attacker compromises the dependency's distribution account or causes a malicious release of `simmer-sdk` to become available. 2. The Skill is installed or rebuilt after that release. 3. Because no version or hash is pinned, the package installer resolves the attacker-controlled release. 4. `trader.py` imports the installed package, executing its module-level code. 5. `get_client()` passes `SIMMER_API_KEY` to the attacker-controlled `SimmerClient`. 6. The malicious package can transmit the key externally, alter market da ...[truncated 658 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to an exact, audited version rather than accepting any available release. 2. Use a lockfile or hash-verified installation mechanism so package artifacts are reproducible and tampering causes installation to fail. 3. Review the pinned package source and its transitive dependencies before enabling live trading. 4. Run the dependency with a narrowly scoped, revocable API key that cannot withdraw funds and has strict server-side transaction limits. 5. Separate paper-trading and live-trading credentials. 6. Monitor dependency advisories and require an explicit security review before upgrading the pinned version. 7. Where supported, validate trade parameters independently before handing them to the SDK and reconcile submitted orders against server-side account activity. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:219
Finding
Configured Minimum Market Volume Is Never Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:219-258`; parameter declaration at `trader.py:27` **Vulnerability Type**: Missing liquidity control **Risk Level**: Medium ### Complete Code Snippet ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "1000")) ``` The complete trading loop does not inspect market volume before submitting an order: ```python def run(live: bool = False) -> None: mode = "LIVE" if live else "PAPER (sim)" print(f"[polymarket-music-entertainment-trader] mode={mode} max_pos=${MAX_POSITION} min_vol=${MIN_VOLUME} max_spread={MAX_SPREAD:.0%} min_days={MIN_DAYS}") client = get_client(live=live) markets = find_markets(client) print(f"[polymarket-music-entertainment-trader] {len(markets)} candidate markets") placed = 0 for m in markets: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_signal(m) if not side: print(f" [skip] {reasoning}") continue ok, why = context_ok(client, m.id) if not ok: print(f" [skip] {why}") continue try: r = client.trade( market_id=m.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}" print(f" [trade] {side.upper()} ${size} {tag} {status} — {reasoning[:70]}") if r.success: placed += 1 except Exception as e: print(f" [error] {m.id}: {e}") print(f"[polymarket-music-entertainment-trader] done. {placed} orders placed.") ``` ### Technical Analysis `MIN_VOLUME` is read from the environment and displayed in the startup message, but neither `compute_signal()` nor the trading loop compare ...[truncated 1611 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Retrieve a trusted numeric volume or liquidity field for each market before computing a signal. 2. Reject markets whose volume is below `MIN_VOLUME`. 3. Fail closed when volume data is missing, stale, nonnumeric, negative, or otherwise untrusted. 4. Consider checking executable order-book depth at the intended order size rather than relying solely on historical volume. 5. Revalidate liquidity immediately before live order submission to reduce time-of-check/time-of-use risk. 6. Add tests proving that markets below the threshold and markets with absent volume data cannot reach `client.trade()`. 7. Align the documented default with the implementation and metadata so users know the actual threshold. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:180
Finding
Minimum Trade Floor Can Exceed the Maximum Position Limit<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:180-195`; conflicting configurable ranges at `clawhub.json:13-24, 88-99` **Vulnerability Type**: Financial limit bypass through unsafe configuration interaction **Risk Level**: Medium ### Complete Code Snippet ```python if p <= YES_THRESHOLD: # conviction=0 at threshold boundary, conviction=1 at p=0 — scaled by sentiment 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]}" ``` The metadata independently permits `MAX_POSITION` as low as 1 and `MIN_TRADE` as high as 100: ```json { "env": "SIMMER_MAX_POSITION", "default": 15, "range": [ 1, 200 ], "step": 1, "label": "Max position size (USD)", "type": "number" } ``` ```json { "env": "SIMMER_MIN_TRADE", "type": "number", "default": 5, "range": [ 1, 100 ], "step": 1, "label": "Min trade size (USD)" } ``` ### Technical Analysis The order size is calculated with: ```python max(MIN_TRADE, conviction * MAX_POSITION) ``` This enforces only a lower bound. It does not enforce `MAX_POSITION` as an upper bound after applying the minimum trade floor. If `MIN_TRADE` is greater than `MAX_POSITION`, every qualifying order can exceed the configured maximum position. The metadata permits this invalid relationship without validation. For example, `MAX_POSITION=1` and `MIN_TRADE=100` are both individually valid according to the declared ranges, but the resulting order size is $100 rather than the intended $1 cap. Even when `M ...[truncated 1221 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject configuration unless `0 < MIN_TRADE <= MAX_POSITION`. 2. Apply an explicit upper bound after all sizing logic: ```python computed = round(conviction * MAX_POSITION, 2) size = min(MAX_POSITION, max(MIN_TRADE, computed)) ``` 3. Validate all numeric configuration values with `math.isfinite()` and reject negative, zero, NaN, and infinite values where inappropriate. 4. Add equivalent cross-field validation to the configuration UI so invalid combinations cannot be saved. 5. Add a final independent order-validation function immediately before `client.trade()` that rejects any amount exceeding `MAX_POSITION`. 6. Add unit tests for boundary conditions, including `MIN_TRADE > MAX_POSITION`, zero conviction, extreme environment values, and both YES and NO branches. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:225
Finding
Maximum Open Positions Setting Limits Orders Per Run Instead of Portfolio Exposure<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:225-258` **Vulnerability Type**: Ineffective portfolio exposure limit **Risk Level**: Medium ### Complete Code Snippet ```python placed = 0 for m in markets: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_signal(m) if not side: print(f" [skip] {reasoning}") continue ok, why = context_ok(client, m.id) if not ok: print(f" [skip] {why}") continue try: r = client.trade( market_id=m.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}" print(f" [trade] {side.upper()} ${size} {tag} {status} — {reasoning[:70]}") if r.success: placed += 1 except Exception as e: print(f" [error] {m.id}: {e}") print(f"[polymarket-music-entertainment-trader] done. {placed} orders placed.") ``` ### Technical Analysis `MAX_POSITIONS` is documented and labeled as a maximum number of concurrent open positions. The implementation instead initializes a local `placed` counter to zero for every invocation and increments it only for successful orders during that invocation. The code never queries the account's existing open positions. It also does not reserve capacity atomically or explicitly prevent new orders in markets where exposure already exists. Therefore, the configured setting is an order-count limit per process run, not a portfolio-level position limit. Repeated manual or scheduled executions can continue adding positions. Concurrent executions are especially problematic because each process starts with an independent zero counter. ### Attack Path 1. The account already has open positions, whether created by a previous Skill run or another client. 2. The ...[truncated 1028 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query the authoritative list of current open positions before submitting any new order. 2. Compute remaining capacity as `MAX_POSITIONS - current_open_position_count` and stop when no capacity remains. 3. Define whether multiple orders in the same market count as one position and enforce that definition consistently. 4. Prevent duplicate or additive exposure unless explicitly allowed by strategy configuration. 5. Recheck portfolio capacity immediately before order submission. 6. Use server-side atomic limits, idempotency keys, or locking to prevent concurrent processes from independently consuming the same available capacity. 7. Track pending orders as exposure because they may become open positions after the check. 8. Add integration tests covering existing positions, pending orders, repeated runs, duplicate markets, and concurrent invocations. 9. Rename the setting to “maximum orders per run” only if portfolio-level enforcement is not intended; otherwise implement the documented behavior. ]]>
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
84% confidence
Finding
The skill references environment-based credentials (`SIMMER_API_KEY`) and describes live trading behavior, but it does not declare any explicit tool scope or permission boundaries. That omission weakens least-privilege controls because an agent or runtime may expose environment access more broadly than intended, increasing the chance of credential misuse or unauthorized live-trading actions if the surrounding platform permits execution.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The manifest explicitly requires a sensitive API key and configures an automated trading entrypoint, but provides no user-facing disclosure about what the credential can do, how it will be used, or the financial consequences of running the skill. In a trading skill, silent credential use increases the risk of users authorizing live market actions without informed consent, which can lead to unauthorized trades, account exposure, or unexpected financial loss.

Static analysis

No suspicious patterns detected.