Back to skill

Security audit

Polymarket 48h Precipitation Range Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed trading skill, but its live-trading safeguards and market validation are weak enough that it should be reviewed carefully before use with real funds.

Use this only in paper mode unless you have reviewed and fixed the trading logic and risk controls. For live use, pin the SDK, scope and protect the API key, require explicit confirmation or strong account-level limits, and verify that markets are grouped by the exact same event and resolution period before any order can be submitted.

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

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:113
Finding

Markets from different years are merged into the same trading distribution

Content
View full analysis
str | None: m = _PERIOD_PATTERN.search(question) if m: return m.group(1).strip().lower() return None ``` The resulting period is subsequently used in the distribution key: ```python key = f"{city}|{period}" pb = PrecipBin(m, city, period, bin_info, float(p)) distributions.setdefault(key, []).append(pb) ``` ### Technical Analysis The regular expression accepts an optional four-digit year but does not capture it. `parse_period()` consequently returns only the month name. For example, both `April 2026` and `April 2027` become `april`. `build_distributions()` groups markets solely by normalized city and this month-only period. Contracts concerning different years can therefore be analyzed as if they were mutually exclusive bins in one probability distribution. Their probabilities may then produce an artificial sum deviation or monotonicity violation. Because the resulting opportunity can be passed directly to `client.trade()`, this is financially unsafe when the process is launched with `--live`. ### Attack Path 1. Multiple searchable precipitation contracts exist for the same city and month but different years. 2. `find_markets()` discovers those contracts. 3. `parse_period()` discards each contract's year. 4. `build_distributions()` places the unrelated contracts under the same `city|month` key. 5. `find_violations()` interprets their prices as parts of one distribution. 6. A false violation passes the probability and execution gates. 7. With `--live`, the client submits a real Polymarket order using the configured trading authority. ### Impact Assessme ...[truncated 383 chars]
Remediation
View remediation
str | None: match = _PERIOD_PATTERN.search(question) if not match: return None return f"{match.group(1).lower()}-{match.group(2)}" ``` Include the complete resolution period in the distribution key. Reject contracts without an unambiguous year unless authoritative market metadata supplies an equivalent date. Where available, group by event or series identifier rather than relying only on question text. Add tests confirming that different years, time zones, units, and resolution periods cannot be merged. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:224
Finding

Probability-sum trading proceeds without verifying a complete and non-overlapping outcome partition

Content
View full analysis
= 2: total = sum(pb.price for pb in all_range_bins) deviation = total - 1.0 ``` ### Technical Analysis The code assumes that all discovered `between` bins plus the cumulative contract with the numerically highest threshold form a complete probability partition. It does not verify that: - Range boundaries are contiguous. - Ranges do not overlap. - Duplicate bins are absent. - The lower tail of the distribution is represented. - The cumulative threshold equals the upper boundary of the highest range. - All contracts use the same units and exact resolution criteria. - The selected contracts belong to one authoritative event series. The comment states that the selected contracts partition the outcome space, but that property is not established by the implementation. Even two arbitrary range bins are sufficient to enter the sum calculation. Their total is then incorrectly expected to be approximately 100%. ### Attack Path 1. Discovery returns a sparse, overlapping, duplicated, or otherwise incomplete collection of precipitation contracts. 2. The parser classifies at least two of them as `betwe ...[truncated 841 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:374
Finding

Documented minimum-volume safeguard is not 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 presented as a risk parameter and is read into `MIN_VOLUME`, but it is never used by `valid_market()`, discovery, signal generation, or execution. Changing the configured value therefore has no effect. Low-volume contracts are more susceptible to unstable prices, manipulation, poor fills, and an inability to exit. A market can pass validation even when its volume is zero or unavailable. ### Attack Path 1. A low-volume precipitation market is returned by market discovery. 2. Its displayed probability contributes to an apparent distribution violation. 3. `valid_market()` checks its probability, spread, and resolution date but not its volume. 4. The signal passes the configured probability threshold. 5. The application submits a trade despite the operator's configur ...[truncated 387 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:513
Finding

Configured maximum open positions only limits successful orders within the current run

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 except Exception as e: print(f" [error] {market_id}: {e}") ``` ### Technical Analysis `MAX_POSITIONS` is documented as the maximum number of concurrent open positions, but the implementation initializes `placed` to zero on every invocation and counts only successful orders submitted during that invocation. The code never queries existing open positions or pending orders. Repeated executions can therefore accumulate substantially more concurrent exposure than the configured limit. Concurrent process instances can also race because there is no atomic account-level capacity reservation. ### Attack Path 1. The account already has open positions, possibly from an earlier invocation. 2. A new process starts and resets `placed` to zero. 3. The process submits up to `MAX_POSITIONS` additional successful orders. 4. Subsequent scheduled or manual runs repeat the process while prior positions remain op ...[truncated 569 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
clawhub.json:6
Finding

Trading SDK dependency is installed without version or integrity pinning

Content
View full analysis
Remediation
View remediation
" ] ``` Use a lock file with cryptographic hashes where the deployment system supports it. Install only from the expected package index over authenticated TLS, verify the package publisher and release provenance, and review dependency changes before upgrades. Run the skill under a dedicated least-privilege account and scope the API credential to only the required trading capabilities and financial limits. Rotate the API key if dependency compromise is suspected. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (3)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill states it uses environment-based credentials (SIMMER_API_KEY) and performs trading actions, but it does not declare any explicit tool scope or permissions boundary in the skill manifest. That creates an unnecessary trust gap: an agent/runtime may expose broader environment or execution capabilities than required, increasing the blast radius if the skill is misused or composed with other tools.

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
94% confidence
Finding

The manifest clearly describes an automated trading skill, exposes trading-risk tunables such as max position size, spread thresholds, and maximum open positions, yet provides no explicit warning, consent gate, or disclosure about autonomous financial activity and loss risk. In this context, omission is security-relevant because users may enable the skill without understanding it can place real trades automatically, increasing the chance of unintended financial exposure.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.