Back to skill

Security audit

Polymarket Geopolitics Deadline Cascade Trader

Security checks for vulnerabilities and agentic risk

Overview

The skill appears purpose-built for trading, but its live-trading safeguards can fail in ways that may place real trades or exceed stated limits unexpectedly.

Install only if you are comfortable reviewing the code and using a tightly limited Simmer/Polymarket credential. Avoid live mode until the dependency is pinned, paper and live clients are separated, and order-size plus account-wide exposure limits 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 (3)

T08 · Insecure Dependencies

Warning
Location
clawhub.json:4
Finding

Unpinned Trading SDK Creates a Supply-Chain Compromise Risk

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:53
Finding

Global Client Cache Can Reuse a Live Trading Session During a Paper Run

Content
View full analysis
SimmerClient: global _client, MAX_POSITION, MIN_VOLUME, MAX_SPREAD, MIN_DAYS, MAX_POSITIONS global YES_THRESHOLD, NO_THRESHOLD, MIN_TRADE, MIN_VIOLATION 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))) MIN_VIOLATION = float(os.environ.get("SIMMER_MIN_VIOLATION", str(MIN_VIOLATION))) return _client ``` ### Technical Analysis The module maintains one global `_client` instance. The requested `live` mode is evaluated only when `_client` is `None`. Once a live Polymarket client has been created, subsequent calls to `get_client(live=False)` in the same process return that existing live client rather than constructing a simulated client. The `run()` fun ...[truncated 1607 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:420
Finding

Position and Order-Size Limits Do Not Reliably Bound Financial Exposure

Content
View full analysis
= MAX_POSITIONS: break market = opportunity[0] side, size, reasoning = compute_signal(market, opportunity) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, market_id) if not ok: safe_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}" safe_print(f" [trade] {side.upper()} ${size} {tag} {status} - {reasoning[:110]}") if r.success: placed += 1 ``` 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)" }, { "env": "SIMMER_MIN_TRADE", "default": 5, "range": [ 1, 100 ], "step": 1, "label": "Min trade size (USD)" } ``` The concurrent-position setting is declared as follows: ```json { "env": "SIMMER_MAX_POSITIONS", "default": 6, "range": [ 1, 20 ], "step": 1, "label": "Max open positions" } ``` ## ...[truncated 2487 chars]
Remediation
View remediation
MAX_POSITION`, repeated invocations, existing positions, pending orders, and partial or delayed order execution. ]]>
Vulnerability Patterns
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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
Findings (2)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill references and requires a high-value environment credential (SIMMER_API_KEY) but does not declare an explicit tool/permission scope such as allowed tools or permissions. That creates a mismatch between documented capabilities and the platform’s enforceable security boundary, increasing the risk that an agent using this skill can access sensitive environment data without clear least-privilege constraints. In this trading context, the missing scope is more sensitive than usual because the credential enables financial actions rather than just read-only API access.

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 233)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.