Back to skill

Security audit

polymarket-fast-loop

Security checks for vulnerabilities and agentic risk

Overview

This trading skill needs review because it can send authenticated trade requests while claiming paper-trading and safety controls that the code does not enforce.

Install only if you are prepared to audit and constrain it as a live financial trading tool. Use narrowly scoped credentials, restrict SIMMER_API_URL to a trusted HTTPS endpoint, and require real dry-run/live-gating changes before allowing it near funds.

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

T09 · Insecure Skill Coding Practices

Error
Location
run.py:16
Finding

Bearer API credential can be transmitted to an unrestricted network endpoint

Content
View full analysis

Vulnerability Details

File Location: run.py:16-18, run.py:59-90
Vulnerability Type: Unrestricted credential destination and unsafe endpoint configuration
Risk Level: High

Vulnerable Code

python
BASE = os.environ["SIMMER_API_URL"].rstrip("/")
KEY = os.environ.get("SIMMER_API_KEY", "sk_replay")
HEADERS = {"Authorization": f"Bearer {KEY}"}
python
def get_held_market_ids():
    r = requests.get(f"{BASE}/api/sdk/positions", headers=HEADERS, timeout=30)
    r.raise_for_status()
    return {p["market_id"] for p in r.json().get("positions", [])}

def get_balance():
    r = requests.get(f"{BASE}/api/sdk/balance", headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.json().get("balance", 1000.0)

def get_markets():
    r = requests.get(f"{BASE}/api/sdk/markets", params={"limit": 100}, headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.json().get("markets", [])

def place_trade(market_id: str, side: str, action: str, amount: float, reasoning: str):
    """Place a trade via Simmer API"""
    resp = requests.post(
        f"{BASE}/api/sdk/trade",
        json={
            "market_id": market_id,
            "side": side,
            "action": action,
            "amount": amount,
            "reasoning": reasoning,
            "skill_slug": "polymarket-fast-loop",
            "source": "fast-loop-enhanced"
        },
        headers=HEADERS,
        timeout=30
    )
    return resp.json() if resp.ok else {"error": resp.text}

Technical Analysis

The value of SIMMER_API_URL is accepted without validating its scheme, hostname, port, or destination network. The same bearer token is then attached to requests for positions, account balance, market data, and trade submission.

Network access is necessary for the declared trading functionality, but sending a financial API credential to an arbitrary configurable destinatio ...[truncated 2270 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse SIMMER_API_URL using urllib.parse.urlparse and reject malformed URLs.
  2. Require https and reject URLs containing user information, unexpected ports, fragments, or query strings.
  3. Maintain an explicit allowlist of approved API hostnames rather than accepting arbitrary destinations.
  4. Resolve the hostname and reject loopback, private, link-local, multicast, reserved, and unspecified addresses unless internal access is explicitly required.
  5. Disable automatic redirects for authenticated requests, or validate the scheme, hostname, port, and resolved destination of every redirect before forwarding credentials.
  6. Remove the "sk_replay" fallback and fail closed when SIMMER_API_KEY is absent.
  7. Use a narrowly scoped credential that cannot perform live financial actions unless live operation has been explicitly authorized.
  8. Separate read-only market-data credentials from trade-execution credentials where supported.
  9. Avoid logging authorization headers, and redact credentials from exceptions and diagnostic output.
  10. Add automated tests proving that HTTP URLs, unapproved hosts, IP literals, private addresses, and cross-origin redirects are rejected.

T09 · Insecure Skill Coding Practices

Error
Location
run.py:75
Finding

Documented paper-trading and live-trade authorization gates are not enforced

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:124-135, run.py:75-90, run.py:229-242, run.py:251-256
Vulnerability Type: Missing authorization controls for financial actions
Risk Level: High

Vulnerable Code

The Skill documentation claims that simulation is forced and that three independent conditions are required for live trading:

markdown
## Governance
- All trade decisions emit ACTA Ed25519 receipts (IETF draft-farley-acta-signed-receipts-03)
- Cedar policy engine evaluates pre-trade authorization
- Quarter-Kelly position sizing (max 10% bankroll per trade)
- Hard stop-loss at 5%, take-profit at 10%
- TRADING_VENUE=sim forced for paper trading
- 3-gate safety: TRADING_VENUE=polymarket + --live + SIMMER_MCP_ALLOW_LIVE=true required for live

## Safety
- Only executes when BOTH pipeline and swarm agree on side
- Position size capped at 10% bankroll
- Hard stop-loss at 5% from entry
- Max 3 concurrent trades per tick
- TRADING_VENUE=sim enforced (paper only)

However, the trade function submits an order directly to the configured endpoint:

python
def place_trade(market_id: str, side: str, action: str, amount: float, reasoning: str):
    """Place a trade via Simmer API"""
    resp = requests.post(
        f"{BASE}/api/sdk/trade",
        json={
            "market_id": market_id,
            "side": side,
            "action": action,
            "amount": amount,
            "reasoning": reasoning,
            "skill_slug": "polymarket-fast-loop",
            "source": "fast-loop-enhanced"
        },
        headers=HEADERS,
        timeout=30
    )
    return resp.json() if resp.ok else {"error": resp.text}

Once the strategy conditions pass, no venue or live-trading authorization check occurs before the order is submitted:

python
if not quiet:
    print(f"[FastLoop] EXECUTING TRADE: {predicted_side.upper()} ${position_size:.2f}
...[truncated 3228 chars]
Remediation
View remediation

Remediation Suggestions

  1. Default to simulation and fail closed when the trading venue is absent, unknown, or ambiguous.
  2. Bind simulation mode to a trusted, allowlisted simulation endpoint rather than relying only on a descriptive environment value.
  3. Before every live order, require all documented controls:
    • TRADING_VENUE=polymarket
    • An explicitly parsed --live command-line option
    • SIMMER_MCP_ALLOW_LIVE=true
  4. Require an additional explicit operator confirmation or externally issued authorization token for live deployment where practical.
  5. Keep strategy approval separate from financial authorization; both must succeed independently.
  6. Revalidate live authorization immediately before the network request to prevent state changes between startup and order submission.
  7. Use separate credentials for simulation and live trading, with simulation credentials technically incapable of live financial actions.
  8. Implement the documented Cedar pre-trade policy check and make a denial or policy-engine failure block the trade.
  9. Implement and verify the documented stop-loss, take-profit, and maximum hold-time controls before representing them as enforced.
  10. Emit the documented signed decision receipts only after ensuring they do not expose credentials or unnecessary sensitive account data.
  11. Add tests proving that no order can be sent when any one of the three live gates is missing, malformed, or false.
  12. Add an integration test using a live-like endpoint to confirm that ordinary invocation remains paper-only.
  13. Update SKILL.md so every claimed safeguard precisely matches implemented and tested behavior.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · run.py (reported line 57)May include surrounding context.

python
# ──────────────────────────────────────────────────────────────────────────────

def get_held_market_ids():
    r = requests.get(f"{BASE}/api/sdk/positions", headers=HEADERS, timeout=30)
    r.raise_for_status()
    return {p["market_id"] for p in r.json().get("positions", [])}

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · run.py (reported line 62)May include surrounding context.

python
return {p["market_id"] for p in r.json().get("positions", [])}

def get_balance():
    r = requests.get(f"{BASE}/api/sdk/balance", headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.json().get("balance", 1000.0)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · run.py (reported line 67)May include surrounding context.

python
return r.json().get("balance", 1000.0)

def get_markets():
    r = requests.get(f"{BASE}/api/sdk/markets", params={"limit": 100}, headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.json().get("markets", [])

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

While using an authorization header for trade placement is expected, the destination URL is fully controlled by the SIMMER_API_URL environment variable with no validation or allowlisting. In a hostile or misconfigured runtime, this could send the bearer token and trade instructions to an attacker-controlled server, causing credential disclosure and unauthorized trading actions.

Content

Scanner excerpt · run.py (reported line 73)May include surrounding context.

python
def place_trade(market_id: str, side: str, action: str, amount: float, reasoning: str):
    """Place a trade via Simmer API"""
    resp = requests.post(
        f"{BASE}/api/sdk/trade",
        json={
            "market_id": market_id,

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The skill advertises governance, receipt signing, 15-minute market support, and Polymarket trading behavior that may not actually exist. In a financial automation context, this is dangerous because operators may rely on nonexistent safety controls or venue behavior, leading to unsafe live trading assumptions, audit gaps, and miscalibrated risk decisions.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The file advertises 'Paper Trading' but the code performs real network trade submissions via /api/sdk/trade. In an agent-skill context, this is dangerous because users or orchestrators may grant execution assuming simulated behavior, leading to unexpected live orders and financial loss.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill can execute trades automatically on the default path without an explicit confirmation gate, dry-run default, or always-visible warning. In a trading agent, silent live execution raises the risk of accidental order placement, misuse by upstream automation, and financial harm from configuration errors or manipulated inputs.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · run.py (reported line 73)May include surrounding context.

python
def place_trade(market_id: str, side: str, action: str, amount: float, reasoning: str):
    """Place a trade via Simmer API"""
    resp = requests.post(
        f"{BASE}/api/sdk/trade",
        json={
            "market_id": market_id,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · run.py (reported line 99)May include surrounding context.

python
import aiohttp
        async with aiohttp.ClientSession() as session:
            async with session.get(
                "https://api.binance.com/api/v3/klines",
                params={"symbol": "BTCUSDT", "interval": "1m", "limit": 20}
            ) as resp:
                klines = await resp.json()

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The manifest explicitly says the skill trades both BTC 5-minute and 15-minute fast markets. In code, market selection is hard-coded to slugs containing only 5-minute variants, and the log message also says 'BTC 5-min markets', with no 15-minute branch or fallback anywhere in the file.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.