Back to skill

Security audit

Polymarket Climate Trader

Security checks for vulnerabilities and agentic risk

Overview

This trading skill is not clearly malicious, but its live-trading authority is paired with inconsistent documentation and incomplete risk-control enforcement.

Install only if you understand that live mode can place real trades using SIMMER_API_KEY. Use paper mode first, provide a least-privilege or low-funded trading credential, avoid unattended live runs, and treat the documented risk limits as unreliable until the code and manifest defaults are reconciled and the missing liquidity/portfolio checks 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:27
Finding
Market-volume and portfolio-exposure safeguards are not enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:27-35`, `trader.py:220-258` **Vulnerability Type**: Missing financial risk-control enforcement **Risk Level**: High ### Vulnerable Code ```python # Risk parameters — declared as tunables in clawhub.json, tunable from Simmer UI. # Named SIMMER_* so apply_skill_config() can load automaton-managed overrides. MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", "25")) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "1000")) MAX_SPREAD = float(os.environ.get("SIMMER_MAX_SPREAD", "0.12")) MIN_DAYS = int(os.environ.get( "SIMMER_MIN_DAYS", "0")) MAX_POSITIONS = int(os.environ.get( "SIMMER_MAX_POSITIONS", "8")) # Signal thresholds — buy YES below YES_THRESHOLD, sell NO above NO_THRESHOLD. # Position size scales with conviction, further boosted/dampened by seasonal alignment. YES_THRESHOLD = float(os.environ.get("SIMMER_YES_THRESHOLD", "0.42")) NO_THRESHOLD = float(os.environ.get("SIMMER_NO_THRESHOLD", "0.58")) MIN_TRADE = float(os.environ.get("SIMMER_MIN_TRADE", "5")) ``` ```python def run(live: bool = False) -> None: mode = "LIVE" if live else "PAPER (sim)" print(f"[polymarket-climate-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-climate-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=TR ...[truncated 2636 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce the volume threshold before signal generation or trade submission: ```python volume = getattr(m, "volume", None) if volume is None or volume < MIN_VOLUME: print(f" [skip] Volume {volume!r} below minimum ${MIN_VOLUME}") continue ``` Confirm the authoritative volume property and units in the pinned SDK version rather than assuming the field name. 2. Retrieve the account's current open positions before processing markets. 3. Calculate remaining capacity from the actual portfolio: ```python remaining_slots = max(0, MAX_POSITIONS - len(open_positions)) ``` 4. Refuse new orders when the market already has exposure unless intentional position increases are explicitly supported and bounded. 5. Add account-wide and per-market notional limits, including pending orders. 6. Recheck portfolio state immediately before trade submission to reduce race conditions between concurrent runs. 7. Use server-side limits or idempotency keys where supported so multiple processes cannot bypass client-side checks. 8. Add automated tests proving that: - Markets below `MIN_VOLUME` are rejected. - Existing positions count toward `MAX_POSITIONS`. - Repeated and concurrent invocations cannot exceed the configured exposure limits. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:85
Finding
Documented safety defaults conflict with executable configuration<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:85-91`, `SKILL.md:121-129`, `trader.py:27-35`, `clawhub.json:20-48` **Vulnerability Type**: Misleading and inconsistent financial safety configuration **Risk Level**: Medium ### Vulnerable Code and Documentation `SKILL.md:85-91` states: ```markdown | Parameter | Default | Notes | |-----------|---------|-------| | Max position size | $20 USDC | Per market | | Min market volume | $3,000 | Climate markets are less liquid | | Max bid-ask spread | 12% | Wider allowed for niche markets | | Min days to resolution | 14 | Weather requires sufficient lead time | | Max open positions | 8 | Diversify across events | ``` `SKILL.md:121-129` separately states: ```markdown | Variable | Default | Purpose | |----------|---------|---------| | `SIMMER_MAX_POSITION` | `25` | Max USDC per trade (reached at 100% conviction) | | `SIMMER_MIN_VOLUME` | `3000` | Min market volume filter (USD) | | `SIMMER_MAX_SPREAD` | `0.12` | Max bid-ask spread (0.12 = 12%) | | `SIMMER_MIN_DAYS` | `14` | Min days until market resolves | | `SIMMER_MAX_POSITIONS` | `8` | Max concurrent open positions | | `SIMMER_YES_THRESHOLD` | `0.38` | Buy YES if market price ≤ this value | | `SIMMER_NO_THRESHOLD` | `0.62` | Sell NO if market price ≥ this value | | `SIMMER_MIN_TRADE` | `5` | Floor for any trade (min USDC regardless of conviction) | ``` The executable defaults in `trader.py:27-35` are: ```python MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", "25")) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "1000")) MAX_SPREAD = float(os.environ.get("SIMMER_MAX_SPREAD", "0.12")) MIN_DAYS = int(os.environ.get( "SIMMER_MIN_DAYS", "0")) MAX_POSITIONS = int(os.environ.get( "SIMMER_MAX_POSITIONS", "8")) # Signal thresholds — buy YES below YES_THRESHOLD, sell NO above NO_THRESHOLD. # Position size scales with conviction, further boosted/dampened by seasonal alignment. YES_THRESHOLD = float(os.environ.ge ...[truncated 2936 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define every default in a single authoritative configuration source and generate documentation from it. 2. Make `SKILL.md`, `clawhub.json`, and `trader.py` agree on: - Maximum position size. - Minimum market volume. - Minimum days to resolution. - YES and NO thresholds. - Maximum concurrent positions. 3. Prefer conservative defaults for live-capable software, including a nonzero resolution-time gate and an enforced liquidity threshold. 4. Validate configuration at startup and terminate on contradictory or unsafe values. 5. Print the fully resolved runtime configuration only after `apply_skill_config()` has completed. 6. Add tests that compare documented defaults against package and executable defaults. 7. Add integration tests demonstrating that each advertised risk control changes actual order eligibility. 8. Clearly distinguish per-trade, per-market, per-run, and account-wide limits in the documentation. ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:7
Finding
Unpinned privileged trading SDK creates a supply-chain risk<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:7-9`, `trader.py:11`, `trader.py:48-64` **Vulnerability Type**: Unpinned third-party dependency with credential and trading access **Risk Level**: Medium ### Vulnerable Code `clawhub.json:7-9` declares the dependency without a version or integrity constraint: ```json "pip": [ "simmer-sdk" ] ``` `trader.py:11` imports executable code from that package: ```python from simmer_sdk import SimmerClient ``` `trader.py:48-64` passes the sensitive trading credential to the dependency and invokes its configuration logic: ```python _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) # Load tunable overrides set via the Simmer UI (SIMMER_* vars only). 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))) ``` ### Technical Analysis The package declaration resolves a mutable version of `simmer-sdk` during installation. There is no exact version pin, lockfile, or cryptographic hash in the audited project. Imported Python packages execute code in the host process and can access its environment. Here, the dependency is explicitly passed `SIMMER_API_KEY` and i ...[truncated 1814 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to an exact, audited version rather than a floating package name. 2. Record and verify cryptographic hashes, for example through a hash-locked requirements file and installation with `pip --require-hashes`. 3. Commit a reproducible lockfile covering transitive dependencies. 4. Review the source and published artifact of the pinned SDK before providing live credentials. 5. Use an isolated virtual environment or container with minimal filesystem and network permissions. 6. Scope `SIMMER_API_KEY` to the minimum necessary trading permissions, venue, account, and funding limits. 7. Use separate credentials for paper and live trading, and avoid exposing a live-capable key during simulated execution. 8. Rotate credentials immediately if dependency integrity is suspected. 9. Add dependency vulnerability and provenance checks to the release process, and require manual review before dependency upgrades. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill references environment-variable access for sensitive credentials like `SIMMER_API_KEY` but does not declare any explicit tool scope or permission boundary. In an automated trading context, undocumented env access increases the chance of over-privileged execution, accidental secret exposure, or the skill being granted broader capabilities than operators expect.

Intent-Code Divergence

Medium
Confidence
82% confidence
Finding
The documentation makes strong safety claims about paper-trading defaults and live trading requiring `--live`, while also describing cron/autostart behavior in a way that could mislead operators about whether the skill can run automatically. In a financial trading skill, ambiguous execution semantics are dangerous because users may deploy it assuming non-live behavior when configuration or runtime wiring could still trigger real trades or unattended operation.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The manifest declares an external API credential requirement (SIMMER_API_KEY) for an automated trading skill, but provides no user-facing disclosure about what external service is contacted, what permissions the key grants, or that trades may be executed automatically. In a trading context, this increases the risk of users supplying sensitive credentials without informed consent, which can lead to unauthorized market interaction, financial loss, or overbroad trust in the skill.

Static analysis

No suspicious patterns detected.