T09 · Insecure Skill Coding Practices
Error
- Location
- trader.py:47
- Finding
- Cached Live Client Can Bypass Paper-Trading Mode<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:47-67` **Vulnerability Type**: Unsafe global client state and execution-mode confusion **Risk Level**: High ### Vulnerable Code ```python _client: SimmerClient | None = None 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_DIVERGENCE 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 # apply_skill_config only available in Simmer runtime ``` The cached client is subsequently used for order 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 process-global `_client` is initialized only once. The requested `live` mode is considered only while `_client is None`; subsequent calls reuse the existing client without verifying its venue or live-trading state. If `get_client(live=True)` or `run(live=True)` executes first, `_client` is configured for the real Polymarket venue and explicitly marked as live. A later `run(live=False)` in the same Python process reuses that live client even though the caller requested paper trading. This violates the documented safe-by-default guarantee. The issue is particularly relevant in managed runtimes, test harnesses, notebooks, or applications that import and invoke `run()` repeatedly rather than launching a fresh process for each execution. ### Attack Path 1. A process imports `trader.py`. 2. An invocation calls `run(live=True)`, causing `_client` to be initialized with `ve ...[truncated 842 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Do not use one process-global client for both execution modes. - Cache clients separately by immutable venue, for example with a dictionary keyed by `"sim"` and `"polymarket"`. - Alternatively, construct a new client for every `run()` invocation. - Before each call to `client.trade()`, verify that the client's effective venue and live state match the invocation's requested mode. - Fail closed if the SDK does not expose a verifiable venue or live-state property. - Avoid mutating an SDK client's internal live flag after construction when the SDK provides a dedicated live-client constructor. - Add a regression test that invokes live mode followed by paper mode in the same process and verifies that the second order is simulated. ]]>
