Back to skill

Security audit

Polymarket 24h Precipitation Range Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed precipitation-market trading skill, but its live-trading safeguards have concrete gaps that could expose real funds beyond what users expect.

Install only if you are comfortable with an automated trading skill that can use SIMMER_API_KEY to place real Polymarket orders when live mode is enabled. Keep it in paper mode until the client-mode caching issue, volume check, position-limit enforcement, and market-grouping validation are fixed, and use a tightly scoped trading credential with external account limits.

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 (6)

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:224
Finding

Incomplete Market Ranges Are Treated as Complete Probability Distributions

Content
View full analysis
= 2: total = sum(pb.price for pb in all_range_bins) deviation = total - 1.0 ``` ### Technical Analysis The strategy assumes that the selected contracts form a mutually exclusive and collectively exhaustive partition. The implementation does not validate that assumption. In particular, it does not verify: - That all intermediate ranges are present and contiguous. - That the distribution includes the lower tail. - That ranges do not overlap. - That the selected cumulative contract starts exactly at the upper endpoint of the final range. - That all contracts use equivalent resolution conditions and measurement periods. Although `between_sorted` is computed, it is not used to validate continuity. The code also selects the cumulative contract with the numerically highest threshold rather than one proven to match the highest range endpoint. Consequently, any collection containing at least two parsed range contracts can be summed and compared with 100%, even when those contracts cover only a fraction of the outcome space. ### Attack Path 1. An incomplete, overlapping, or inconsistently structured set of precipitation markets is returned by the remote market API. 2. `build_distributions()` places those contracts in the same city-and-period group. 3. `find_viola ...[truncated 832 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:124
Finding

Market Year Is Discarded When Grouping Contracts

Content
View full analysis
str | None: m = _PERIOD_PATTERN.search(question) if m: return m.group(1).strip().lower() return None ``` ### Technical Analysis The regular expression accepts an optional four-digit year, but only the first capture group—the month name—is returned. `build_distributions()` subsequently creates its key from only the city and returned month: ```python key = f"{city}|{period}" ``` Contracts for Seattle in April 2026 and April 2027 therefore become members of the same distribution. Their outcomes are unrelated and cannot be combined in a probability sum or monotonicity comparison. The same issue can arise where questions omit the year but refer to distinct events through metadata that the parser ignores. ### Attack Path 1. The API returns contracts for the same city and calendar month in multiple years. 2. `parse_period()` strips the year from every question. 3. `build_distributions()` merges the contracts under the same `city|month` key. 4. `find_violations()` compares or sums prices from unrelated resolution periods. 5. A false sum deviation or monotonicity violation is generated. 6. In live mode, the resulting signal can cause a real Polymarket order. ### Impact Assessment The flaw undermines the integrity of every financial signal generated from cross-year results. It can cause incorrect live trades using the configured trading credential. The direct scope is the connected trading account rather than the host system, but losses may accumulate across repeated runs and multiple affected markets. ]]>
Remediation
View remediation
January|February|March|April|May|June|July|August|" r"September|October|November|December)" r"(?:\s+(?P\d{4}))?", re.I, ) ``` - Return a structured period containing both month and year. - Reject live trading when the year is absent or ambiguous. - Prefer authoritative market start, end, and resolution metadata over question-text parsing. - Group by city, year, month, measurement interval, metric, unit, and resolution source. - Add tests proving that otherwise identical markets from different years never enter the same distribution. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:369
Finding

Declared Minimum-Volume Safeguard Is Never Enforced

Content
View full analysis
tuple[bool, str]: p = getattr(market, "current_probability", None) if not isinstance(p, (int, float)): return False, "missing probability" spread_cents = getattr(market, "spread_cents", None) if isinstance(spread_cents, (int, float)) and spread_cents / 100 > MAX_SPREAD: return False, f"Spread {spread_cents/100:.1%} > {MAX_SPREAD:.1%}" resolves_at = getattr(market, "resolves_at", None) if resolves_at: try: resolves = datetime.fromisoformat(resolves_at.replace("Z", "+00:00")) days = (resolves - datetime.now(timezone.utc)).days if days < MIN_DAYS: return False, f"Only {days} days to resolve" except Exception: pass return True, "ok" ``` ### Technical Analysis `SIMMER_MIN_VOLUME` is documented as a market-volume filter and is loaded into `MIN_VOLUME`, but it is never used by `valid_market()`, `find_markets()`, or the execution loop. This creates a discrepancy between the stated risk controls and actual behavior. Low-volume contracts are easier to manipulate, may have unreliable displayed probabilities, and can incur severe price impact even where the reported spread appears acceptable. The function also accepts a missing spread value rather than failing closed, increasing the relevance of the absent liquidity check. ### Attack Path 1. A low-volume precipitation contract is discovered. 2. Its displayed probability contributes to an apparent distribution violation. 3. `valid_market()` checks probability, optional spread, and resolution time bu ...[truncated 533 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:518
Finding

Maximum Concurrent Position Limit Ignores Existing Positions

Content
View full analysis
= MAX_POSITIONS: break market = opp[0] side, size, reasoning = compute_signal(market, opp) 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 ``` ### Technical Analysis `MAX_POSITIONS` is documented as the maximum number of concurrent open positions. In practice, it only caps successful orders during the current invocation. The `placed` counter starts at zero every time `run()` executes, and no existing portfolio positions or outstanding orders are queried. The issue is amplified by repeated manual, managed-automaton, or externally scheduled executions. Every run can submit up to `MAX_POSITIONS` additional orders. The implementation also counts successful order submissions, not unique open positions. It does not account for pre-existing positions, pending orders, partial fills, or repeated exposure to the same market. ### Attack Path 1. The account already has open positions, potentially at or above the configured limit. 2. The skill starts and resets `placed` to zero. 3. It does not query existing positions or pending orders. 4. It ...[truncated 518 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:63
Finding

Cached Client Can Retain Live Trading Mode 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, SUM_TOLERANCE 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))) SUM_TOLERANCE = float(os.environ.get("SIMMER_SUM_TOLERANCE", str(SUM_TOLERANCE))) return _client ``` ### Technical Analysis The module stores a single client in the global `_client`. The requested `live` argument is considered only when `_client` is `None`. If a long-lived interpreter first calls `get_client(live=True)`, the cached client is configured for the real Polymarket venue and has its `live` property set. A subsequent `run(live=False)` receives that ...[truncated 1190 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
clawhub.json:6
Finding

Trading SDK Dependency Is Unpinned

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (5)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill declares access to a high-value environment credential (SIMMER_API_KEY) and describes live trading behavior, but it does not define an explicit tool/permission scope. That creates a least-privilege gap: an agent using this skill may be granted broader environment access than necessary, increasing the chance of secret exposure or misuse if the surrounding platform over-permits by default.

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

md
## The Edge: Distribution Arbitrage for Precipitation Markets

In traditional markets, discrete outcome probabilities must sum to 1.0 — this is a fundamental axiom. On Polymarket, each precipitation range bin trades independently with its own order book and liquidity. Retail treats each bin as an isolated bet without checking the full distribution.

### Violation Type 1: Sum Deviation

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest enables a managed automated trading entrypoint but provides no user-facing disclosure that the skill can autonomously place financial trades or incur losses. In a trading skill, lack of clear warning and consent context is safety-relevant because users may enable or configure it without understanding execution risk, position exposure, or that real funds may be used.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest and module description emphasize reconstructing distributions across bins and checking monotonicity on cumulative 'more than X inches' markets. The code also parses, analyzes, and generates trades for 'less than X inches' markets, which is an additional market type not described in the stated skill purpose.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manifest specifically claims monotonicity checks on cumulative 'more than X inches' markets. This block implements a separate monotonicity strategy for 'less than X' markets and creates corresponding buy/sell opportunities, expanding the behavior beyond the declared description.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.