Back to skill

Security audit

Polymarket Macro Fear Index Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed automated trading skill, but its live-trading safeguards do not fully enforce the risk limits it advertises.

Review this carefully before installing with a live trading key. Use paper mode first, provide only a narrowly scoped and revocable SIMMER_API_KEY, pin or audit simmer-sdk, and do not rely on the documented max-position or minimum-volume safeguards unless the code is fixed or the venue enforces equivalent limits.

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

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:45
Finding

Documented Liquidity and Portfolio Exposure Safeguards Are Not Enforced

Content
View full analysis

Vulnerability Details

File Location: trader.py:45-49, trader.py:410-432, trader.py:479, and trader.py:516
Vulnerability Type: Missing liquidity filtering and portfolio-level exposure enforcement
Risk Level: High

The implementation declares configurable minimum-volume, maximum-position, and maximum-open-position controls, but it does not fully enforce them.

python
MAX_POSITION   = float(os.environ.get("SIMMER_MAX_POSITION",  "40"))
MIN_VOLUME     = float(os.environ.get("SIMMER_MIN_VOLUME",    "3000"))
MAX_SPREAD     = float(os.environ.get("SIMMER_MAX_SPREAD",    "0.10"))
MIN_DAYS       = int(os.environ.get(  "SIMMER_MIN_DAYS",      "7"))
MAX_POSITIONS  = int(os.environ.get(  "SIMMER_MAX_POSITIONS", "10"))

The trade counter is initialized for every invocation and only counts successful orders placed during that invocation:

python
# Determine regime
placed = 0
if fear_index > FEAR_THRESHOLD:
    # PANIC regime: markets are oversold by fear -- buy the most oversold
    safe_print(
        f"  [regime] PANIC (fear={fear_index:.0%} > {FEAR_THRESHOLD:.0%}) "
        f"-- looking for oversold markets to buy"
    )

    # Find all markets below YES_THRESHOLD, sorted by probability (most oversold first)
    oversold = []
    for m in markets:
        p = float(m.current_probability)
        if p <= YES_THRESHOLD:
            oversold.append(m)
    oversold.sort(key=lambda m: float(m.current_probability))

    safe_print(f"  [panic] {len(oversold)} oversold markets (p <= {YES_THRESHOLD:.0%})")

    for m in oversold:
        if placed >= MAX_POSITIONS:
            break

Equivalent per-run checks are used in the complacency and neutral regimes:

python
for m in overpriced:
    if placed >= MAX_POSITIONS:
        break
python
for m in markets:
    if placed >= MAX_POSITIONS:
        break

Technical Analys

...[truncated 2258 chars]

Remediation
View remediation

Remediation Suggestions

  1. Apply the volume filter before signal generation and trading:

    python
    volume = getattr(market, "volume", None)
    if not isinstance(volume, (int, float)) or volume < MIN_VOLUME:
        return None, 0, "Market volume is below the configured minimum"
    

    Use the actual volume field and units defined by the SDK.

  2. Query open positions and pending orders before submitting any trade. Count existing positions toward MAX_POSITIONS.

  3. Calculate remaining capacity for each market:

    python
    remaining = max(0, MAX_POSITION - existing_market_exposure - pending_exposure)
    size = min(size, remaining)
    

    Skip the trade when the remaining capacity is below the venue's minimum order size.

  4. Enforce a portfolio-wide capital or notional limit in addition to the per-market limit.

  5. Refresh portfolio state after every successful order. Where supported, use atomic SDK or venue-side risk limits to prevent concurrent runs from bypassing local checks.

  6. Prevent overlapping executions with a process lock or distributed lock when scheduled operation is enabled.

  7. Add automated tests covering existing positions, pending orders, repeated invocations, low-volume markets, and concurrent executions.

T08 · Insecure Dependencies

Warning
Location
clawhub.json:3
Finding

Unpinned Third-Party Dependency Receives a Trading Credential

Content
View full analysis

Vulnerability Details

File Location: clawhub.json:3-10; credential use occurs at trader.py:12 and trader.py:84-87
Vulnerability Type: Unpinned credential-bearing dependency
Risk Level: Medium

The project installs simmer-sdk without an exact version or integrity constraint:

json
"requires": {
  "env": [
    "SIMMER_API_KEY"
  ],
  "pip": [
    "simmer-sdk"
  ]
}

The dependency is imported and then directly receives the trading API key:

python
from simmer_sdk import SimmerClient
python
_client = SimmerClient(
    api_key=os.environ["SIMMER_API_KEY"],
    venue=venue,
)

Technical Analysis

An unpinned package specification allows different installations to resolve to different package versions. Python packages can execute arbitrary code during installation and module import. In this project, the imported dependency subsequently receives SIMMER_API_KEY, which the documentation identifies as having trading authority.

This creates a supply-chain trust boundary in which a compromised, malicious, or unexpectedly changed upstream release can execute with the permissions of the skill process and access a financially sensitive credential. No evidence was found that the currently published dependency is malicious; the vulnerability is the absence of reproducible version and integrity controls around a privileged dependency.

Attack Path

  1. An attacker compromises the upstream package publisher, package repository account, release process, or another dependency in the resolved dependency graph.
  2. A malicious package release is published under the dependency name or introduced into the resolution path.
  3. A fresh installation resolves the unpinned simmer-sdk requirement to the malicious release.
  4. Malicious code executes during package installation or when simmer_sdk is imported.
  5. The package runs in the skill process and can read environment ...[truncated 898 chars]
Remediation
View remediation

Remediation Suggestions

  1. Pin simmer-sdk to an exact version that has been reviewed:

    json
    "pip": [
      "simmer-sdk==<audited-version>"
    ]
    
  2. Use a lock file with hashes for the direct package and all transitive dependencies. Enforce hash verification during installation.

  3. Retrieve packages only from an explicitly configured trusted package index over authenticated TLS. Disable untrusted supplemental indexes.

  4. Review package source and release artifacts before updating the pin. Integrate dependency vulnerability and provenance checks into the release process.

  5. Use a narrowly scoped and revocable API key. If supported, restrict it by venue, account, order limits, source, and expiration.

  6. Run the skill as a dedicated low-privilege operating-system user or isolated container with minimal filesystem and network access.

  7. Rotate the API key immediately if dependency integrity is ever in doubt, and monitor the account for unauthorized trading activity.

Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill requires access to a sensitive credential (SIMMER_API_KEY) via environment variables but does not declare any explicit tool scope or permission boundary in the skill manifest. That creates an avoidable trust gap: an agent or runtime may grant broader environment access than necessary, increasing the chance of credential exposure or misuse, especially in a trading skill capable of live execution.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The panic/complacency branches select markets solely by price thresholds (oversold/overpriced) and then trade them regardless of whether the individual market is actually fear-related or plausibly mispriced due to panic rather than fundamentals. Because the skill fetches all markets, this creates strategy drift and can trigger trades on arbitrary markets whenever the global fear index crosses a regime threshold.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

In the neutral regime, the strategy falls back to generic threshold trading across all fetched markets, which is broader than the skill’s stated purpose of trading fear-driven overreactions. This can cause the agent to place real trades on unrelated markets without validating that they are connected to macro fear or panic/complacency conditions, increasing unintended financial exposure and making operator review harder.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.