Back to skill

Security audit

Polymarket Twitter Weekend Drift Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed paper-by-default trading skill, but its live-trading safeguards are incomplete enough that real-funds use needs review first.

Use this in paper mode unless you have reviewed and fixed the live risk controls. Before providing a live-capable key, pin and review simmer-sdk, use a limited trading credential, and verify that market volume, total open-position limits, and min/max trade sizing are enforced server-side or in code.

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

T09 · Insecure Skill Coding Practices

Note
Location
trader.py:42
Finding
Minimum Market Volume Safeguard Is Declared but Never Enforced## Vulnerability Details **File Location**: `trader.py:42`, `trader.py:108-130`, and `trader.py:233-250` **Vulnerability Type**: Missing financial risk-control enforcement **Risk Level**: Suspicious The project documentation presents `SIMMER_MIN_VOLUME` as a minimum market-volume filter, but the trading workflow never checks a market's volume before submitting an order. **Relevant code:** ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "1000")) ``` ```python def find_markets(client: SimmerClient) -> list: seen, unique = set(), [] for kw in KEYWORDS: try: for m in client.find_markets(query=kw): q = getattr(m, 'question', '') if m.id not in seen and POST_FILTER.search(q) and BIN_PATTERN.search(q): seen.add(m.id) unique.append(m) except Exception as e: safe_print(f"[search] {kw!r}: {e}") try: for m in client.get_markets(limit=200): q = getattr(m, 'question', '') if m.id not in seen and POST_FILTER.search(q) and BIN_PATTERN.search(q): seen.add(m.id) unique.append(m) except Exception as e: safe_print(f"[fallback] {e}") return unique ``` ```python for m in markets: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_signal(m) 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, ) ``` ### Technical Analysis `MIN_VOLUME` is initialized and refreshed after skill configuration, but it is not referenced by market di ...[truncated 1522 chars]
Remediation
## Remediation Suggestions - Retrieve a trusted numeric volume field for every market before signal evaluation. - Reject markets when `market.volume < MIN_VOLUME`. - Fail closed when volume is absent, malformed, stale, or cannot be independently verified. - If the SDK exposes multiple volume measurements, document and use the intended interval, currency, and venue-specific source. - Revalidate volume immediately before live execution to reduce time-of-check/time-of-use risk. - Add automated tests proving that markets below the threshold and markets with missing volume never reach `client.trade()`. - Log the observed volume and applied threshold for auditability.

T09 · Insecure Skill Coding Practices

Note
Location
trader.py:233
Finding
Configured Maximum Open Positions Only Limits Orders Within the Current Process## Vulnerability Details **File Location**: `trader.py:233-255` **Vulnerability Type**: Portfolio-limit bypass through repeated execution **Risk Level**: Suspicious The `MAX_POSITIONS` control counts only successful orders placed during the current invocation. Existing open positions are never queried or included. **Relevant code:** ```python placed = 0 for m in markets: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_signal(m) 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 variable `placed` is initialized to zero every time `run()` executes. It tracks successful orders rather than the number of currently open portfolio positions. This conflicts with the documented meaning of `SIMMER_MAX_POSITIONS` as a concurrent open-position limit. The control can therefore be bypassed without modifying code or configuration: each new process receives a fresh allowance. It may also count an order that adds to an existing position as though it were a distinct position, while failing to account for positions opened by earlier runs or other clients. ### Attack Path 1. The trader already has up to `MAX_POSITIONS` open positions. 2. A user, scheduler, or automation mechanism invokes `trader.py --live` again. 3. ...[truncated 651 chars]
Remediation
## Remediation Suggestions - Query all current open positions from the trading venue before processing candidate markets. - Calculate the number of unique resulting positions rather than the number of successful orders. - Subtract the existing open-position count from `MAX_POSITIONS` to determine the remaining allowance. - Distinguish between increasing an existing position and opening a new position. - Recheck the position count immediately before each live trade. - Use an account-level atomic limit or server-side risk control where available, preventing concurrent processes from racing past the cap. - Add tests covering repeated runs, pre-existing positions, simultaneous processes, failed orders, and orders that add to existing positions.

T09 · Insecure Skill Coding Practices

