Back to skill

Security audit

Polymarket Social Trends Trader

Security checks for vulnerabilities and agentic risk

Overview

This skill openly performs trading, but several advertised financial safety limits are unreliable and should be reviewed before use.

Install only if you understand that this can place live financial trades when run with live mode and that several documented safeguards are not reliably enforced. Use a limited, revocable credential, keep live trading disabled until the risk-control bugs are fixed, and require a pinned or otherwise verified simmer-sdk dependency before trusting it with real funds.

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

T08 · Insecure Dependencies

Error
Location
clawhub.json:6
Finding
Unpinned privileged third-party trading dependency<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:6-10`; related credential use at `trader.py:14, 57-60` **Vulnerability Type**: Supply-chain risk from an unpinned privileged dependency **Risk Level**: High ### Vulnerable Code ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] } ``` The dependency is subsequently imported and given the API credential: ```python from simmer_sdk import SimmerClient ``` ```python _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) ``` ### Technical Analysis The project installs `simmer-sdk` without an exact version constraint or integrity hash. This package is imported into the application's process and receives a high-value API key used for trading operations. Because dependency installation is not reproducible or integrity-verified, a compromised, malicious, or unexpectedly changed package release could execute arbitrary code at import time, read environment variables, alter market information, or modify trade requests. The audited project does not itself exfiltrate the credential; the risk arises from granting an unpinned dependency privileged access. ### Attack Path 1. An attacker compromises the package distribution account, package repository, or an accepted future release of `simmer-sdk`. 2. The Skill environment installs the dependency without enforcing a reviewed version or hash. 3. Malicious package code executes when `simmer_sdk` is imported. 4. The package reads `SIMMER_API_KEY` from process memory or the environment. 5. The package can exfiltrate the key, falsify SDK responses, or modify real orders when live mode is active. ### Impact Assessment A compromised dependency executes with the same privileges as the Skill process. It could access the trading API key, inspect other process-accessible environment variables, communicate over available network channels, and manipulate simulated or live trading operations. In l ...[truncated 87 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `simmer-sdk` to a specifically audited version rather than accepting arbitrary releases. - Use a lockfile and require cryptographic hashes during dependency installation. - Verify the package publisher and source repository before upgrades. - Review changelogs and source changes before updating the pinned version. - Use a narrowly scoped, revocable trading credential with transaction and balance limits. - Isolate the dependency in a restricted runtime with minimal filesystem, environment-variable, and network access. - Add dependency scanning and provenance verification to the release process. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:316
Finding
Configured minimum market volume is not enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:32, 316-356` **Vulnerability Type**: Missing financial risk-control enforcement **Risk Level**: High ### Vulnerable Code The minimum volume is loaded as a risk parameter: ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "5000")) ``` The complete trading loop does not compare any market volume field against `MIN_VOLUME` before placing an order: ```python def run(live: bool = False) -> None: mode = "LIVE" if live else "PAPER (sim)" year = datetime.now(timezone.utc).year month = datetime.now(timezone.utc).month is_election_gridlock = (year % 2 == 0 and month >= 8) print(f"[polymarket-social-trends-trader] mode={mode} max_pos=${MAX_POSITION} min_vol=${MIN_VOLUME} max_spread={MAX_SPREAD:.0%} election_gridlock={is_election_gridlock}") client = get_client(live=live) markets = find_markets(client) print(f"[polymarket-social-trends-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-social-trends-trader] done. ...[truncated 1225 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Obtain the authoritative market-volume field before calculating or submitting a trade. - Reject markets whose volume is below `MIN_VOLUME`. - Fail closed when volume is absent, malformed, stale, or expressed in an unexpected unit. - Confirm whether the SDK exposes total, rolling, or venue-specific volume and use the metric documented by the strategy. - Apply the check immediately before order submission as well as during discovery. - Add tests for volume below, equal to, and above the threshold, plus missing and malformed volume values. - Avoid merely printing configured controls; verify through tests that every declared safeguard affects execution. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:257
Finding
Minimum-trade floor can exceed the configured maximum position<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:257-272`; relevant configuration range at `clawhub.json:14-20` **Vulnerability Type**: Incorrect order-size limit enforcement **Risk Level**: High ### Vulnerable Code ```python if p <= YES_THRESHOLD: # conviction=0 at threshold boundary, conviction=1 at p=0 — scaled by policy 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 maximum-position configuration explicitly permits values below the default minimum trade: ```json { "env": "SIMMER_MAX_POSITION", "type": "number", "default": 25, "range": [ 1, 200 ], "step": 1, "label": "Max position size (USD)" } ``` ### Technical Analysis Both order-size branches use: ```python max(MIN_TRADE, calculated_size) ``` No subsequent operation caps the result at `MAX_POSITION`. Therefore, if `MIN_TRADE` is greater than `MAX_POSITION`, the minimum floor overrides the supposed maximum. With the permitted `MAX_POSITION=1` configuration and default `MIN_TRADE=5`, every qualifying signal produces at least a $5 order, exceeding the explicit $1 limit. This contradicts the documentation that order size is capped at `MAX_POSITION`. ### Attack Path 1. A user or managed configuration sets `SIMMER_MAX_POSITION` to a value below `SIMMER_MIN_TRADE`; for example, `$1` versus the default `$5`. 2. A market meets either the YES or NO threshold. 3. Conviction-based sizing calculates an amount no greater than the configured maximum. 4. `max(MIN_TRADE, ...[truncated 374 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Validate configuration at startup and reject `MIN_TRADE > MAX_POSITION`. - Apply an explicit final cap: ```python calculated = round(conviction * MAX_POSITION, 2) size = min(MAX_POSITION, max(MIN_TRADE, calculated)) ``` - If the venue minimum exceeds the configured maximum, skip the trade rather than silently overriding the maximum. - Revalidate the final amount immediately before calling `client.trade()`. - Declare `SIMMER_MIN_TRADE` in managed metadata if users are expected to configure it. - Add unit tests for equal limits, inverted limits, boundary probabilities, and extreme environment-variable values. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:324
Finding
Maximum concurrent-position limit ignores existing positions<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:324-355` **Vulnerability Type**: Incomplete aggregate exposure control **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}") ``` ### Technical Analysis `MAX_POSITIONS` is described as the maximum number of concurrent open positions, but the implementation only counts successful orders submitted during the current invocation. The counter starts at zero on every run. The code does not query existing open positions, pending orders, or positions opened by previous executions. It also does not reserve capacity atomically before order placement. Repeated manual or automated executions can therefore accumulate substantially more than `MAX_POSITIONS` concurrent positions. ### Attack Path 1. The Skill executes and successfully opens up to `MAX_POSITIONS` positions. 2. Those positions remain open. 3. The Skill executes again manually or through an automaton. 4. `placed` resets to zero, and no existing positions are queried. 5. A second set of up to `MAX_POSITIONS` orders is submitted. 6. Repeated runs continue increasing aggregate exposure beyond the configured limit. ### Impact Assessm ...[truncated 337 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Query authoritative open positions and pending orders before market processing. - Compute remaining capacity as `MAX_POSITIONS - current_open_or_pending_count`. - Count unique market exposure according to a clearly documented definition. - Skip markets for which an open position or pending order already exists unless position increases are explicitly supported. - Refresh portfolio state after each successful order. - Use venue-side limits or atomic reservations where available to prevent concurrent runs from racing. - Add a process or distributed lock if multiple automation instances can execute simultaneously. - Test repeated runs, pending orders, partially filled orders, and concurrent invocations. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:48
Finding
Cached client can retain live-trading mode during a later paper-mode invocation<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:48-80` **Vulnerability Type**: Unsafe security-mode state caching **Risk Level**: High ### Vulnerable Code ```python def get_client(live: bool = False) -> SimmerClient: """ live=False → venue="sim" (paper trades — safe default). live=True → venue="polymarket" (real trades, only with --live flag). """ global _client, MAX_POSITION, MIN_VOLUME, MAX_SPREAD, MIN_DAYS, MAX_POSITIONS, YES_THRESHOLD, NO_THRESHOLD, MIN_TRADE if _client is None: venue = "polymarket" if live else "sim" _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))) return _client ``` ### Technical Analysis The module stores one global client in `_client`. The requested `live` mode is only evaluated when that global is `None`. Once initialized, later calls return the same object ...[truncated 1281 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Avoid a single global client for security-sensitive modes. - Cache clients separately by venue, for example with distinct `sim` and `polymarket` instances. - Alternatively, recreate the client whenever the requested mode differs from the active venue. - Make the effective venue immutable and expose a verified venue property. - Assert immediately before every trade that the client's effective venue matches the current invocation's requested mode. - Derive console status from the verified client venue rather than only from the function argument. - Require an additional explicit confirmation or separate credential for live trading. - Add regression tests for `live → paper`, `paper → live`, and repeated invocations in the same process. ]]>
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 trading execution behavior, but it does not declare any explicit tool scope or permissions boundary. In an agent ecosystem, that mismatch can lead to broader-than-expected access to environment secrets or execution features, making accidental credential exposure or unauthorized live trading more likely if the runtime grants default capabilities.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The manifest declares a required API credential (SIMMER_API_KEY) but provides no user-facing explanation of what service it authenticates to, what actions it enables, or how the secret will be used by the skill. In an agent skill that can place trades, undisclosed credential use increases the risk of users granting sensitive access without informed consent and can mask downstream API calls with financial consequences.

Static analysis

No suspicious patterns detected.