Back to skill

Security audit

Polymarket Bundle Crypto Fade Trader

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed trading bot, but its live-trading safeguards and dependency controls do not fully match the authority it requests.

Review this carefully before installing for live use. Keep it in paper mode unless you have verified the YES/NO trade semantics, pinned and reviewed simmer-sdk, enforced real account-wide position and volume limits, and restricted SIMMER_API_KEY with the smallest possible trading scope and spend 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 (3)

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:24
Finding
Declared Minimum-Volume Safeguard Is Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:24`, `trader.py:185-229` **Vulnerability Type**: Missing liquidity validation before financial transactions **Risk Level**: Medium ### Vulnerable Code ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "3000")) ``` The signal validation applies spread and resolution-time checks but never validates market volume: ```python def compute_signal(market, fade_direction: str, streak_len: int) -> tuple[str | None, float, str]: """ Returns (side, size, reasoning) or (None, 0, skip_reason). Conviction-based sizing per CLAUDE.md. Fades strong directional moves: - After strong-UP streak -> expect mean reversion DOWN -> buy NO if p >= NO_THRESHOLD (i.e. the next interval is still priced Up, so we fade it) - After strong-DOWN streak -> expect mean reversion UP -> buy YES if p <= YES_THRESHOLD (i.e. the next interval is still priced Down, so we fade it) """ p = market.current_probability q = getattr(market, "question", "") # Spread gate if market.spread_cents is not None and market.spread_cents / 100 > MAX_SPREAD: return None, 0, f"Spread {market.spread_cents/100:.1%} > {MAX_SPREAD:.1%}" # Days-to-resolution gate if market.resolves_at: try: resolves = datetime.fromisoformat(market.resolves_at.replace("Z", "+00:00")) days = (resolves - datetime.now(timezone.utc)).days if days < MIN_DAYS: return None, 0, f"Only {days} days to resolve" except Exception: pass ``` ### Technical Analysis The project declares `SIMMER_MIN_VOLUME` as a risk parameter and documents it as a minimum market-volume filter. However, neither market discovery nor signal validation reads a market's volume or compares it with `MIN_VOLUME`. Consequently, the configured control has no effect. A matching market may progress to trade execution based only on probability, spread, resol ...[truncated 1378 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Determine the canonical volume field exposed by `simmer-sdk`. 2. Validate volume before calculating or submitting any signal: ```python volume = getattr(market, "volume", None) if volume is None: return None, 0, "Market volume unavailable" if float(volume) < MIN_VOLUME: return None, 0, f"Volume ${float(volume):,.2f} below minimum ${MIN_VOLUME:,.2f}" ``` 3. Fail closed when volume is absent, malformed, negative, or non-finite. 4. Prefer executable liquidity or order-book depth over aggregate historical volume where the SDK supports it. 5. Revalidate liquidity immediately before trade submission because discovery data may be stale. 6. Add tests covering unavailable volume, malformed values, boundary values, and markets below and above the configured minimum. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:310
Finding
Maximum Open-Position Limit Only Counts Orders in the Current Run<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:310-345` **Vulnerability Type**: Incomplete exposure and position-limit enforcement **Risk Level**: Medium ### Vulnerable Code ```python placed = 0 for m, fade_dir, streak_len in fade_targets: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_signal(m, fade_dir, streak_len) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, m.id) if not ok: safe_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}" safe_print(f" [trade] {side.upper()} ${size} {tag} {status} -- {reasoning[:70]}") if r.success: placed += 1 except Exception as e: safe_print(f" [error] {m.id}: {e}") ``` ### Technical Analysis The configuration describes `SIMMER_MAX_POSITIONS` as the maximum number of concurrent open positions. The implementation instead initializes `placed` to zero every time `run` executes and increments it only for successful orders during that process invocation. The program does not: - Query existing open positions - Account for positions created by prior executions - Account for positions created by another concurrent process - Reserve capacity atomically before placing an order - Prevent duplicate exposure to the same market Therefore, the control is a per-run order-count limit rather than a concurrent-position limit. Repeated or overlapping executions can exceed the advertised exposure boundary. ### Attack Path 1. The account already has one or more open positions from a previous run. 2. The trader is executed again manually or through an ex ...[truncated 1030 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query current open positions before processing opportunities. 2. Compute remaining capacity from actual account state: ```python open_positions = client.get_positions(status="open") remaining = max(0, MAX_POSITIONS - len(open_positions)) ``` 3. Stop immediately when no capacity remains. 4. Maintain a set of market IDs with existing exposure and reject duplicate trades unless explicitly allowed. 5. Re-query account state after every successful order. 6. If concurrent executions are possible, enforce the limit server-side or use an atomic lock/reservation mechanism. A local counter alone cannot prevent cross-process races. 7. Consider enforcing both: - Maximum open-position count - Maximum aggregate USDC exposure 8. Add integration tests for existing positions, repeated executions, duplicate markets, and simultaneous workers. 9. Rename the setting if the intended behavior is only a per-run order limit; otherwise, implement the documented concurrent-position behavior. ]]>

