Back to skill

Security audit

Polymarket Bundle Dota2 Props Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Polymarket trading bot, but some promised live-trading safeguards are not actually enforced.

Review this before enabling live trading. It appears coherent and not malicious, but users should treat SIMMER_API_KEY as a high-value credential, pin or verify simmer-sdk, start in paper mode, and not rely on the documented volume or concurrent-position safeguards until those checks are fixed or enforced by the trading platform/account.

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

Unpinned Privileged Third-Party Dependency

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:430
Finding

Declared Minimum Market Volume Safeguard Is Not Enforced

Content
View full analysis
list: """ Find active Dota 2 prop markets via keyword search + bulk fetch fallback, deduplicated. Uses get_markets(limit=200) as primary discovery since Dota markets are often missed by find_markets keyword search. """ seen: set[str] = set() unique: list = [] # Bulk fetch FIRST -- Dota markets often missed by keyword search try: for m in client.get_markets(limit=200): market_id = getattr(m, "id", None) if market_id and market_id not in seen: q = getattr(m, "question", "") if is_dota_prop_market(q): seen.add(market_id) unique.append(m) except Exception as e: safe_print(f"[bulk-fetch] {e}") # Keyword search as supplement for kw in KEYWORDS: ``` The configuration value is loaded at `trader.py:36`: ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "3000")) ``` ### Technical Analysis `SIMMER_MIN_VOLUME` is documented as a minimum market-volume filter and is loaded into `MIN_VOLUME`. However, discovered markets are accepted based only on their identifier and question text. The code never obtains and validates a market volume field against `MIN_VOLUME`. The omission also persists later in the execution flow: signal calculation and the pre-trade context check do not enforce minimum volume. As a result, configuring `SIMMER_MIN_VOLUME` has no protective effect. Low-volume prediction markets can be easier to manipulate and may have poor depth even when their displayed probability creates an apparent inconsistency. The strategy may therefore treat a transient or manipulated thin-market price as a legitimate signal. ### Attack Path 1. An attacker creates, iden ...[truncated 1191 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:492
Finding

Concurrent Position Limit Resets on Every Invocation

Content
View full analysis
= MAX_POSITIONS: break pm = opp[0] side, size, reasoning = compute_signal(pm.market, opp) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, pm.market.id) if not ok: safe_print(f" [skip] {why}") continue try: r = client.trade( market_id=pm.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 except Exception as e: safe_print(f" [error] {pm.market.id}: {e}") safe_print(f"[bundle-dota2-props] done. {placed} orders placed.") ``` ### Technical Analysis `MAX_POSITIONS` is described as the maximum number of concurrent open positions, but the implementation only counts successful orders submitted during the current process invocation. The `placed` counter starts at zero every time `run()` executes. The code does not query existing open positions, pending orders, or exposure already held in the same markets. It also does not reserve capacity atomically before submitting an order. Consequently, repeated executions can each submit up to `MAX_POSITIONS` additional orders while previous positions remain open. Concurrent skill instances can also ...[truncated 1149 chars]
Remediation
View remediation
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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
Findings (3)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill declares access to a high-value credential (SIMMER_API_KEY) and implies use of environment-backed execution, but it does not define any explicit tool scope such as permissions or allowed-tools. In an agent setting, missing scope boundaries can let the runtime grant broader capabilities than intended, increasing the risk of unintended credential access or misuse during trading operations.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

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

md
Polymarket prices each prop in isolation via its own order book. There is no mechanism to enforce cross-prop consistency the way a professional sportsbook would. This creates systematic mispricings:

1. **Correlated props priced independently** -- Retail traders bet on "Roshan?" without checking what the kills market implies about game action level
2. **Action-score anchor** -- The kills O/U market is the most liquid and best-priced prop; it serves as a reliable anchor for what kind of game to expect
3. **Thin liquidity on exotic props** -- Rampage, ultra kill, and daytime markets have much less volume, making them more prone to mispricing
4. **Multi-game series multiply opportunities** -- BO3 matches have separate prop bundles for Game 1, Game 2, and Game 3, tripling the surface area

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 201)May include surrounding context.

python
if market is not None:
        for attr in ("event_slug", "group_slug", "import_source", "event_id"):
            val = getattr(market, attr, None)
            if val and isinstance(val, str) and len(val) >= 3:
                return val.lower().strip()

Static analysis

No suspicious patterns detected.