Back to skill

Security audit

Polymarket Kalshi Divergence

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed trading automaton, but live mode can make real recurring trades with weak market matching and fail-open safety checks.

Review this carefully before installing. Keep it in dry-run or paper-trading mode unless you have audited the market matching, set strict account and trade limits, pinned dependencies, and are comfortable with an automated job that can place repeated trades using your SIMMER_API_KEY.

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
kalshi_divergence.py:149
Finding
Insufficient Market Identity Validation Can Trigger Trades on Unrelated Markets<![CDATA[ ## Vulnerability Details **File Location**: `kalshi_divergence.py:149-183`, with the trade sink at `kalshi_divergence.py:220-272` **Vulnerability Type**: Weak market matching and validation **Risk Level**: High ### Complete Code Snippet ```python def find_polymarket_match(client: SimmerClient, kalshi_market: dict, keywords: list) -> dict | None: """Find best matching Polymarket market for a Kalshi market.""" kalshi_desc = extract_kalshi_description(kalshi_market).lower() # Try each keyword to search Polymarket for kw in keywords: try: markets = client.find_markets(query=kw) except Exception: markets = [] if not markets: continue # Score matches best_match = None best_score = 0 for pm in markets: q = (pm.question or "").lower() score = 0 # Check for overlapping terms kalshi_tokens = set(kalshi_desc.split()) pm_tokens = set(q.split()) overlap = len(kalshi_tokens & pm_tokens) score += overlap * 2 # Bonus for price-level matches (e.g., "$100,000", "above 5000") import re kalshi_nums = set(re.findall(r'\d+[,.]?\d*', kalshi_desc)) pm_nums = set(re.findall(r'\d+[,.]?\d*', q)) if kalshi_nums & pm_nums: score += 10 # Bonus for keyword in question if kw.lower() in q: score += 5 if score > best_score: best_score = score best_match = pm if best_match and best_score >= 5: return best_match return None ``` The selected market is subsequently used as the target of a live trade: ```python if live: try: result = client.trade( market_id=sig["market_id"], side=sig["side"], amount=TRADE_SIZE_USD, source=TRADE_SOURCE, ...[truncated 2663 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace heuristic keyword matching with an explicit, reviewed mapping between equivalent Kalshi and Polymarket contracts where possible. 2. Before accepting a match, normalize and compare: - Asset or event identity - Threshold value and currency or unit - Comparison direction - Start and resolution timestamps - Outcome definitions - Resolution source 3. Require multiple independent match conditions. A keyword alone must never satisfy the acceptance threshold. 4. Reject matches when multiple candidates have similar scores or required contract metadata is absent. 5. Introduce a minimum confidence threshold based on structured fields rather than token overlap. 6. Persist approved market pairs and require manual confirmation before the first live trade on any new pair. 7. Add unit tests covering similarly worded but non-equivalent markets, reversed outcomes, different dates, and different numeric units. 8. Apply per-market and aggregate exposure limits to reduce losses if matching validation fails. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
kalshi_divergence.py:37
Finding
Trading Safety Checks Fail Open When Market Context Is Unavailable<![CDATA[ ## Vulnerability Details **File Location**: `kalshi_divergence.py:37-54`, with authorization use at `kalshi_divergence.py:258-263` **Vulnerability Type**: Fail-open risk-control logic **Risk Level**: High ### Complete Code Snippet ```python def check_context(client, market_id, my_probability=None): """Check market context before trading (flip-flop, slippage, edge).""" try: params = {} if my_probability is not None: params["my_probability"] = my_probability ctx = client.get_market_context(market_id, **params) trading = ctx.get("trading", {}) flip_flop = trading.get("flip_flop_warning") if flip_flop and "SEVERE" in flip_flop: return False, f"flip-flop: {flip_flop}" slippage = ctx.get("slippage", {}) if slippage.get("slippage_pct", 0) > 0.15: return False, "slippage too high" edge = ctx.get("edge_analysis", {}) if edge.get("recommendation") == "HOLD": return False, "edge below threshold" return True, "ok" except Exception: return True, "context unavailable" ``` The returned value directly controls whether execution proceeds: ```python ok, reason = check_context(client, sig["market_id"]) if not ok: log.warning("Skipping trade: %s", reason) continue if live: try: result = client.trade( market_id=sig["market_id"], side=sig["side"], amount=TRADE_SIZE_USD, source=TRADE_SOURCE, skill_slug=SKILL_SLUG, reasoning=reasoning, ) ``` ### Technical Analysis `check_context()` is intended to block trades when there is severe flip-flop activity, excessive slippage, or insufficient edge. However, every exception is converted into `(True, "context unavailable")`. This behavior treats the inability to evaluate safety as affirmative authorization. Exceptions may arise from: - Network failures or timeouts - SDK serv ...[truncated 1720 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed for live execution: ```python except Exception as exc: log.error("Market context unavailable for %s: %s", market_id, exc) return False, "context unavailable" ``` 2. If unavailable context is acceptable for informational scans, permit fail-open behavior only in dry-run mode and never in live mode. 3. Catch specific SDK, HTTP, timeout, authentication, and parsing exceptions rather than all exceptions. 4. Add bounded retries with exponential backoff for transient failures. 5. Validate the type and required fields of the context response before authorizing a trade. 6. Distinguish “safe,” “unsafe,” and “unknown” states. Treat `unknown` as non-tradable in live mode. 7. Record structured failure details for monitoring without logging credentials. 8. Add tests proving that every context exception and malformed response blocks live execution. ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:4
Finding
Security-Sensitive Third-Party Dependencies Are Not Version-Pinned<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:4-8`; also declared in `SKILL.md:49-51` **Vulnerability Type**: Unpinned third-party dependencies **Risk Level**: Medium ### Complete Code Snippet From `clawhub.json`: ```json "requires": { "pip": [ "simmer-sdk", "requests" ], "env": [ "SIMMER_API_KEY" ] } ``` From `SKILL.md`: ```markdown ## Requirements **pip dependencies:** `simmer-sdk`, `requests` ``` ### Technical Analysis Both dependencies are declared without exact versions, integrity hashes, or a lockfile. Installation can therefore resolve a different package release over time without any change to the audited Skill files. This is particularly security-sensitive for `simmer-sdk` because the Skill passes `SIMMER_API_KEY` to `SimmerClient`, invokes remote market APIs through the client, and uses the client to submit trades. A compromised or unexpectedly changed SDK release would execute in the same Python process and inherit access to the Skill environment. No evidence in the audited files proves that either current package is malicious. The finding concerns the absence of reproducible dependency controls and the resulting supply-chain exposure. ### Attack Path 1. The Skill is installed or rebuilt at a later time. 2. The package resolver selects the then-current release of `simmer-sdk` or `requests`. 3. That release is compromised, maliciously replaced, or contains an incompatible security-relevant behavior change. 4. The package is imported by `kalshi_divergence.py`. 5. Package initialization or called SDK methods execute with the process privileges and environment available to the Skill. 6. A compromised SDK could read `SIMMER_API_KEY`, alter search results, redirect network operations, or manipulate submitted trades. This path requires compromise or unsafe modification of an upstream dependency or package-distribution channel; such compromise was not established by the static audit. ### Impact Assessme ...[truncated 577 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin each dependency to an exact reviewed version, for example: ```json "pip": [ "simmer-sdk==<reviewed-version>", "requests==<reviewed-version>" ] ``` 2. Generate and enforce cryptographic package hashes using a locked requirements file or equivalent reproducible dependency mechanism. 3. Verify package names, publishers, and official distribution sources before installation. 4. Use a trusted package index and disable untrusted fallback indexes to reduce dependency-confusion exposure. 5. Run dependency vulnerability and provenance checks in CI. 6. Review SDK release notes and relevant source changes before updating the pinned version. 7. Restrict the API key to the minimum required venue, account, trade size, and operation set where supported. 8. Isolate the process so dependencies cannot read unrelated files or environment variables. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill describes capabilities that require environment access and outbound network access, but it does not declare any explicit tool scope or permissions boundary. In an automation that polls APIs and can place trades when run live, missing scope declarations weakens least-privilege controls and can allow broader runtime capabilities than reviewers or operators expect.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Scheduling

Runs every 5 minutes via cron (`*/5 * * * *`). Managed automaton (auto-executes on schedule).
Dry-run by default. Pass `--live` to execute real trades.

## Requirements
Confidence
85% confidence
Finding
The skill is designed to auto-execute on a schedule every five minutes, which means trading decisions can be made without human review once deployed. Although it is dry-run by default, the documented --live mode turns the same autonomous loop into real trade execution, increasing the risk of unintended orders, strategy bugs, or abuse if configuration is changed or the environment is compromised.

External Transmission

Medium
Category
Data Exfiltration
Content
return True, "context unavailable"


KALSHI_API_BASE = "https://api.elections.kalshi.com/trade-api/v2"

BUY_THRESHOLD = float(os.environ.get('KALSHI_BUY_THRESHOLD', '0.08'))
SELL_THRESHOLD = float(os.environ.get('KALSHI_SELL_THRESHOLD', '0.10'))
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill can place real trades whenever the --live flag is supplied, with no interactive confirmation, secondary approval, or other safety gate immediately before order submission. In a trading skill that derives signals from heuristic market matching, this increases the chance of unintended financial loss from operator error, automation misuse, or bad signal generation.

Static analysis

No suspicious patterns detected.