Back to skill

Security audit

Polymarket Emerging Tech Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Polymarket trading skill, but it needs Review because some advertised financial safeguards are not enforced and mode handling can be unsafe in reused sessions.

Review this carefully before installing with a live-capable key. Use a narrowly scoped SIMMER_API_KEY with strict spending limits, prefer paper mode, avoid importing or reusing trader.py in long-lived processes, and do not rely on the documented volume or max-open-position limits until the implementation enforces them.

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

Warning
Location
trader.py:199
Finding

Documented liquidity and portfolio exposure safeguards are not enforced

Content
View full analysis

Vulnerability Details

File Location: trader.py:24-32, trader.py:199-235
Vulnerability Type: Missing financial risk-control enforcement
Risk Level: Medium

The skill declares a minimum market-volume requirement and describes MAX_POSITIONS as a limit on concurrent open positions. However, the trading loop never checks market volume, while MAX_POSITIONS only limits successful orders during the current invocation.

Relevant configuration code (trader.py:24-32):

python
# Risk parameters — declared as tunables in clawhub.json, tunable from Simmer UI.
# Named SIMMER_* so apply_skill_config() can load automaton-managed overrides.
MAX_POSITION   = float(os.environ.get("SIMMER_MAX_POSITION",  "25"))
MIN_VOLUME     = float(os.environ.get("SIMMER_MIN_VOLUME",    "2000"))
MAX_SPREAD     = float(os.environ.get("SIMMER_MAX_SPREAD",    "0.15"))
MIN_DAYS       = int(os.environ.get(  "SIMMER_MIN_DAYS",      "14"))
MAX_POSITIONS  = int(os.environ.get(  "SIMMER_MAX_POSITIONS", "8"))
# Signal thresholds — buy YES below YES_THRESHOLD, sell NO above NO_THRESHOLD.

Trading loop (trader.py:199-235):

python
def run(live: bool = False) -> None:
    mode = "LIVE" if live else "PAPER (sim)"
    print(f"[polymarket-emerging-tech-trader] mode={mode} max_pos=${MAX_POSITION} min_vol=${MIN_VOLUME} max_spread={MAX_SPREAD:.0%} min_days={MIN_DAYS}")

    client  = get_client(live=live)
    markets = find_markets(client)
    print(f"[polymarket-emerging-tech-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.tra
...[truncated 2394 chars]
Remediation
View remediation

Remediation Suggestions

  • Validate market volume before signal generation or order submission:
    python
    volume = getattr(m, "volume", None)
    if volume is None:
        print("  [skip] Market volume unavailable")
        continue
    if volume < MIN_VOLUME:
        print(f"  [skip] Volume ${volume:.2f} below ${MIN_VOLUME:.2f}")
        continue
    
  • Confirm the SDK's authoritative volume field and units rather than assuming an attribute name.
  • Query current open positions before trading and calculate available position slots from the account's actual state.
  • Count newly opened positions together with existing positions, rather than counting only successful orders in the current process.
  • Fail closed if account-position or liquidity information cannot be retrieved.
  • Add tests covering low-volume markets, pre-existing positions, repeated invocations, and unavailable risk data.
  • Update documentation if MAX_POSITIONS is intentionally meant to be only a per-run order limit.

T08 · Insecure Dependencies

Warning
Location
clawhub.json:7
Finding

Trading SDK dependency is not version-pinned

Content
View full analysis

Vulnerability Details

File Location: clawhub.json:7-9
Vulnerability Type: Unrestricted third-party dependency resolution
Risk Level: Medium

Complete vulnerable configuration:

json
"pip": [
  "simmer-sdk"
]

The application imports the package and gives it direct access to the trading credential:

python
from simmer_sdk import SimmerClient
python
_client = SimmerClient(
    api_key=os.environ["SIMMER_API_KEY"],
    venue=venue,
)

Technical Analysis

The package requirement does not specify an exact version or integrity hash. Installation can therefore resolve a future package release that was not part of this audit.

This dependency is security-sensitive because SimmerClient receives SIMMER_API_KEY, selects the trading venue, retrieves market information, and submits orders. A compromised upstream release, publisher account, package index response, or dependency chain could execute arbitrary Python code during installation or import and access the credential at runtime.

The audit found no evidence that the currently referenced package is malicious. The vulnerability is the absence of reproducible, integrity-verified dependency resolution.

Attack Path

  1. An attacker compromises the upstream package publisher, release process, package repository, or one of the package's transitive dependencies.
  2. The attacker publishes a newer malicious release under the same package name.
  3. A subsequent skill installation resolves the unrestricted simmer-sdk requirement to that release.
  4. Malicious package code executes during installation, import, or SimmerClient initialization.
  5. The code reads SIMMER_API_KEY from the constructor or environment and can exfiltrate it or abuse its trading authority.

Impact Assessment

Malicious dependency code would execute with the operating-system privileges of the process installing or running the skill. It co ...[truncated 283 chars]

Remediation
View remediation

Remediation Suggestions

  • Pin simmer-sdk to an exact version that has been reviewed and tested.
  • Use a lockfile or deployment mechanism that records resolved transitive dependency versions.
  • Verify package artifacts with cryptographic hashes where the installation framework supports them.
  • Review upstream source and release provenance before updating the pinned version.
  • Run the skill under a dedicated, least-privileged operating-system identity.
  • Use a narrowly scoped API key with strict trading and spending limits.
  • Rotate the key immediately if a dependency compromise is suspected.

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:38
Finding

Global client cache can reuse a live trading client for a later paper-mode run

Content
View full analysis

Vulnerability Details

File Location: trader.py:38-72
Vulnerability Type: Trading-mode state confusion
Risk Level: Medium

Complete vulnerable client-initialization 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

Technical Analysis

The first call to get_client() permanentl ...[truncated 1690 chars]

Remediation
View remediation

Remediation Suggestions

  • Avoid a single mutable global client. Create a new client for each run() invocation and pass it explicitly to helper functions.
  • Alternatively, cache clients separately by immutable venue:
    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,
            )
        return _clients[venue]
    
  • Verify the client's actual venue and live flag immediately before every trade.
  • Derive displayed mode from the initialized client's authoritative state rather than solely from the requested argument.
  • Refuse to trade if requested mode and client mode differ.
  • Add a regression test that invokes live mode followed by paper mode, and the reverse order, within the same interpreter.
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 (3)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill references environment-based credentials (SIMMER_API_KEY) and describes live trading capability, but it does not declare any explicit tool scope or permission boundaries. That creates an avoidable trust gap: a runtime may grant broader access than intended, and users cannot easily verify that the skill is limited to only the minimum capabilities required.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 116)May include surrounding context.

md
## Key Data Sources

- **DeFiLlama**: https://defillama.com/
- **GitHub API**: https://api.github.com/
- **IBM Quantum Network**: https://quantum.ibm.com/
- **The Good Food Institute**: https://gfi.org/
- **CoinGlass NFT**: https://www.coinglass.com/nft

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This manifest requires the sensitive environment variable SIMMER_API_KEY, but the file contains no accompanying warning or disclosure about credential access or use. For this file type, the available metadata does not inform the user that the skill depends on and will access an API key.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.