Back to skill

Security audit

Polymarket Science Milestones Trader

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed trading bot, but its live-trading safeguards have concrete gaps that could cause unintended financial exposure.

Review carefully before installing or running live. Use a restricted Simmer/Polymarket credential, keep balances and per-trade limits low, avoid long-lived/imported use until the client cache bug is fixed, and require the publisher to pin simmer-sdk and enforce SIMMER_MIN_VOLUME before any live order.

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)

T08 · Insecure Dependencies

Warning
Location
clawhub.json:6
Finding
Unpinned Trading SDK Creates a Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:6-10` **Vulnerability Type**: Unpinned third-party dependency **Risk Level**: Medium ### Vulnerable Code ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] } ``` The dependency is also documented without an exact version in `SKILL.md:119-122`: ```markdown ## Dependency `simmer-sdk` by Simmer Markets (SpartanLabsXyz) - PyPI: https://pypi.org/project/simmer-sdk/ - GitHub: https://github.com/SpartanLabsXyz/simmer-sdk ``` ### Technical Analysis The project declares `simmer-sdk` without an exact version or package integrity hash. Consequently, installation can resolve to a different package release over time without any change to the audited project. This dependency operates in a particularly sensitive trust boundary. `trader.py` imports `SimmerClient`, gives it `SIMMER_API_KEY`, and uses it for market discovery, context retrieval, and real-money trade execution. A compromised, malicious, or unexpectedly incompatible future release would execute in the same Python process and inherit access to the process environment and the trading credential. This finding does not establish that the current `simmer-sdk` package is malicious. It identifies the absence of controls that bind installations to a reviewed artifact. ### Attack Path 1. An attacker compromises the dependency's publishing account, distribution channel, or a future release process. 2. The attacker publishes a malicious release under the legitimate `simmer-sdk` package name. 3. A deployment installs the project and resolves the unpinned requirement to that release. 4. Python imports the malicious package when `trader.py` starts. 5. The package executes with the application's privileges and receives or can read `SIMMER_API_KEY`. 6. It can exfiltrate the credential, alter market information, or manipulate calls intended to submit live trades. ### Impact Assessment Successful exploitation would ...[truncated 336 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `simmer-sdk` to an exact, reviewed version rather than allowing unconstrained resolution. - Use a lock file with cryptographic hashes, or an installation mechanism equivalent to `pip --require-hashes`. - Verify that packages are downloaded only from the intended official registry. - Review release provenance, maintainer ownership, and source changes before upgrading. - Test upgrades in an isolated environment before permitting access to production credentials. - Restrict the API key to the minimum account and trading privileges supported by the service. - Rotate the credential if dependency compromise is suspected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:84
Finding
Configured Minimum-Volume Trading Safeguard Is Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:84-94` **Vulnerability Type**: Missing risk-control enforcement **Risk Level**: Medium ### Vulnerable Code The configured limit is loaded at `trader.py:31-35`: ```python MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", "25")) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "5000")) MAX_SPREAD = float(os.environ.get("SIMMER_MAX_SPREAD", "0.12")) MIN_DAYS = int(os.environ.get( "SIMMER_MIN_DAYS", "14")) MAX_POSITIONS = int(os.environ.get( "SIMMER_MAX_POSITIONS", "6")) ``` Market discovery does not apply that limit: ```python def find_markets(client: SimmerClient) -> list: """Find active markets matching strategy keywords, deduplicated.""" seen, unique = set(), [] for kw in KEYWORDS: try: for m in client.find_markets(query=kw): if m.id not in seen: seen.add(m.id) unique.append(m) except Exception as e: print(f"[search] {kw!r}: {e}") return unique ``` The execution loop at `trader.py:348-379` also submits trades without checking market volume: ```python markets = find_markets(client) print(f"[polymarket-science-milestones-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, ) ``` ### Technical Analysis `SIMMER_MIN_VOLUME` is represented in the documentation and configuration as a minimum market-volume filter, but the value is only pars ...[truncated 1583 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Enforce `MIN_VOLUME` before signal computation and before every trade submission. - Convert the market volume to a validated finite numeric value and reject values below the configured threshold. - Fail closed when volume is absent, malformed, stale, or cannot be verified. - Ensure that the selected SDK volume property represents the intended unit and period. - Revalidate market liquidity immediately before submitting a live trade because discovery results may become stale. - Add tests covering volume below, equal to, and above the threshold, as well as missing and malformed values. - Consider checking executable order-book depth in addition to aggregate volume. Example defensive logic: ```python volume = getattr(market, "volume", None) try: volume = float(volume) except (TypeError, ValueError): return None, 0, "Market volume unavailable or invalid" if volume < MIN_VOLUME: return None, 0, f"Volume ${volume:,.2f} below ${MIN_VOLUME:,.2f}" ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:52
Finding
Global Client Cache Can Retain Live Trading Mode During a Later Paper Run<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:52-82` **Vulnerability Type**: Unsafe state reuse across execution modes **Risk Level**: High ### Vulnerable Code ```python _client: SimmerClient | None = None 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 ``` The displayed mode is based on the requested argument rather than the cached client's effective venue at `trader.py:342-349`: ```python def run(live: bo ...[truncated 2334 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not share one mutable client between simulation and live venues. - Maintain separate clients keyed by venue, or instantiate a new client for each run. - If caching is required, record the effective venue and recreate the client whenever it differs from the requested mode. - Verify the client's effective venue immediately before every trade. - Derive displayed mode from the verified client configuration rather than only from the caller's argument. - Require a second explicit live-mode assertion at the execution boundary. - Add regression tests for `live → paper`, `paper → live`, repeated paper runs, and repeated live runs in the same process. Example design: ```python _clients: dict[str, SimmerClient] = {} def get_client(live: bool = False) -> SimmerClient: venue = "polymarket" if live else "sim" if venue not in _clients: _clients[venue] = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) client = _clients[venue] if live and getattr(client, "venue", venue) != "polymarket": raise RuntimeError("Live client venue mismatch") if not live and getattr(client, "venue", venue) != "sim": raise RuntimeError("Paper client venue mismatch") return client ``` ]]>
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
88% confidence
Finding
The skill explicitly requires a high-value environment credential (`SIMMER_API_KEY`) but does not declare any `permissions` or `allowed-tools` scope to constrain environment access. That creates an overbroad trust boundary: if the runtime exposes environment variables generally, the skill could access more secrets than intended, and users reviewing the manifest have no explicit least-privilege declaration to validate.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This manifest requires the SIMMER_API_KEY environment variable, which indicates the skill accesses a sensitive credential. In this file there is no accompanying disclosure or warning explaining that the skill will read and use that credential, which fits the missing user warning criterion for user-visible skill descriptions/metadata.

Static analysis

No suspicious patterns detected.