Back to skill

Security audit

Polymarket Twitter Cross Contagion Trader

Security checks for vulnerabilities and agentic risk

Overview

The skill is openly a trading bot and is paper-by-default, but live-trading safeguards are weaker than documented and its trading SDK dependency is unpinned.

Review this skill before installing with any live-capable key. Use only paper mode or a restricted API key with tight account-level limits unless the dependency is pinned and audited and the volume and open-position safeguards are fixed or independently enforced.

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:7
Finding
Unpinned Third-Party Trading SDK Creates a Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json:7-9`; supporting documentation at `SKILL.md:114-121` **Vulnerability Type**: Unpinned executable dependency **Risk Level**: Medium ### Vulnerable Code ```json "pip": [ "simmer-sdk" ] ``` The documentation confirms that this package is obtained from PyPI and receives access to a live-capable trading credential: ```markdown ## Dependency `simmer-sdk` is published on PyPI by Simmer Markets. - PyPI: https://pypi.org/project/simmer-sdk/ - GitHub: https://github.com/SpartanLabsXyz/simmer-sdk - Publisher: hello@simmer.markets Review the source before providing live credentials if you require full auditability. ``` ### Technical Analysis The project declares `simmer-sdk` without an exact version, lockfile, or package-integrity hash. Consequently, installation can resolve to a package release that differs from the release originally reviewed. This dependency is imported and executed by `trader.py` and is given `SIMMER_API_KEY`. In live mode it is also used to submit Polymarket trades. A compromised upstream release, publisher account, package distribution channel, or dependency of `simmer-sdk` could therefore execute arbitrary Python code in the skill's process with the same operating-system privileges and environment access as the skill. No evidence was found that the current package is malicious. The vulnerability is the absence of reproducible, integrity-verified dependency resolution. ### Attack Path 1. An attacker compromises the `simmer-sdk` publisher account, distribution pipeline, or an unpinned transitive dependency. 2. The attacker publishes a malicious package version that remains compatible with the unrestricted package name. 3. A user installs or reinstalls the skill. 4. The package manager resolves `simmer-sdk` to the malicious release. 5. The malicious code executes when Python imports or invokes the SDK. 6. It can read `SIMMER_API_KEY`, alter returned market information, mod ...[truncated 833 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to a specifically audited version, for example by using an exact `==` constraint. 2. Use a lockfile that also pins all transitive dependencies. 3. Require package hashes during installation, such as through a hash-locked requirements file. 4. Verify the package's publisher, source repository, release signatures, and build provenance. 5. Review dependency updates before changing the pinned version. 6. Use a dedicated, least-privileged API key with restricted trading limits where supported. 7. Run the skill in an isolated environment with minimal filesystem access and no unrelated credentials. 8. Add automated dependency vulnerability and integrity scanning to the release process. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:202
Finding
Configured Minimum Market Volume Safeguard Is Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:202-243`; configuration declaration at `trader.py:57` **Vulnerability Type**: Missing enforcement of a documented financial safety control **Risk Level**: Medium ### Vulnerable Code The minimum-volume value is loaded: ```python MIN_VOLUME = float(os.environ.get("SIMMER_MIN_VOLUME", "1000")) ``` However, the complete signal-validation function performs spread, resolution-time, market-type, and probability checks without checking market volume: ```python def compute_signal(market, contagion_boosts: dict[str, float]) -> 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" boost = contagion_boosts.get(person['key'], 1.0) if p <= YES_THRESHOLD: conviction = min(1.0, (YES_THRESHOLD - p) / YES_THRESHOLD * boost) size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) edge = YES_THRESHOLD - p return "yes", size, ( f"YES {p:.0%} edge={edge:.0%} contagion={boost:.2f}x ${size} " f"-- {person['key']} {bl}-{bu} -- {q[:50]}" ) if p >= NO_THRESHOLD: conviction = min(1.0, (p - NO_THRESHOLD) / (1 - NO_THRESHOLD) * boost) size = max(MIN_TRADE, round(conviction * MAX_POSITION, 2)) edge = p - NO_THRESHOLD return "no", size, ( f" ...[truncated 2100 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Retrieve the authoritative market-volume or liquidity field before signal generation. 2. Convert that field to a validated finite numeric value. 3. Reject the market when its volume is below `MIN_VOLUME`. 4. Fail closed when volume is absent, malformed, negative, or cannot be retrieved, particularly in live mode. 5. Distinguish total historical volume from currently executable liquidity and consider enforcing both volume and order-book-depth thresholds. 6. Revalidate liquidity immediately before submitting an order to reduce time-of-check/time-of-use risk. 7. Add tests covering: - Volume below, equal to, and above the threshold. - Missing and malformed volume values. - Simulated and live execution paths. - Markets with narrow spreads but inadequate depth. 8. Log the observed volume and configured threshold in every rejection or trade decision. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
trader.py:267
Finding
Maximum Open Position Limit Only Counts Orders Placed During the Current Run<![CDATA[ ## Vulnerability Details **File Location**: `trader.py:267-291` **Vulnerability Type**: Incorrect enforcement of a position-exposure limit **Risk Level**: Medium ### Vulnerable Code ```python # Phase 2: trade with contagion-adjusted conviction placed = 0 for m in markets: if placed >= MAX_POSITIONS: break side, size, reasoning = compute_signal(m, contagion_boosts) 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}") safe_print(f"[twitter-cross-contagion] done. {placed} orders placed.") ``` ### Technical Analysis The configuration and documentation describe `MAX_POSITIONS` as the maximum number of concurrent open positions. The implementation instead initializes `placed` to zero for every process invocation and increments it only when an order succeeds during that invocation. The code does not query existing open positions, pending orders, or prior exposure. It also does not reserve capacity atomically before placing an order or prevent duplicate exposure to a market. Thus, `MAX_POSITIONS` is only an order-count limit per run, not an open-position limit. If the skill is run repeatedly while earlier positions remain open, each execution can add up to `MAX_POSITIONS` more orders. Concurrent invocations can make the l ...[truncated 1503 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Query authoritative open positions and pending orders before entering the trading loop. 2. Calculate remaining capacity as the configured maximum minus existing qualifying positions and pending opening orders. 3. Stop immediately when no capacity remains. 4. Prevent duplicate market exposure unless explicitly allowed by strategy. 5. Recheck position capacity immediately before each trade. 6. Use a venue-side limit, transaction, distributed lock, or other atomic reservation mechanism to handle concurrent invocations. 7. Define whether opposite-side orders, partially filled orders, resolved positions, and closing orders count toward the limit. 8. Consider enforcing both: - A maximum count of concurrent positions. - A maximum aggregate notional exposure. 9. Add repeated-run and concurrent-run tests proving that total open exposure cannot exceed the configured cap. 10. Rename the setting to “maximum orders per run” only if that behavior is intentional; otherwise implement the documented concurrent-position semantics. ]]>
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares access to a sensitive environment credential (`SIMMER_API_KEY`) but does not define any explicit tool scope such as `permissions` or `allowed-tools`. In an agent ecosystem, missing scope boundaries can let the runtime expose more capability than intended and makes it harder to enforce least privilege around credential access and trading-related actions.

Static analysis

No suspicious patterns detected.