Back to skill

Security audit

Polymarket Resolution Lattice Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Polymarket trading bot, but its live/paper boundary and position-limit safeguards are not reliable enough for automatic installation.

Review this before installing. Use a least-privilege API key if available, keep live trading disabled until you understand the strategy, set low trade limits, and do not rely on SIMMER_MAX_POSITIONS as a true account-wide position cap. Avoid reusing the module in a long-lived process that switches between live and paper mode, and prefer a pinned, reviewed simmer-sdk version.

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:41
Finding

Cached Live Client Can Be Reused for Paper-Mode Trades

Content
View full analysis
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, ) if live: _client.live = True try: _client.apply_skill_config(SKILL_SLUG) except AttributeError: pass # apply_skill_config only available in Simmer runtime 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 module stores one `SimmerClient` instance in the global `_client` variable. The requested `live` mode is evaluated only when this variable is `None`. Once a live Polymarket client has been created, later calls to `get_client(live=False)` return that same li ...[truncated 1218 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:360
Finding

Configured Position Limit Does Not Account for Existing Open Positions

Content
View full analysis
= MAX_POSITIONS: break market = opportunity[0] side, size, reasoning = compute_signal(market, opportunity) if not side: print(f" [skip] {reasoning}") continue ok, why = context_ok(client, market_id) if not ok: print(f" [skip] {why}") continue try: r = client.trade( market_id=market_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}" print(f" [trade] {side.upper()} ${size} {tag} {status} - {reasoning[:110]}") if r.success: placed += 1 except Exception as e: print(f" [error] {market_id}: {e}") ``` ### Technical Analysis `MAX_POSITIONS` is documented and configured as the maximum number of concurrent open positions. The implementation instead initializes `placed` to zero on every invocation and counts only orders successfully submitted during that invocation. The code never retrieves the account's existing open positions. Consequently, the limit is a per-run order count rather than a portfolio-wide concurrent-position limit. It also does not prevent repeated runs from adding to positions already opened in previous runs. ### Attack Path 1. The account already has open positions, potentially created by an earlier invocation. 2. The Skill is run again with live mode enabled. 3. `placed` is reset to zero without checking the existing portfo ...[truncated 688 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
clawhub.json:3
Finding

Security-Critical Trading SDK Is Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation
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)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding

The skill references a high-value credential (SIMMER_API_KEY) and implies environment access, but it does not declare any explicit tool scope or permission boundaries. In an agent environment, missing scope declarations can cause overbroad access to secrets or runtime capabilities, making it harder to enforce least privilege and easier for downstream code to read or misuse sensitive environment variables.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest explicitly requires a live API credential and the skill description makes clear it will place trades, but the manifest provides no user-facing warning about credential use, financial risk, or automated trading side effects. In a trading skill, silent credential access is more dangerous because users may authorize the skill without understanding it can transact on their behalf or expose them to loss if the runtime or downstream dependency is compromised.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · trader.py (reported line 145)May include surrounding context.

python
def market_volume(market) -> float:
    for attr in ("volume", "volume_usd", "liquidity", "total_volume"):
        value = getattr(market, attr, None)
        if isinstance(value, (int, float)):
            return float(value)
    return math.inf

Static analysis

No suspicious patterns detected.