Back to skill

Security audit

Polymarket Multi Source Estimator

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed prediction-market trading bot, but it needs Review because it can repeatedly make financial trades from LLM estimates influenced by external internet data with limited safeguards.

Install only if you are comfortable running an automated trading bot. Keep it in dry-run or paper trading until you have reviewed the LLM endpoint, pinned dependencies, set strict trade-size and exposure limits, and added a fail-closed/manual-approval control for live trades. Use narrowly scoped, revocable API keys.

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
multi_source_estimator.py:738
Finding
Untrusted Internet Content Can Influence Automated Financial Trades Through Prompt Injection<![CDATA[ ## Vulnerability Details **File Location**: `multi_source_estimator.py:738-791`, `multi_source_estimator.py:926-955`, `multi_source_estimator.py:1017-1062` **Vulnerability Type**: Indirect prompt injection through untrusted external data **Risk Level**: High ### Vulnerable Code ```python def fetch_context(question: str, category: str = "", market_price: float = 0.5, max_chars: int = 2000) -> str: """ Gather real-time context from all relevant sources in parallel. Returns a formatted string (max ~2000 chars) for injection into the LLM prompt. """ categories = detect_categories(question) if category and category not in categories: categories.insert(0, category) parts = [] # Always fetch news news = _match_news(question) if news: parts.append("RECENT NEWS:\n" + "\n".join(news)) # Dispatch category-specific sources in parallel with ThreadPoolExecutor(max_workers=4) as pool: futures = {} if any(c in categories for c in ["economics", "crypto"]): futures[pool.submit(_get_fred_context, question)] = "FRED" if "geopolitics" in categories: futures[pool.submit(_get_gdelt_context, question)] = "GDELT" if "sports" in categories: futures[pool.submit(_get_odds_context, question)] = "ODDS" if "politics" in categories: futures[pool.submit(_get_538_context, question)] = "538" futures[pool.submit(_get_congress_context, question)] = "CONGRESS" if "pharma" in categories: futures[pool.submit(_get_fda_context, question)] = "FDA" if "weather" in categories: futures[pool.submit(_get_meteo_context, question)] = "METEO" if any(kw in question.lower() for kw in ["earthquake", "quake", "seismic"]): futures[pool.submit(_get_usgs_context, question)] = "USGS" if any(kw in question.lower() for kw in ["earnings", "ipo", "revenue", "stock ...[truncated 5061 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every value obtained from an external feed or API as untrusted data. 2. Place external evidence in a clearly delimited structure and explicitly instruct the model that text inside it is quoted evidence, not executable instructions. 3. Prefer structured, allowlisted fields over arbitrary free-form text. 4. Reject or neutralize context containing instruction-like phrases, role markers, tool requests, JSON-output directives, or attempts to override prior instructions. 5. Preserve source identity and require corroboration from multiple independent sources before external content can materially change a trading estimate. 6. Apply deterministic bounds to how far an LLM estimate may move based on a single source. 7. Validate the model output against independent risk rules rather than relying only on JSON parsing and a divergence threshold. 8. Require manual approval, or a separate deterministic policy decision, before executing live trades based on untrusted textual context. 9. Add adversarial tests containing prompt-injection payloads in RSS titles and API fields. 10. Consider running the LLM analysis and financial execution as separate trust domains, with the execution component accepting only narrowly validated numeric signals. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
multi_source_estimator.py:1073
Finding
Live Trading Safety Checks Fail Open When Market Context Retrieval Fails<![CDATA[ ## Vulnerability Details **File Location**: `multi_source_estimator.py:1073-1086` **Vulnerability Type**: Fail-open financial risk control **Risk Level**: Medium ### Vulnerable Code ```python # Context check before trading (flip-flop, slippage) market_id = market.id try: ctx = client.get_market_context(market_id, my_probability=est_prob) trading = ctx.get("trading", {}) flip_flop = trading.get("flip_flop_warning") if flip_flop and "SEVERE" in flip_flop: log.warning("Skipping '%s...': %s", question[:50], flip_flop) continue slippage = ctx.get("slippage", {}) if slippage.get("slippage_pct", 0) > 0.15: log.warning("Skipping '%s...': slippage too high", question[:50]) continue except Exception as e: log.debug("Context check failed (non-blocking): %s", e) # Place trade try: amount = float(os.environ.get("TRADE_SIZE", "10.0")) result = client.trade( market_id=market_id, side=side.lower(), amount=amount, source=TRADE_SOURCE, skill_slug=SKILL_SLUG, reasoning=f"LLM estimate {est_prob:.0%} vs market {price:.0%} " f"(divergence {abs(divergence):.0%}). {reasoning}", ) ``` ### Technical Analysis The Skill attempts to check flip-flop warnings and estimated slippage before placing a trade. These are security- and risk-relevant preconditions because they are intended to prevent unstable or economically unfavorable execution. Any exception from `get_market_context()`, response parsing, or unexpected response types is caught and explicitly treated as non-blocking. Execution then continues to the trade block. Consequently, the absence of risk information is treated as permission to trade. This is a fail-open design. In a system capable of financial transactions, the safe default should be to reject or postpone a transaction when required risk controls cannot be evaluated. The code also uses permissive defaults such as `ctx.get( ...[truncated 1462 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed in live mode: skip the trade whenever market-context retrieval or validation fails. 2. Require `ctx`, `trading`, and `slippage` to have the expected types and required fields. 3. Treat missing `slippage_pct` as unknown and unsafe, not as zero. 4. Validate that slippage is a finite numeric value within an expected range. 5. Log blocked trades at warning level so operators can observe safety-control outages. 6. Implement bounded retries with backoff for transient context-service failures. 7. Separate dry-run behavior from live behavior: dry runs may continue without context, but live execution should require all mandatory checks to pass. 8. Add tests for timeouts, malformed responses, missing fields, `None` values, and unexpected SDK exceptions. 9. Consider enforcing maximum order size and total exposure limits independently of the context endpoint. ]]>

T08 · Insecure Dependencies

Warning
Location
clawhub.json:4
Finding
Security-Sensitive Runtime Dependencies Are Not Version-Pinned<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:4-8` **Vulnerability Type**: Unpinned third-party dependency supply chain **Risk Level**: Medium ### Vulnerable Code ```json "requires": { "pip": [ "simmer-sdk", "requests" ], "env": [ "SIMMER_API_KEY", "LLM_API_KEY" ] } ``` ### Technical Analysis The package metadata declares `simmer-sdk` and `requests` without exact versions or integrity hashes. Installation can therefore resolve to whatever versions are available under those package names at installation time. This is particularly sensitive for `simmer-sdk`: the dependency receives the Simmer API credential and exposes the method used to submit trades. A malicious or compromised release could access the credential, change transaction parameters, issue unauthorized network requests, or place trades outside the reviewed application logic. No evidence was found that either named package is currently malicious. The confirmed weakness is the lack of dependency immutability and provenance controls, which leaves future managed installations exposed to registry compromise, maintainer compromise, or unsafe upstream changes. ### Attack Path 1. An attacker compromises a dependency publisher account, package registry path, build pipeline, or future upstream release. 2. A malicious version is published under `simmer-sdk` or `requests`. 3. The managed Skill installation resolves the dependency without an exact version or hash restriction. 4. The malicious package is imported when `multi_source_estimator.py` starts. 5. The package executes with the process's environment variables and network permissions. 6. In the case of the trading SDK, it can access the supplied API key or alter calls intended for `SimmerClient`. ### Impact Assessment A compromised dependency executes with the same privileges as the Skill process. Potential impact includes disclosure of `SIMMER_API_KEY`, `LLM_API_KEY`, and optional provider keys; unautho ...[truncated 363 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every runtime dependency to an exact reviewed version. 2. Use a lock file or requirements file containing cryptographic hashes. 3. Install dependencies with hash verification, such as `pip --require-hashes`. 4. Review the provenance, ownership, release history, and transitive dependencies of `simmer-sdk`. 5. Use a controlled package index or an internally mirrored repository for managed installations. 6. Automate vulnerability scanning while keeping dependency upgrades explicit and reviewable. 7. Run the Skill in a restricted environment with narrowly scoped credentials and outbound-network allowlists. 8. Prefer a dedicated, revocable trading token with strict order-size and venue limits. 9. Rotate API credentials immediately if dependency compromise is suspected. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (18)

Tainted flow: 'api_url' from os.environ.get (line 931, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
try:
        _llm_calls_this_cycle += 1
        resp = requests.post(
            api_url,
            headers={"Authorization": f"Bearer {api_key}",
                     "Content-Type": "application/json",
Confidence
97% confidence
Finding
The LLM endpoint URL is taken directly from an environment variable and used in a POST request that includes the Authorization bearer token. If an attacker can influence LLM_API_URL, they can redirect prompts and secrets to an attacker-controlled server, causing credential exfiltration and leakage of market questions plus enriched external context. In a live trading bot, this also enables model-response manipulation that can steer trades.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill description emphasizes estimation and data enrichment, but the documented behavior includes live trade execution, use of a trading client, and cross-platform market access without corresponding declared permissions. This mismatch is dangerous because reviewers or users may treat the skill as informational analysis while it can perform financially sensitive actions and broader network interactions.

Ssd 1

High
Confidence
96% confidence
Finding
Untrusted market questions and externally sourced text are concatenated directly into the prompt and framed as decision-relevant context. This creates a prompt-injection and data-poisoning risk: malicious or misleading source text can bias the model's probability estimate, and the result directly influences trading decisions in a live bot.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The manifest and module docstring present this skill as a bot that trades when its estimate diverges from market price, including a '--live' mode. In the actual trade call, the reasoning string interpolates an undefined variable 'reasoning', which will raise a NameError before the trade executes, so the advertised live-trading behavior does not match the implemented code path.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill declares no explicit tool scope or permissions despite requiring environment-variable access, network access, and the ability to initiate trades through an external trading client. In an agent platform, missing permission declarations can cause operators or enforcement layers to underestimate the skill's effective capabilities, increasing the risk of unauthorized external calls or financial actions.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Scheduling

Runs every 5 minutes via cron (`*/5 * * * *`). Managed automaton (auto-executes on schedule).
Capped at 50 LLM calls per run (`LLM_MAX_CALLS`) to control costs.

## Security
Confidence
88% confidence
Finding
The skill is configured to auto-execute on a schedule every five minutes and can place trades when run with live mode, meaning an LLM-driven decision process may repeatedly trigger external financial actions with limited human oversight. In the context of a trading bot, autonomous execution increases the blast radius of model errors, prompt/data poisoning from external sources, or configuration mistakes.

External Transmission

Medium
Category
Data Exfiltration
Content
results.append(cached)
            continue
        data = _safe_get(
            "https://api.stlouisfed.org/fred/series/observations",
            params={"series_id": sid, "api_key": api_key, "file_type": "json",
                    "sort_order": "desc", "limit": 5}
        )
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
results.append(cached)
            continue
        data = _safe_get(
            "https://api.gdeltproject.org/api/v2/doc/doc",
            params={"query": query, "mode": "tonechart", "format": "json",
                    "timespan": "1d"}
        )
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
cached = _get_cached(cache_key, _ODDS_TTL)
        if cached is None:
            cached = _safe_get(
                f"https://api.the-odds-api.com/v4/sports/{sport_key}/odds",
                params={"apiKey": api_key, "regions": "us,eu",
                        "markets": "h2h", "oddsFormat": "decimal"}
            )
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
return []
    cached = _get_cached("congress_recent", _CONGRESS_TTL)
    if cached is None:
        data = _safe_get("https://api.congress.gov/v3/bill",
                         params={"api_key": api_key, "format": "json",
                                 "limit": 30, "sort": "updateDate+desc"})
        if data and "bills" in data:
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if cached is None:
        today = datetime.utcnow().strftime("%Y%m%d")
        month_ago = (datetime.utcnow() - timedelta(days=60)).strftime("%Y%m%d")
        data = _safe_get("https://api.fda.gov/drug/drugsfda.json",
                         params={"search": f"submissions.submission_status_date:"
                                 f"[{month_ago}+TO+{today}]", "limit": 30})
        if data and "results" in data:
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
cached = _get_cached(cache_key, _METEO_TTL)
    if cached is not None:
        return cached
    data = _safe_get("https://api.open-meteo.com/v1/forecast",
                     params={"latitude": lat, "longitude": lon,
                             "daily": "temperature_2m_max,temperature_2m_min",
                             "forecast_days": 16, "temperature_unit": "fahrenheit"})
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
_kalshi_cache = None
_kalshi_ts = 0.0

MANIFOLD_URL = "https://api.manifold.markets/v0"
KALSHI_URL = "https://trading-api.kalshi.com/trade-api/v2"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Ssd 4

Medium
Confidence
90% confidence
Finding
The prompt explicitly tells the model to 'weight real-time context heavily' and describes outside prices as a 'strong signal,' increasing trust in unverified external content. In this skill's context, that trust amplification is dangerous because multiple fetched sources and market text are untrusted and can be noisy, adversarial, or mismatched, yet they directly shape trading actions.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The code sends market questions and aggregated context to an external LLM service without a clear runtime disclosure or consent checkpoint. Even if the data is not highly sensitive, it may include proprietary strategy inputs, market selections, and third-party content that operators may not expect to be transmitted off-platform.

External Transmission

Medium
Category
Data Exfiltration
Content
try:
        _llm_calls_this_cycle += 1
        resp = requests.post(
            api_url,
            headers={"Authorization": f"Bearer {api_key}",
                     "Content-Type": "application/json",
Confidence
82% confidence
Finding
This external transmission sends market questions, cross-platform pricing context, and aggregated real-time data to a third-party LLM provider. In a trading skill, that can expose strategy-relevant information and proprietary workflow data outside the local environment, with added risk if the provider retains prompts or the endpoint is misconfigured.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The bot can place real trades immediately when run with --live, without any interactive confirmation or secondary safeguard at the point of execution. In a trading skill that also consumes untrusted external data and LLM output, this increases the chance of accidental or manipulated financial actions.

Intent-Code Divergence

Low
Confidence
85% confidence
Finding
The module docstring says the bot enriches with '10+ sources' and lists cross-platform prices from Manifold and Kalshi among those sources, while the source section is labeled 'Data sources (1-10)' and only enumerates ten context sources there; Manifold and Kalshi are implemented later in a separate cross-platform section. This documentation does not match the structure of the code and can mislead reviewers about what counts as enrichment context versus price comparison inputs.

Static analysis

No suspicious patterns detected.