Note
Location
trader.py:201
Finding
Minimum Trade Floor Can Override the Maximum Position Limit## Vulnerability Details **File Location**: `trader.py:201-220` and `clawhub.json:20-24,67-71` **Vulnerability Type**: Financial limit bypass caused by inconsistent configuration constraints **Risk Level**: Suspicious The position-sizing expression applies `MIN_TRADE` as an unconditional floor but does not clamp the result back to `MAX_POSITION`. **Relevant code:** ```python if p <= YES_THRESHOLD: 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%} drift={drift_pct:+.0%} " f"bias={bias:.1f}x ${size} -- {person['key']} {bl}-{bu}" ) 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 {p:.0%} edge={edge:.0%} drift={drift_pct:+.0%} " f"bias={bias:.1f}x ${size} -- {person['key']} {bl}-{bu}" ) ``` ```json { "env": "SIMMER_MAX_POSITION", "default": 40, "range": [1, 200], "step": 1, "label": "Max position size (USD)", "type": "number" }, { "env": "SIMMER_MIN_TRADE", "type": "number", "default": 5, "range": [1, 100], "step": 1, "label": "Min trade size (USD)" } ``` ### Technical Analysis The independently valid configuration ranges permit `SIMMER_MAX_POSITION=1` and `SIMMER_MIN_TRADE=100`. Under those settings, both sizing branches calculate: ```python size = max(100, calculated_amount) ``` The submitted amount is therefore at least `$100`, despite the configured maximum position being `$1`. The same violation occurs whenever `MIN_TRADE` exceeds `MAX_POSITION`. This is a broken security invariant: the maximum is expected to be an upper bound regardless of conviction or mini ...[truncated 1046 chars]
Remediation
## Remediation Suggestions - Validate configuration after loading it and reject startup unless `0 < MIN_TRADE <= MAX_POSITION`. - Clamp the final amount defensively: ```python calculated = round(conviction * MAX_POSITION, 2) size = min(MAX_POSITION, max(MIN_TRADE, calculated)) ``` - Do not place a trade when venue minimums exceed the configured maximum; report that the risk policy prevents execution. - Add a relational constraint to the configuration interface rather than relying only on independent numeric ranges. - Validate all environment-derived values for finiteness, positivity, and permitted bounds. - Add tests for equal limits, inverted limits, zero or negative values, non-finite floats, and both YES and NO sizing branches.

T08 · Insecure Dependencies

Note
Location
clawhub.json:6
Finding
Unpinned Third-Party Trading SDK Receives a Live-Capable API Credential## Vulnerability Details **File Location**: `clawhub.json:6-9` and `trader.py:16,67-78` **Vulnerability Type**: Unpinned privileged dependency **Risk Level**: Suspicious The project installs `simmer-sdk` without an exact version or integrity hash. The imported package is then given `SIMMER_API_KEY` and controls market discovery and trade submission. **Relevant code:** ```json "requires": { "env": [ "SIMMER_API_KEY" ], "pip": [ "simmer-sdk" ] } ``` ```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 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 ``` ### Technical Analysis An unversioned package declaration allows installation behavior to change over time without any project modification or renewed review. Python packages can execute code during installation and import. After import, this SDK is explicitly supplied with the API key and trusted to perform network and trading operations. The audit found no evidence that the currently intended SDK is malicious. The issue is supply-chain exposure: a compromised publisher account, malicious future release, or unexpected dependency resolution could introduce code with access to the credential and live trading workflow. ### Attack Path 1. An attacker compromises the package publication channel, maintainer account, build pipeline, or a relevant transitive dependency. 2. A modified release becomes the version selected by the unp ...[truncated 907 chars]
Remediation
## Remediation Suggestions - Pin `simmer-sdk` to an exact version that has been reviewed. - Install it using a lockfile or requirements file containing cryptographic hashes. - Lock and review transitive dependencies as well as the direct package. - Use a private, controlled package index or verified artifact repository where practical. - Separate paper and live credentials, and ensure paper-mode credentials cannot authorize real trades. - Grant live keys only the minimum account and venue permissions required. - Rotate the key after any suspected dependency compromise. - Run dependency installation and execution in an isolated environment with restricted filesystem and network access. - Monitor dependency advisories and require a new review before upgrading the pinned version.
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill declares that it requires the SIMMER_API_KEY environment variable but does not define any explicit tool scope such as permissions or allowed-tools. That creates an implicit capability boundary: an agent may access environment data without a clearly declared policy, increasing the risk of unintended secret exposure or broader runtime access than the skill metadata suggests.

Static analysis

No suspicious patterns detected.