Back to skill

Security audit

Polymarket 48h Nba Game Structure Trader

Security checks for vulnerabilities and agentic risk

Overview

This skill is openly a trading bot, but some advertised live-trading safeguards are not actually enforced, so users should review it carefully before granting trading access.

Install only with a least-privilege Simmer/Polymarket key and account-side spending limits. Treat the advertised volume and concurrent-position safeguards as unreliable until fixed, and test in paper mode before any live use.

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:436
Finding
Configured minimum market volume safeguard is never enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:436-455` **Related Configuration**: `trader.py:51`, `SKILL.md:116`, `clawhub.json:40-52` **Vulnerability Type**: Missing enforcement of a documented financial risk control **Risk Level**: High ### Vulnerable Code ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "5000")) ``` The market validation function does not inspect market volume or compare it against `MIN_VOLUME`: ```python def valid_market(market) -> tuple[bool, str]: """Check basic market quality gates.""" p = getattr(market, "current_probability", None) if not isinstance(p, (int, float)): return False, "missing probability" spread_cents = getattr(market, "spread_cents", None) if isinstance(spread_cents, (int, float)) and spread_cents / 100 > MAX_SPREAD: return False, f"Spread {spread_cents/100:.1%} > {MAX_SPREAD:.1%}" resolves_at = getattr(market, "resolves_at", None) if resolves_at: try: resolves = datetime.fromisoformat(resolves_at.replace("Z", "+00:00")) days = (resolves - datetime.now(timezone.utc)).days if days < MIN_DAYS: return False, f"Only {days} days to resolve" except Exception: pass return True, "ok" ``` ### Technical Analysis `SIMMER_MIN_VOLUME` is documented as a minimum market-volume filter and is exposed as a configurable tunable. The application reads it into `MIN_VOLUME`, but no execution path uses that value when validating markets. Consequently, any market that has a valid probability and passes the spread and resolution-time checks can reach `client.trade()`, regardless of its liquidity or trading volume. This is particularly dangerous because the program supports real-USDC trading when invoked with `--live`. Low-volume markets are more susceptible to price manipulation and may have insufficient depth for safe execution. The presence of a documented but inactive c ...[truncated 1155 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Read the authoritative market-volume property supplied by the SDK and reject markets below the configured minimum: ```python volume = getattr(market, "volume", None) if not isinstance(volume, (int, float)): return False, "missing market volume" if volume < MIN_VOLUME: return False, f"Volume ${volume:,.2f} below minimum ${MIN_VOLUME:,.2f}" ``` 2. Confirm the exact SDK field and its units. If multiple volume fields exist, use the field representing the intended period and document that period. 3. Fail closed when volume is absent or malformed in live mode rather than treating missing data as acceptable. 4. Validate all numeric risk parameters for finite, nonnegative values before trading. 5. Add tests covering: - Volume below the threshold. - Volume exactly at the threshold. - Missing or malformed volume. - Simulation and live modes. 6. Log the validated volume with each trade decision to support operational review. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:588
Finding
Maximum concurrent position limit resets on every invocation<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:588-615` **Related Configuration**: `trader.py:54`, `SKILL.md:119`, `clawhub.json:73-85` **Vulnerability Type**: Incorrect enforcement of an account-level exposure limit **Risk Level**: High ### Vulnerable Code ```python # Execute trades on the most inconsistent legs placed = 0 for market_id, opp in sorted(all_opps.items(), key=lambda x: -x[1][2]): if placed >= MAX_POSITIONS: break market = opp[0] side, size, reasoning = compute_signal(market, opp) if not side: print(f" [skip] {reasoning}") continue ok, why = context_ok(client, market_id) if not ok: print(f" [skip] {why}") continue try: r = client.trade( market_id=market_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[:110]}") if r.success: placed += 1 except Exception as e: print(f" [error] {market_id}: {e}") ``` ### Technical Analysis The documentation describes `SIMMER_MAX_POSITIONS` as the maximum number of concurrent open positions. The implementation instead initializes a local `placed` counter to zero each time `run()` executes and increments it only for successful trades placed during that invocation. The program never retrieves the account's existing open positions. It also does not subtract existing exposure from the configured cap or prevent repeated runs from adding positions beyond that cap. Therefore, `MAX_POSITIONS` is a per-process order limit rather than an account-level concurrent-position limit. Repeated manual, scheduled, or managed-automaton execution can continually reset the counter. ### Attack P ...[truncated 1139 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query the account's authoritative open-position list before evaluating new trades. 2. Calculate remaining capacity using the account-level state: ```python open_positions = client.get_positions(status="open") open_market_ids = {position.market_id for position in open_positions} remaining_capacity = max(0, MAX_POSITIONS - len(open_positions)) ``` 3. Stop immediately when `remaining_capacity` is zero. 4. Decrement remaining capacity only after a confirmed successful order. 5. Decide and document whether multiple legs in the same market count as one position or multiple positions. 6. Skip markets that already have an open position unless intentional position increases are explicitly supported. 7. Recheck portfolio state immediately before each live order to reduce race conditions between concurrent skill instances. 8. Use an account-level lock or atomic server-side limit if multiple processes can trade simultaneously. 9. Add regression tests that begin with existing positions and confirm that repeated invocations cannot exceed the configured cap. ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:6
Finding
Unpinned third-party trading dependency receives API credentials and executes trades<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:6-10` **Related Code**: `trader.py:25`, `trader.py:62-77`, `trader.py:601-608` **Vulnerability Type**: Unpinned security-sensitive third-party dependency **Risk Level**: Medium ### Vulnerable Code The package requirement has no exact version or integrity constraint: ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] } ``` The dependency is imported and receives the API key: ```python from simmer_sdk import SimmerClient ``` ```python def get_client(live: bool = False) -> SimmerClient: global _client, MAX_POSITION, MIN_VOLUME, MAX_SPREAD, MIN_DAYS, MAX_POSITIONS global YES_THRESHOLD, NO_THRESHOLD, MIN_TRADE, MIN_INCONSISTENCY if _client is None: venue = "polymarket" if live else "sim" _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) if live: _client.live = True try: _client.apply_skill_config(SKILL_SLUG) except AttributeError: pass ``` It also controls trade submission: ```python r = client.trade( market_id=market_id, side=side, amount=size, source=TRADE_SOURCE, skill_slug=SKILL_SLUG, reasoning=reasoning, ) ``` ### Technical Analysis The project declares `simmer-sdk` without an exact version, lockfile, or integrity hash. Package resolution can therefore install a newer release than the one originally reviewed. This dependency occupies a security-sensitive trust boundary. Imported package code executes in the process, receives `SIMMER_API_KEY`, retrieves market information, applies configuration, and submits simulated or live trades. A compromised upstream release, package-index account, distribution artifact, or dependency chain could therefore access the credential and alter trading behavior. The reviewed project contains no evidence that the current `simmer-sdk` package is ...[truncated 1324 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to an exact reviewed version rather than accepting arbitrary future releases. 2. Use a dependency lockfile and require package hashes, for example through a hash-locked requirements file. 3. Verify package provenance, publisher identity, signatures, and release artifacts before updating. 4. Review both the direct package and its transitive dependency tree. 5. Introduce automated vulnerability and software-composition scans in the build process. 6. Test SDK updates in simulation mode before approving them for live deployment. 7. Grant `SIMMER_API_KEY` only the minimum required permissions and enforce account-side order and spending limits. 8. Rotate the credential immediately if dependency compromise is suspected. 9. Run the skill in a restricted environment with limited filesystem and network access so a compromised package cannot access unrelated secrets or resources. ]]>
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 declares a required high-value credential (`SIMMER_API_KEY`) and implies environment access, but it does not define an explicit tool/permission scope such as `permissions` or `allowed-tools`. That creates an avoidable least-privilege gap: an agent runtime may grant broader environment access than necessary, increasing the chance that secrets are exposed to unrelated logic, prompts, or future modifications.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The manifest explicitly requires a sensitive API credential (SIMMER_API_KEY) and configures an automated trading entrypoint, but the file provides no user-facing disclosure that the skill will connect to an external trading service and place market trades. In a trading skill, undisclosed credential use increases the risk of users granting powerful API access without understanding the external actions the agent can take, which can lead to unauthorized or unexpected trading activity and financial loss.

Static analysis

No suspicious patterns detected.