Back to skill

Security audit

Polymarket Central Bank Trader

Security checks for vulnerabilities and agentic risk

Overview

The skill is openly a trading bot, but its live-trading risk controls are under-scoped and some documented safeguards are not actually enforced.

Review carefully before installing, especially for live trading. Use a tightly permissioned API key, keep the skill in paper mode until the dependency is pinned and the volume, exposure, and classification safeguards are fixed, and do not run it on a funded account without external account-level 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

Error
Location
clawhub.json:3
Finding
Unpinned Trading SDK Creates a Supply-Chain Execution Risk<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:3-10` **Vulnerability Type**: Unpinned third-party dependency **Risk Level**: High ### Vulnerable Code ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] }, ``` ### Technical Analysis The project declares `simmer-sdk` without an exact version constraint or an integrity hash. Consequently, separate installations can retrieve different package versions even when the reviewed project files remain unchanged. This dependency is security-sensitive because `trader.py` imports `SimmerClient`, passes it the `SIMMER_API_KEY`, uses it to discover markets, and invokes its `trade()` method. Any code executed by the package during installation, import, client initialization, or method calls runs with the same operating-system privileges and environment access as the skill. The audit did not establish that the current package is malicious. The vulnerability is that the effective executable dependency can change after review without any corresponding change to this repository. ### Attack Path 1. An attacker compromises the package publisher account, package registry, source repository, or build pipeline used for `simmer-sdk`. 2. The attacker publishes a modified release under the same package name. 3. A user or automated runtime installs the skill's dependencies without a lockfile, exact version, or hash verification. 4. The package manager selects the attacker-controlled release. 5. Malicious package code executes during installation or when `simmer_sdk` is imported. 6. The package can read `SIMMER_API_KEY`, alter market data, modify order parameters, submit unauthorized trades through the user's authorized account, or access other resources available to the process. ### Impact Assessment A compromised dependency would execute with the privileges of the skill process. Its scope may include: - Access to `SIMMER_API_KEY` and other environment variables visible ...[truncated 456 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to a specific, audited version rather than accepting any available release. 2. Generate and commit a dependency lockfile containing hashes for the complete transitive dependency graph. 3. Require hash verification during installation, such as pip's `--require-hashes`. 4. Install dependencies only from an approved package index or a controlled internal mirror. 5. Review the dependency's source, release provenance, publisher identity, and build process before upgrades. 6. Use automated dependency monitoring while requiring manual approval for security-sensitive SDK updates. 7. Restrict `SIMMER_API_KEY` to the minimum trading permissions and financial limits required by this skill. 8. Run the skill in a sandbox with restricted filesystem, environment-variable, and outbound-network access. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:404
Finding
Minimum Market Volume Safeguard Is Declared but Never Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:31, 404-436` **Vulnerability Type**: Missing liquidity validation before financial transactions **Risk Level**: High ### Vulnerable Code The safeguard is configured at line 31: ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "15000")) ``` However, the complete order-processing loop contains no volume check: ```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}") ``` ### Technical Analysis `MIN_VOLUME` is loaded from the environment and documented as the minimum market-volume filter, but neither `compute_signal()` nor the execution loop compares a market's volume against it. The value is therefore informational only and does not protect order execution. The strategy discovers markets through broad keyword searches. Without a liquidity gate, a market can reach `client.trade()` solely because its current probability crosses a configured threshold and the other limited checks pass. Low-volume markets are more susceptible to distorted prices, manipulation, poor fills, and inability to exit a position. This is particularly significant in live mode because the code submits transactions using real USDC. ## ...[truncated 1342 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate market volume before calling `compute_signal()` or `client.trade()`: ```python volume = getattr(m, "volume", None) if volume is None: print(f" [skip] Missing market volume for {m.id}") continue if not isinstance(volume, (int, float)) or volume < MIN_VOLUME: print(f" [skip] Volume ${volume:,.2f} < ${MIN_VOLUME:,.2f}") continue ``` 2. Confirm the SDK's volume field name, units, venue, and time horizon; avoid comparing incompatible values. 3. Fail closed when volume information is missing, malformed, stale, negative, or non-finite. 4. Revalidate volume and liquidity immediately before order submission to reduce time-of-check/time-of-use exposure. 5. Consider requiring order-book depth at the intended order size rather than relying only on historical volume. 6. Apply explicit maximum-slippage and minimum-fill requirements at the order API. 7. Add unit and integration tests proving that markets below the configured threshold can never reach `client.trade()`. 8. Ensure documentation and UI labels accurately describe the exact enforced metric. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:410
Finding
Configured Maximum Open Positions Only Limits Orders Per Invocation<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:410-437` **Vulnerability Type**: Incorrect enforcement of portfolio exposure limit **Risk Level**: High ### Vulnerable Code ```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-central-bank-trader] done. {placed} orders placed.") ``` ### Technical Analysis The configuration describes `SIMMER_MAX_POSITIONS` as the maximum number of concurrent open positions. The implementation instead initializes `placed` to zero each time `run()` executes and increments it only for successful orders submitted during that invocation. The skill never queries the portfolio to determine: - The number of positions already open. - Existing positions opened by earlier runs. - Pending or partially filled orders. - Whether an order adds a new position or increases an existing position. - Positions created by another simultaneous process. As a result, the control limits successful orders per invocation rather than concurrent portfolio exposure. Repeated or concurrent runs can exceed the documented ceiling. ### Attack Path 1. The live account already has open positions, or the skill completes one run and opens up to `MA ...[truncated 1334 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query the authoritative portfolio and pending-order state before market processing begins. 2. Compute remaining capacity from existing open positions and pending orders: ```python open_positions = client.get_open_positions() pending_orders = client.get_pending_orders() occupied = count_distinct_exposures(open_positions, pending_orders) remaining = max(0, MAX_POSITIONS - occupied) ``` 3. Stop immediately when no portfolio capacity remains. 4. Define whether opposite sides of the same market, partially filled orders, and additions to existing positions count toward the limit. 5. Recheck capacity immediately before each order because portfolio state can change during processing. 6. Use an account-level atomic reservation, transaction, or distributed lock to prevent concurrent runners from consuming the same capacity. 7. Enforce a separate aggregate notional-exposure limit in addition to the position count. 8. Make the server or brokerage layer reject orders that exceed account-level limits; do not rely exclusively on client-side checks. 9. Add tests covering repeated runs, existing positions, pending orders, partial fills, duplicate markets, and concurrent invocations. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:233
Finding
Classification Order Increases Exposure to Emergency Rate Markets Instead of Dampening It<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:233-270` **Vulnerability Type**: Unsafe overlapping keyword-classification precedence **Risk Level**: High ### Vulnerable Code ```python # Factor 1: question type tractability if any(w in q for w in ("rate cut", "rate hike", "rate hold", "basis points", "fed funds", "fomc decision", "rate decision", "cut rates", "hike rates", "pause", "skip", "25bp", "50bp", "75bp", "hold rates")): type_mult = 1.25 # No-surprise doctrine; futures track record ~95% near-term elif any(w in q for w in ("cpi", "pce", "inflation", "consumer price", "core inflation", "deflation", "price level", "above 3%", "below 2%", "2% target")): type_mult = 1.20 # Measured precisely; Cleveland Fed nowcast within 0.1pp elif any(w in q for w in ("year-end rate", "fed funds rate by", "rate by december", "rate cuts this year", "rate hikes this year", "how many cuts", "how many hikes", "dot plot", "end of year rate", "rate target by")): type_mult = 1.15 # Dot plot + futures curve both public and precise elif any(w in q for w in ("yield curve", "inverted", "uninvert", "10-year", "2-year spread", "10y-2y", "10y-3m", "term premium", "treasury spread")): type_mult = 1.10 # FRED daily; duration predictable from rate path elif any(w in q for w in ("recession", "gdp contraction", "two quarters", "negative growth", "nber", "sahm", "downturn")): type_mult = 0.80 # NBER lags 6-18 months; Sahm Rule unknown to retail elif any(w in q for w in ("powell", "lagarde", "chair", "governor", "fired", "resign", "reappoint", "nomination", "succession", "replace the fed", "fed chair")): ...[truncated 2950 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Evaluate specific and high-risk categories before broad generic categories: ```python if any(w in q for w in ( "emergency cut", "emergency rate", "inter-meeting", "unscheduled", "surprise cut", "surprise hike", "emergency meeting" )): type_mult = 0.70 elif any(w in q for w in ( "rate cut", "rate hike", "rate hold", "basis points", "fed funds", "fomc decision", "rate decision" )): type_mult = 1.25 ``` 2. Replace unrestricted substring matching with explicit tokenization or regular expressions using word boundaries. 3. Collect all matching categories and resolve overlaps using an explicit priority table rather than relying on source-code ordering. 4. Adopt a fail-safe rule under ambiguity: choose the lowest applicable risk multiplier when multiple categories match. 5. Log the selected category and exact matching term, not only the resulting multiplier. 6. Add regression tests for at least: - `emergency rate cut` - `inter-meeting rate cut` - `unscheduled rate hike` - `surprise rate cut` - ordinary next-meeting `rate cut` 7. Test every documented classification against overlapping phrases to ensure that risk-dampening categories cannot be shadowed by generic categories. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (5)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares access to a high-value credential (`SIMMER_API_KEY`) and describes live trading behavior, but it does not declare explicit tool scope or allowed tools. That creates ambiguous authority boundaries for an agent runtime and can permit broader-than-intended access to environment-backed capabilities, especially if the platform infers capabilities from context rather than explicit policy.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The skill states that default operation uses keyword-based market discovery without tight activation boundaries, which risks triggering on ordinary macroeconomic or financial discussion. In a trading skill, vague triggers can lead to unintended market selection, accidental trade recommendations, or execution against irrelevant events, increasing operational and financial risk.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The monitored keyword list is very broad and includes many terms common in general economics commentary, politics, and news. Because this skill can influence or execute trades, such overlap increases the chance of false-positive market discovery and mis-scoped actions, especially when paired with conviction-based sizing and optional live mode.

Description-Behavior Mismatch

Medium
Confidence
88% confidence
Finding
The keyword search list includes institutions and topics broader than the declared central-bank-decision focus, including Bank of England, BOJ, RBA, Norges Bank, recession, unemployment, NFP, and Sahm Rule. Because discovery drives what the bot can trade, these terms can pull in unrelated or weakly related markets and cause unintended automated positions outside the advertised mandate.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The trading logic expands beyond narrow central-bank decision markets into broader macro and quasi-political categories such as recession, GDP contraction, yield curve, employment, and leadership/succession questions. In an automated live-trading skill, this scope drift is dangerous because it can place real-money trades on markets with very different resolution mechanics, risk profiles, and thesis validity than the stated strategy, undermining operator expectations and risk controls.

Static analysis

No suspicious patterns detected.