Back to skill

Security audit

Polymarket Btc Momentum

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Bitcoin trading automation, but live mode can place real-money trades with weak market validation and fail-open safety checks.

Review this carefully before installing. Use only a dedicated, low-limit, revocable Simmer API key; keep dry-run mode unless you have audited the code; pin dependencies; and require fail-closed safety checks plus stronger market identification before enabling live trading.

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:3
Finding
Unpinned Third-Party SDK Is Entrusted with a Sensitive API Credential<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:3-5`; credential handoff at `btc_momentum.py:23-32` **Vulnerability Type**: Unpinned dependency with access to credentials and financial operations **Risk Level**: Medium ### Vulnerable Code ```json "requires": { "pip": ["simmer-sdk", "requests"], "env": ["SIMMER_API_KEY"] }, ``` The unpinned SDK is subsequently given the API credential: ```python def get_client(venue="polymarket"): global _client if _client is None: try: from simmer_sdk import SimmerClient except ImportError: print("ERROR: simmer-sdk not installed. Run: pip install simmer-sdk") sys.exit(1) _client = SimmerClient( api_key=os.environ["SIMMER_API_KEY"], venue=venue, ) return _client ``` ### Technical Analysis The Skill declares `simmer-sdk` and `requests` without exact versions, integrity hashes, or a dependency lock file. Package resolution can therefore install a different version whenever the environment is rebuilt. This is particularly significant for `simmer-sdk`: it is imported into the process and directly receives `SIMMER_API_KEY`. It is also responsible for market queries and live-trade submission. Python package initialization and imported module code execute with the same operating-system privileges as the Skill. The reviewed project does not directly send the API key to Binance or another unrelated endpoint, and the external SDK implementation was not included in the audit. Consequently, there is no evidence that the current package intentionally exfiltrates the credential. The confirmed weakness is the absence of dependency integrity controls around a component trusted with sensitive credentials and financial operations. ### Attack Path 1. An attacker compromises the package publisher, package-index account, distribution infrastructure, or a transitive dependency. 2. The attacker publishes a malicious ...[truncated 944 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct dependency to a reviewed, exact version. 2. Generate a lock file containing versions and cryptographic hashes for direct and transitive dependencies. 3. Install with hash verification enabled, such as `pip install --require-hashes`. 4. Obtain `simmer-sdk` only from its verified official publisher and review release provenance before upgrades. 5. Run automated dependency vulnerability and package-integrity checks in CI. 6. Use a dedicated, revocable API credential with the minimum required trading scope and strict account-level spending limits. 7. Isolate the Skill in a restricted runtime with minimal filesystem and network access. 8. Establish an explicit, reviewed dependency-update process rather than accepting versions automatically. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
btc_momentum.py:198
Finding
Live Trading Proceeds When the Safety-Context Check Fails<![CDATA[ ## Vulnerability Details **File Location**: `btc_momentum.py:198-220` **Vulnerability Type**: Fail-open error handling in a financial safety control **Risk Level**: High ### Vulnerable Code ```python # 4. Check context print("\n🔎 Checking market context...") try: ctx = get_client().get_market_context(market.id, my_probability=confidence) warnings = ctx.get("warnings", []) trading = ctx.get("trading", {}) flip_flop = trading.get("flip_flop_warning", "") slippage = ctx.get("slippage", {}).get("slippage_pct", 0) if flip_flop and "SEVERE" in flip_flop: print(f"⛔ Flip-flop warning: {flip_flop}. Aborting.") return if slippage > 0.15: print(f"⛔ Slippage too high ({slippage:.1%}). Aborting.") return if warnings: print(f" ⚠️ Warnings: {warnings}") else: print(" ✅ No blocking warnings.") except Exception as e: print(f" ⚠️ Context check failed: {e} — proceeding anyway.") ``` After this exception handler, execution continues to the trade operation when live mode is enabled. ### Technical Analysis The context request is intended to enforce safety checks for severe flip-flop warnings and excessive slippage. Any exception raised while requesting or parsing that context is caught by a broad `except Exception` handler. Rather than aborting, the handler explicitly proceeds. This creates a fail-open control: the conditions under which the application has the least reliable safety information are treated as authorization to continue. Possible triggers include network failures, timeouts, SDK exceptions, malformed responses, schema changes, missing fields with unexpected types, and service outages. Because `run(live=True)` subsequently invokes `get_client().trade(...)`, this behavior can affect real funds. The default dry-run mode reduces accidental exposure but does not protect executions ex ...[truncated 1158 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed for every context-check exception in live mode: ```python except Exception as e: print(f"⛔ Context check failed: {e}. Aborting live trade.") return ``` 2. Validate that the context response is a mapping with the expected nested types before using it. 3. Distinguish transient network failures from invalid responses, but never bypass the control for either condition during live trading. 4. Use bounded retries with exponential backoff and a short overall deadline. Abort if no validated response is obtained. 5. Require explicit positive confirmation that the context is safe instead of inferring safety from missing fields or default values. 6. Consider blocking on any warning unless it is explicitly classified as non-critical. 7. Add tests covering timeouts, SDK exceptions, malformed JSON, missing fields, wrong field types, severe flip-flop warnings, and excessive slippage. 8. Enforce account-level trade and loss limits as defense in depth. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
btc_momentum.py:127
Finding
Market Selection Does Not Enforce the Declared Five-Minute and Next-Market Constraints<![CDATA[ ## Vulnerability Details **File Location**: `btc_momentum.py:127-150` **Vulnerability Type**: Insufficient validation of the financial transaction target **Risk Level**: Medium ### Vulnerable Code ```python def find_target_market(): """Find the next active BTC sprint market.""" client = get_client() markets = client.get_markets(q="Bitcoin Up or Down", status="active", limit=20) now = datetime.now(timezone.utc) candidates = [] for m in markets: # Filter to 5-min sprint markets that haven't resolved if "Bitcoin Up or Down" not in (m.question or ""): continue prob = getattr(m, "current_probability", None) if prob is None: continue # Look for markets close to 50/50 (most liquid, best edge opportunity) candidates.append(m) if not candidates: return None # Sort by probability closest to 0.50 (most uncertain = most tradeable) candidates.sort(key=lambda m: abs(getattr(m, "current_probability", 0.5) - 0.5)) return candidates[0] ``` ### Technical Analysis The function is documented as selecting the next active five-minute Bitcoin sprint market. It does not inspect market start time, end time, duration, resolution state beyond the broad API status filter, or a canonical market type identifier. Instead, it accepts every active market whose question contains the text `Bitcoin Up or Down` and has a probability. It then selects the market whose probability is closest to 50%. The `now` value is calculated but never used. A name substring is not a robust authorization boundary for selecting a financial contract. If multiple similarly named markets are active, the chosen contract may have a different duration or settlement window from the one assumed by the one-minute momentum signal. This also means the implementation does not satisfy the declared “next active” constraint. ### Attack Path 1. The market API returns multiple active contr ...[truncated 1114 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Select markets using canonical identifiers and structured metadata rather than a question substring. 2. Require and validate all of the following before a market becomes a candidate: - Correct underlying asset. - Correct “up or down” contract type. - Exactly five-minute duration. - Expected start and end timestamps. - Unresolved and actively tradable status. - End time in the intended upcoming window. 3. Use the current UTC time to reject expired, already-started beyond tolerance, or distant contracts. 4. Sort valid candidates by the appropriate start or end timestamp, not by proximity to 50% probability. 5. Abort if metadata is missing, ambiguous, or inconsistent. 6. Before live submission, print and verify the canonical market ID, start time, end time, duration, and settlement rules. 7. Add tests with several similarly named markets of different durations and times. 8. Consider maintaining an allowlist of supported market series or validating against a trusted market-series identifier. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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
Findings (4)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented purpose understates the operational risk: the skill can execute live trades, relies on an undeclared CLI trigger, and may trade markets that are broader than the claimed 5-minute sprint scope. For a financial trading agent, this mismatch can cause users or orchestrators to invoke the skill under false assumptions, leading to unintended real-money trades or trading in the wrong market.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill advertises executable behavior that requires environment access and outbound network access, but it does not declare any tool scope or permissions boundaries. In a trading skill, this omission is dangerous because it obscures the capability to reach external services and use secrets, preventing reviewers and runtime policy from accurately constraining what the skill can do.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The usage section tells users how to enable live trading but omits a clear warning that `--live` can place real-money financial bets. In this context, lack of a prominent risk disclosure increases the chance of accidental activation and uninformed use, especially because the skill is framed as a simple template with 'plumbing' already handled.

External Transmission

Medium
Category
Data Exfiltration
Content
# ── BTC signal ────────────────────────────────────────────────────────────────

def get_binance_klines(symbol="BTCUSDT", interval="1m", limit=15):
    url = "https://api.binance.com/api/v3/klines"
    params = {"symbol": symbol, "interval": interval, "limit": limit}
    resp = requests.get(url, params=params, timeout=10)
    resp.raise_for_status()
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.