T08 · Insecure Dependencies

Error
Location
clawhub.json:6
Finding
Privileged Trading SDK Dependency Is Unpinned<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:6-10` **Vulnerability Type**: Unpinned privileged third-party dependency **Risk Level**: High ### Vulnerable Code ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] } ``` The package is imported into the trading process: ```python from simmer_sdk import SimmerClient ``` It is then supplied with the API credential: ```python _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) ``` ### Technical Analysis The project requests `simmer-sdk` without an exact version, lockfile, or integrity hash. Installation can therefore resolve to a different package release over time. This dependency executes inside the same Python process as the skill and directly receives `SIMMER_API_KEY`. It also implements market discovery, account-context access, and trade submission. A malicious or compromised future package release would consequently execute with all permissions of the skill process and could access the credential before application-level safeguards are applied. The audit found no evidence that the currently intended SDK is malicious. The vulnerability is the mutable and unverified dependency resolution combined with the dependency's high level of trust. ### Attack Path 1. The upstream package account, build system, or distribution channel is compromised, or a malicious release is otherwise published under the expected package name. 2. A fresh skill installation resolves the unversioned `simmer-sdk` requirement to that release. 3. Python imports the package when `trader.py` starts. 4. Package initialization or `SimmerClient` code executes in the skill process. 5. The package receives `SIMMER_API_KEY` and can access the same environment, filesystem, and network privileges as the trader. 6. It could exfiltrate the API key, falsify market or context data, bypass local safeguards, or submit unauthorized transactions. ### Impact ...[truncated 691 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to an exact, reviewed release: ```json "pip": [ "simmer-sdk==<reviewed-version>" ] ``` 2. Use a lockfile with cryptographic hashes where supported, such as a hash-locked requirements file generated by `pip-tools`. 3. Verify the package's publisher, source repository, release signatures, and build provenance before upgrading. 4. Review dependency changes before accepting new versions and use automated vulnerability scanning. 5. Run the trader under a dedicated, unprivileged operating-system account or isolated container. 6. Provide only the environment variables strictly required by the trader. 7. Restrict `SIMMER_API_KEY` using least-privilege controls, including venue restrictions, transaction limits, and withdrawal prohibition where supported. 8. Rotate the API key if dependency compromise is suspected. 9. Consider placing transaction limits in a trusted server-side control so a compromised client library cannot bypass them. ]]>
Vulnerability Patterns
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares access to sensitive environment data via required credentials but does not define any explicit tool scope or permissions boundary. In an agent ecosystem, this weakens least-privilege guarantees and can allow a skill with trading authority credentials to access or misuse secrets or broader capabilities than intended.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The documentation says a strong-up streak should mean-revert down, but the stated action is 'sell NO'. In Polymarket up-or-down markets, selling NO is economically equivalent to taking the UP side, which aligns with continuation rather than a downward fade. This is an active contradiction in the skill's own intent documentation.

Static analysis

No suspicious patterns detected.