Back to skill

Security audit

Polymarket Twitter Sentiment Spike Trader

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed automated trading skill, but its live-trading safety limits are partly missing or weaker than advertised.

Install only if you understand it can trade real USDC when run with --live. Keep it in paper mode unless you have reviewed the code and dependency, use a narrowly scoped trading key, and do not rely on the documented volume, maximum-position, or concurrent-position safeguards without fixing or independently enforcing them.

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

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:205
Finding

Minimum Market Volume Safeguard Is Declared but Never Enforced

Content
View full analysis
tuple[str | None, float, str]: p = market.current_probability q = market.question if market.spread_cents is not None and market.spread_cents / 100 > MAX_SPREAD: return None, 0, f"Spread {market.spread_cents/100:.1%} > {MAX_SPREAD:.1%}" if market.resolves_at: try: resolves = datetime.fromisoformat(market.resolves_at.replace("Z", "+00:00")) days_left = (resolves - datetime.now(timezone.utc)).days if days_left < MIN_DAYS: return None, 0, f"Only {days_left} days left" except Exception: pass person, bl, bu, period_days = parse_post_market(q) if not person: return None, 0, "Not a post-count bin market" crisis_mult = crisis_multipliers.get(person['key'], 1.0) expected = person['daily_rate'] * period_days crisis_expected = expected * crisis_mult bin_mid = (bl + bu) / 2 if crisis_mult > 1.05: if bin_mid > expected: bias = crisis_mult else: bias = max(0.4, 2.0 - crisis_mult) else: bias = 1.0 if p <= YES_THRESHOLD: conviction = min(1.0, (YES_THRESHOLD - p) / YES_THRESHOLD * bias) size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) edge = YES_THRESHOLD - p return "yes", size, ( f"YES {p:.0%} edge={edge:.0%} crisis={crisis_mult:.2f}x " f"bias={bias:.1f ...[truncated 1721 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
trader.py:240
Finding

Minimum Trade Floor Can Bypass the Maximum Position Limit

Content
View full analysis
= NO_THRESHOLD: conviction = min(1.0, (p - NO_THRESHOLD) / (1 - NO_THRESHOLD) * bias) size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) edge = p - NO_THRESHOLD return "no", size, ( f"NO {p:.0%} edge={edge:.0%} crisis={crisis_mult:.2f}x " f"bias={bias:.1f}x ${size} -- {person['key']} {bl}-{bu}" ) ``` The configuration permits incompatible values: ```json { "env": "SIMMER_MAX_POSITION", "default": 40, "range": [ 1, 200 ], "step": 1, "label": "Max position size (USD)", "type": "number" } ``` ```json { "env": "SIMMER_MIN_TRADE", "type": "number", "default": 5, "range": [ 1, 100 ], "step": 1, "label": "Min trade size (USD)" } ``` ### Technical Analysis The order calculation applies a lower bound with `max(MIN_TRADE, calculated_size)` but does not subsequently apply the declared upper bound. Because the supported configuration allows `MIN_TRADE` to exceed `MAX_POSITION`, the minimum floor can override the maximum risk limit. For example, with `SIMMER_MAX_POSITION=1` and `SIMMER_MIN_TRADE=100`, any qualifying signal produces a `$100` order even though the configured maximum position is `$1`. The same flaw affects both YES and NO orders. ### Attack Path 1. The environment or managed tunables are configured with `SIMMER_MIN_TRADE` greater than `SIMMER_MAX_POSITION`, either ...[truncated 734 chars]
Remediation
View remediation
MAX_POSITION: raise ValueError("SIMMER_MIN_TRADE cannot exceed SIMMER_MAX_POSITION") ``` 2. Apply both lower and upper bounds to every calculated order: ```python calculated = round(conviction * MAX_POSITION, 2) size = min(MAX_POSITION, max(MIN_TRADE, calculated)) ``` 3. Add a final invariant immediately before `client.trade()`: ```python if not 0 < size <= MAX_POSITION: safe_print(f" [skip] Invalid order size ${size}") continue ``` 4. Add cross-field validation to the configuration UI so it cannot persist `MIN_TRADE > MAX_POSITION`. 5. Test boundary conditions, including equal limits, fractional values, non-finite values, negative values, and the smallest and largest declared tunable values. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:293
Finding

Concurrent Position Limit Counts Only Orders from the Current Run

Content
View full analysis
= MAX_POSITIONS: break side, size, reasoning = compute_signal(m, crisis_multipliers) if not side: safe_print(f" [skip] {reasoning}") continue ok, why = context_ok(client, m.id) if not ok: safe_print(f" [skip] {why}") continue try: r = client.trade( market_id=m.id, side=side, amount=size, source=TRADE_SOURCE, skill_slug=SKILL_SLUG, reasoning=reasoning, ) tag = "(sim)" if r.simulated else "(live)" status = "OK" if r.success else f"FAIL:{r.error}" safe_print(f" [trade] {side.upper()} ${size} {tag} {status} -- {reasoning[:70]}") if r.success: placed += 1 except Exception as e: safe_print(f" [error] {m.id}: {e}") ``` ### Technical Analysis `MAX_POSITIONS` is documented as the maximum number of concurrent open positions. The implementation initializes `placed` to zero on every invocation and increments it only for successful orders made during that invocation. The code does not query existing open positions, pending orders, or positions opened by previous executions. Consequently, the control limits orders per run rather than concurrent portfolio exposure. ### Attack Path 1. The account already has open positions, potentially including positions created by an earlier invocation. 2. The skill starts and resets `placed` to zero. 3. It places up to `MAX_POSITIONS` additional successful orders without considering existing exposure. 4. The skill is executed repeatedly, manually or through later scheduling. 5. Each invocation can add another set of positions, causing the portfolio to exceed the documente ...[truncated 367 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
clawhub.json:5
Finding

Live Trading Dependency Is Not Version-Pinned

Content
View full analysis
Remediation
View remediation
" ] ``` 2. Use a lockfile or deployment mechanism that records cryptographic package hashes. 3. Install only from the expected package index over authenticated TLS and disable unneeded extra indexes to reduce dependency-confusion exposure. 4. Review release provenance, source changes, maintainers, and transitive dependencies before upgrading. 5. Run the trading process with least privilege and a narrowly scoped API key. 6. Keep live-capable credentials out of paper-only environments. 7. Where supported, restrict network destinations and filesystem access available to the dependency. 8. Perform upgrades through an explicit review process rather than automatically resolving the newest available release. ]]>
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 (2)

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The provided skill context says this skill detects crisis/news spikes and adjusts expected posting rates upward for post-count bins, which describes an analytics or forecasting function. However, this manifest defines a managed entrypoint named "trader.py" and tunables for position size, market volume, bid-ask spread, trade size, YES/NO thresholds, and max open positions, indicating an automated trading strategy rather than merely adjusting posting-rate expectations.

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.

Static analysis

No suspicious patterns detected.