Back to skill

Security audit

Polymarket Scanner

Security checks for vulnerabilities and agentic risk

Overview

This skill can automatically place prediction-market trades, but its code does not enforce some safety controls that the documentation promises.

Review this carefully before installing. Use only an account and API key you are willing to let the script trade with, and do not rely on the documented daily-limit, stop-loss, or real-trade confirmation claims unless they are implemented server-side or added to the skill. Prefer a dry-run or read-only version for evaluation, and avoid scheduling the scanner until execution limits and confirmation behavior are clear.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scanner.py:92
Finding
Advertised Trading Safety Controls Are Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scanner.py:92-164`; conflicting security claims in `SKILL.md:32-39` and `SKILL.md:61-69` **Vulnerability Type**: Missing financial risk controls and unsafe automated trading behavior **Risk Level**: High The documentation states that the Skill enforces daily trading limits, stop-loss thresholds, and human confirmation for real USDC trades: ```markdown | Rule | Virtual ($SIM) | Real (USDC) | |------|---------------|-------------| | Single trade | ≤10% balance | ≤$100 | | Daily limit | ≤30% balance | ≤$500 | | Stop-loss | -15% | -10% | | Max positions | 5 | 3 | | Min opportunity score | 25 | 40 | | Min divergence | 3% | 5% | ``` ```markdown ## Risk Controls - All trades logged to `trading_log.jsonl` - Serious spread warnings (>10%) auto-skip - Already-held markets auto-skip - Balance <$100 auto-stop - Real USDC trades require human confirmation ``` However, the automated scan directly submits trades without implementing those controls: ```python def cmd_scan(): log("=== Market scan started ===") # 1. Status status = api_get("agents/me") balance = status.get("balance", 0) log(f"Balance: ${balance:.2f} SIM") if balance < CONFIG["min_balance"]: log(f"Balance below ${CONFIG['min_balance']}, stopping") return # 2. Positions positions = api_get("positions") active = [p for p in positions.get("positions", []) if p.get("shares", 0) > 0.01] log(f"Active positions: {len(active)}") if len(active) >= CONFIG["max_positions"]: log(f"Max positions ({CONFIG['max_positions']}) reached, skipping") return # 3. Scan opps = api_get("markets/opportunities?limit=20") opportunities = opps.get("opportunities", []) log(f"Found {len(opportunities)} opportunities") trades = 0 held_ids = {p.get("market_id") for p in active} for opp in opportunities: score = opp.get("opportunity_score", 0) divergence ...[truncated 4767 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Enforce daily limits persistently** - Store successful trade identifiers, timestamps, currency or settlement mode, and notional amounts in a durable ledger. - Before every trade, calculate the current UTC day's cumulative notional. - Reject a transaction if the proposed amount would exceed either the percentage-based or absolute daily limit. - Use server-reported transaction history as the authoritative source where available, rather than relying solely on a mutable local log. 2. **Separate virtual and real trading policies** - Determine the account and settlement mode before scanning. - Apply distinct score, divergence, position-count, per-trade, and daily limits for SIM and USDC. - Fail closed if the mode cannot be determined reliably. 3. **Require explicit confirmation for real trades** - When real trading is enabled, display the market, side, amount, current price, maximum loss, and applicable limits. - Require an explicit interactive confirmation immediately before each trade. - Disable real trading in cron and other non-interactive environments unless a separately designed, explicit opt-in policy is present. - Do not treat a generic environment variable or the presence of an API key as human confirmation. 4. **Implement stop-loss management** - Retrieve current position cost and value, calculate unrealized loss consistently, and compare it with the correct mode-specific threshold. - Submit an exit only after validating liquidity, spread, and expected execution. - Document whether stop-loss behavior is guaranteed, best-effort, or dependent on recurring scanner execution. 5. **Prevent race conditions** - Protect the daily ledger with file locking or transactional storage. - Revalidate balance, open positions, and daily exposure immediately before submission. - Where supported, provide an idempotency key to prevent duplicate trades after retries. 6. **Add automated ...[truncated 475 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

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

Critical
Category
Data Flow
Content
def api_get(path):
    r = requests.get(f"{BASE_URL}/{path}", headers=HEADERS, timeout=15)
    r.raise_for_status()
    return r.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
def api_post(path, payload):
    r = requests.post(f"{BASE_URL}/{path}", headers=HEADERS, json=payload, timeout=15)
    r.raise_for_status()
    return r.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Missing User Warnings

High
Confidence
97% confidence
Finding
The skill description and command section do not prominently warn that the default auto-scan path can execute trades automatically, including actions involving virtual funds and a workflow adjacent to real-money trading. Users may reasonably interpret "scan" as read-only analysis, so the lack of a clear warning materially increases the risk of surprise financial actions.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill advertises capabilities that imply access to environment variables, network access, and file writing, but it does not declare any explicit tool scope or permission boundaries. In a trading skill, that omission is dangerous because it can lead to unexpected execution with sensitive credentials, outbound API calls, and persistent logging without clear least-privilege controls or user awareness.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger list includes broad terms like "trade" that are common in many unrelated conversations, increasing the chance the skill is invoked unintentionally. In this context, accidental invocation is more dangerous than usual because the skill can scan markets and potentially place trades, making misfires operationally and financially risky.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The default execution path performs live trade scanning and order placement immediately when the script is run, with no interactive confirmation, dry-run default, or explicit safeguard against real trading mode. In the context of an automated trading skill that may have access to real funds, this can cause unintended financial loss from accidental invocation, misconfiguration, or unsafe upstream automation.

External Transmission

Medium
Category
Data Exfiltration
Content
sys.exit(1)

HEADERS = {"Authorization": f"Bearer {SIMMER_API_KEY}", "Content-Type": "application/json"}
BASE_URL = "https://api.simmer.markets/api/sdk"
LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "trading_log.jsonl")

# Trading rules - configurable
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
def api_post(path, payload):
    r = requests.post(f"{BASE_URL}/{path}", headers=HEADERS, json=payload, timeout=15)
    r.raise_for_status()
    return r.json()
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
All natural-language output and inline usage text in the file are hard-coded in English, and the skill provides no user option to select another language or locale. Under the stated policy, forcing a specific language without user opt-in is a natural-language policy violation.

Static analysis

No suspicious patterns detected.