Back to skill

Security audit

Polymarket Twitter Bin Decay Trader

Security checks for vulnerabilities and agentic risk

Overview

The skill is transparent about being a trading bot and defaults to paper mode, but live trading needs review because several advertised safety limits are not actually enforced.

Install only if you are comfortable reviewing and operating a live-trading bot. Keep it in paper mode unless you have audited the SDK and fixed or accepted the risk-control gaps; use a narrowly scoped key, keep live credentials separate, and verify final order size, liquidity, resolution timing, and existing open positions before using --live.

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:286
Finding
Declared Market Safety Filters Are Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:41-44`, `trader.py:286-310` **Vulnerability Type**: Missing enforcement of configured trading safeguards **Risk Level**: High ### Vulnerable Code ```python MAX_POSITION = float(os.environ.get("SIMMER_MAX_POSITION", "40")) MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "1000")) MAX_SPREAD = float(os.environ.get("SIMMER_MAX_SPREAD", "0.10")) MIN_DAYS = int(os.environ.get( "SIMMER_MIN_DAYS", "0")) MAX_POSITIONS = int(os.environ.get( "SIMMER_MAX_POSITIONS", "8")) ``` ```python def run(live: bool = False) -> None: mode = "LIVE" if live else "PAPER (sim)" safe_print(f"[twitter-bin-decay] mode={mode} max_pos=${MAX_POSITION}") client = get_client(live=live) markets = find_markets(client) safe_print(f"[twitter-bin-decay] {len(markets)} post-count bin markets found") 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 application loads `MIN_VOLUME` and `MIN_DAYS` as security and risk-management parameters, but neither value is consulted before an order is submitted. Consequently, a ...[truncated 2059 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Before signal generation or order submission, reject markets whose reported volume is below `MIN_VOLUME`. 2. Parse and validate the authoritative market resolution timestamp, then reject markets with fewer than `MIN_DAYS` remaining. 3. Treat missing or malformed volume and resolution metadata as a failed safety check in live mode rather than permitting the trade. 4. Query current account positions through the SDK before trading. 5. Count distinct existing open positions together with orders successfully created during the current run. 6. Reject any order that would cause the portfolio to exceed `MAX_POSITIONS`. 7. Recheck market metadata immediately before submitting a live order to reduce time-of-check/time-of-use risk. 8. Add tests proving that low-volume markets, out-of-window markets, and portfolios already at the position limit cannot reach `client.trade()`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:231
Finding
Minimum Trade Size Can Override the Maximum Position Limit<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:231`, `trader.py:241`, `trader.py:256`, `trader.py:266`; `clawhub.json:25-41`, `clawhub.json:68-78` **Vulnerability Type**: Unsafe financial limit calculation and inconsistent configuration ranges **Risk Level**: High ### Vulnerable Code The same unsafe expression appears in all four order-sizing branches: ```python size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) ``` The configuration permits the minimum trade to exceed the maximum position: ```json { "env": "SIMMER_MAX_POSITION", "default": 40, "range": [ 1, 200 ], "step": 1, "label": "Max position size (USD)", "type": "number" } ``` ```json { "env": "SIMMER_MIN_TRADE", "type": "number", "default": 5, "range": [ 1, 100 ], "step": 1, "label": "Min trade size (USD)" } ``` ### Technical Analysis The calculated component, `conviction * MAX_POSITION`, is bounded by `MAX_POSITION` because conviction is capped at `1.0`. However, `max(MIN_TRADE, calculated_size)` replaces that bounded value with `MIN_TRADE` whenever the configured minimum is larger. No configuration validation requires `MIN_TRADE <= MAX_POSITION`. The ranges exposed by `clawhub.json` explicitly allow inconsistent values—for example, `MAX_POSITION=1` and `MIN_TRADE=100`. Under that valid configuration, every qualifying signal produces an order of at least $100 even though the advertised maximum is $1. This error occurs in every YES and NO sizing path, so it is not limited to a specific strategy branch. ### Attack Path 1. An operator mistakenly configures `SIMMER_MIN_TRADE` above `SIMMER_MAX_POSITION`, or a party able to modify managed skill configuration supplies those values. 2. The application accepts both values without checking their relationship. 3. The skill is run with `--live`. 4. A discovered market satisfies any YES or NO signal branch. 5. The sizing expression evaluates `max(MIN_TRADE, conviction * MAX_POSITION ...[truncated 777 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Validate configuration before performing market discovery or trading: ```python if MAX_POSITION <= 0: raise ValueError("SIMMER_MAX_POSITION must be positive") if MIN_TRADE <= 0: raise ValueError("SIMMER_MIN_TRADE must be positive") if MIN_TRADE > MAX_POSITION: raise ValueError( "SIMMER_MIN_TRADE must not exceed SIMMER_MAX_POSITION" ) ``` Apply a final defensive cap to every order: ```python calculated_size = round(conviction * MAX_POSITION, 2) size = min(MAX_POSITION, max(MIN_TRADE, calculated_size)) ``` Additional hardening should include: 1. Modify the configuration schema so invalid minimum/maximum combinations cannot be saved. 2. Centralize sizing in one validated function rather than duplicating the expression across four branches. 3. Confirm the final amount is finite, positive, and no greater than `MAX_POSITION` immediately before `client.trade()`. 4. Fail closed in live mode when any risk parameter is malformed or inconsistent. 5. Add boundary tests for zero conviction, minimum-size orders, equal limits, and `MIN_TRADE > MAX_POSITION`. ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:7
Finding
Trading SDK Dependency Is Not Version or Integrity Pinned<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:7-9` **Vulnerability Type**: Unpinned security-sensitive third-party dependency **Risk Level**: Medium ### Vulnerable Code ```json "pip": [ "simmer-sdk" ] ``` The dependency is also described in `SKILL.md:119-125` as a package published on PyPI, but no exact version, artifact hash, or lock file is specified. ### Technical Analysis The installation configuration requests `simmer-sdk` without an exact version constraint or integrity hash. Package resolution can therefore select a future release that was not part of this audit. This dependency is security-sensitive: `trader.py` provides it with `SIMMER_API_KEY`, uses it for market data, and relies on it to submit simulated or live trades. A compromised publisher account, malicious future release, or supply-chain substitution could consequently execute code in the skill process with access to the API credential and the user's runtime permissions. No evidence in the audited project proves that the current package is malicious. The confirmed issue is the absence of reproducible dependency pinning and artifact verification. ### Attack Path 1. A future malicious or compromised `simmer-sdk` release is published to the configured package index. 2. The skill is installed or reinstalled after that release becomes the version selected by the package resolver. 3. Because no exact version or hash is required, installation accepts the changed artifact. 4. Python imports `SimmerClient` from the newly installed package when `trader.py` starts. 5. The dependency executes within the skill process and receives `SIMMER_API_KEY` during client construction. 6. A malicious release could disclose the credential, falsify market information, alter trade destinations or amounts, or submit unauthorized operations within the credential's authority. ### Impact Assessment Successful exploitation would run dependency code with the same operating-system privileges ...[truncated 441 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to an exact release that has been reviewed, such as `simmer-sdk==<audited-version>`. 2. Generate and commit a dependency lock file containing cryptographic hashes for all direct and transitive packages. 3. Install with hash verification enabled, such as pip's `--require-hashes` workflow. 4. Verify that the installed PyPI artifact corresponds to the reviewed upstream source and expected publisher. 5. Review dependency updates before changing the pin; do not automatically consume new releases in live-trading environments. 6. Run the skill in a restricted environment with minimal filesystem and network access. 7. Use a narrowly scoped API key and separate paper-trading credentials from live-trading credentials. 8. Rotate the credential immediately if dependency compromise is suspected. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill explicitly requires a sensitive environment variable (`SIMMER_API_KEY`) but does not declare any tool scope or permissions boundary describing that environment access. In an agentic execution environment, undeclared access to credentials reduces reviewability and least-privilege enforcement, and is more concerning here because the key authorizes trading activity, potentially including live financial actions if misconfigured.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The manifest explicitly requires a SIMMER_API_KEY and describes automated trading behavior, but it provides no user-facing disclosure about credential use, outbound network access, or the fact that the skill can place trades on an external platform. In a trading bot context, this omission is security-relevant because users may supply sensitive credentials without understanding the scope of account access and financial actions the skill can perform.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Content
return 0.5, "no dates, assume neutral"

    now = datetime.now(timezone.utc)
    end = start + __import__('datetime').timedelta(days=period_days)

    if now < start:
        return 0.5, "period not started"
Confidence
75% confidence
Finding
Dynamic __import__() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Static analysis

No suspicious patterns detected.