Back to skill

Security audit

Storyclaw Polymarket Trading

Security checks for vulnerabilities and agentic risk

Overview

This is a real Polymarket automation skill, but it handles wallet credentials and scheduled live-trading workflows with unsafe scoping and persistence gaps.

Review this carefully before installing. Use a dedicated wallet with limited funds, keep dry-run enabled until you personally inspect results, avoid cron setup unless you understand how to remove it, do not use untrusted USER_ID or strategy IDs, and install dependencies only in an isolated virtual environment with pinned versions.

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)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:98
Finding
Persistent Command Injection Through Unsafely Generated Cron Entries## Vulnerability Details **File Location**: `SKILL.md:98-101` **Vulnerability Type**: Shell command injection in persistent scheduled tasks **Risk Level**: High ### Vulnerable Code ```bash # Signal scan: every 15 minutes (crontab -l 2>/dev/null; echo "*/15 * * * * USER_ID=$TELEGRAM_USER_ID python3 $SKILL_PATH/scripts/signal_cron.py $STRATEGY_ID >> $SKILL_PATH/state/$TELEGRAM_USER_ID.$STRATEGY_ID.log 2>&1") | crontab - # Performance review: daily at 09:00 UTC (crontab -l 2>/dev/null; echo "0 9 * * * USER_ID=$TELEGRAM_USER_ID python3 $SKILL_PATH/scripts/review_cron.py $STRATEGY_ID >> $SKILL_PATH/state/$TELEGRAM_USER_ID.review.log 2>&1") | crontab - ``` ### Technical Analysis The cron instructions interpolate `TELEGRAM_USER_ID`, `SKILL_PATH`, and `STRATEGY_ID` directly into shell and crontab syntax without validation or safe quoting. A strategy ID may be supplied through the strategy configuration, and the implementation does not restrict its characters. Shell metacharacters in any interpolated value can change the command executed by cron. A newline can additionally terminate the current cron record and inject another scheduled entry. Because the resulting command is installed into the user's crontab, exploitation persists across sessions and repeatedly executes with the privileges of the account that installed the job. The use of scheduled execution is relevant to the declared automated market-scanning functionality and the documentation requires user confirmation before installation. The persistence mechanism therefore has a legitimate purpose, but its unsafe construction exceeds what is necessary and turns untrusted identifiers into a persistent code-execution channel. ### Attack Path 1. An attacker influences a strategy configuration so that its explicit `id` contains shell syntax or a newline followed by another cron entry. 2. The strategy is created, and its attacker-controlled ID is returned as `STRATEGY_ID ...[truncated 938 chars]
Remediation
## Remediation Suggestions 1. Restrict user and strategy identifiers to a conservative allowlist such as `^[A-Za-z0-9_-]+$`. 2. Reject newlines, whitespace, path separators, shell metacharacters, and leading hyphens. 3. Resolve `SKILL_PATH` to a trusted absolute path and verify that it points to the installed Skill directory. 4. Avoid composing cron records through interpolated shell strings. Generate a fixed wrapper script whose arguments are loaded from a validated configuration file. 5. If arguments must appear in a cron record, quote them using a robust shell-quoting mechanism and separately reject newline characters because shell quoting does not safely delimit cron records. 6. Install cron entries with unique markers, detect duplicates, and provide an explicit removal command. 7. Display the exact generated cron records and request confirmation immediately before installation. 8. Prefer a scheduler interface that accepts an argument array rather than invoking a shell.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/strategy_manager.py:59
Finding
Path Traversal Through Unsanitized User and Strategy Identifiers## Vulnerability Details **File Locations**: - `scripts/strategy_manager.py:59-80, 223` - `scripts/polymarket.py:40-70` - `scripts/signal_cron.py:41-46` - `scripts/check_and_report.py:43-89` **Vulnerability Type**: Path traversal and cross-user file access **Risk Level**: High ### Vulnerable Code Primary path construction in `scripts/strategy_manager.py`: ```python def get_user_id(): uid = os.environ.get("USER_ID") or os.environ.get("TELEGRAM_USER_ID") if not uid: print("❌ USER_ID not set") sys.exit(1) return uid def strategies_dir(user_id): d = os.path.join(STRATEGIES_DIR, user_id) os.makedirs(d, exist_ok=True) return d def strategy_path(user_id, strategy_id): return os.path.join(strategies_dir(user_id), f"{strategy_id}.json") def perf_path(user_id, strategy_id): state_dir = os.path.join(SKILL_DIR, "state") os.makedirs(state_dir, exist_ok=True) return os.path.join(state_dir, f"{user_id}.{strategy_id}.perf.json") ``` Caller-controlled strategy IDs are accepted without validation: ```python strategy_id = config.get("id") or f"strategy-{str(uuid.uuid4())[:8]}" ``` Equivalent credential and state path construction in `scripts/polymarket.py`: ```python user_id = os.environ.get("USER_ID") or os.environ.get("TELEGRAM_USER_ID") cred_path = os.path.join(CREDENTIALS_DIR, f"{user_id}.json") ``` ```python def save_config(user_id, config): cred_path = os.path.join(CREDENTIALS_DIR, f"{user_id}.json") with open(cred_path, "w") as f: json.dump(config, f, indent=2) os.chmod(cred_path, 0o600) ``` ```python def get_state_path(user_id): return os.path.join(STATE_DIR, f"{user_id}.state.json") ``` ### Technical Analysis Environment-derived user IDs and configuration-derived strategy IDs are used directly as filesystem path components. The code does not reject absolute paths, `..` components, ...[truncated 2279 chars]
Remediation
## Remediation Suggestions 1. Validate every `USER_ID`, `TELEGRAM_USER_ID`, and strategy ID against a strict allowlist such as `^[A-Za-z0-9_-]{1,64}$`. 2. Reject absolute paths, `..`, path separators, null characters, control characters, and empty identifiers. 3. Centralize identifier validation so all scripts apply identical rules. 4. Resolve each generated path and verify containment before opening it. For example, use `Path.resolve()` and confirm that the trusted root is one of the resolved path's parents. 5. Use server-generated opaque strategy IDs rather than accepting arbitrary IDs from configuration. If custom labels are required, store them as metadata rather than filenames. 6. Apply restrictive permissions to the credential directory and explicitly set credential files to mode `0600`. 7. Apply appropriate restrictive permissions to strategy and state files because they can influence live trading behavior. 8. Separate each user's storage under a validated, server-assigned internal identifier rather than relying directly on externally supplied Telegram identifiers. 9. Add tests covering absolute paths, nested traversal, mixed separators, encoded separators, newlines, and cross-user access attempts.

T08 · Insecure Dependencies

Warning
Location
SKILL.md:137
Finding
Unpinned High-Privilege Trading Dependency## Vulnerability Details **File Locations**: - `SKILL.md:137` - `scripts/polymarket.py:96-102` **Vulnerability Type**: Unsafe third-party dependency installation **Risk Level**: Medium ### Vulnerable Code Installation guidance in `SKILL.md`: ```bash pip3 install py-clob-client ``` Runtime import in `scripts/polymarket.py`: ```python try: from py_clob_client.client import ClobClient from py_clob_client.clob_types import ApiCreds except ImportError: print("❌ py-clob-client not installed") print(" Run: pip3 install py-clob-client --break-system-packages") sys.exit(1) ``` ### Technical Analysis The Skill instructs users to install `py-clob-client` without pinning a reviewed version, locking transitive dependencies, or verifying package hashes. The fallback instruction additionally recommends `--break-system-packages`, which can modify an externally managed Python environment. This dependency is security-sensitive: it receives the user's Polygon wallet private key, derives API credentials, authenticates to Polymarket, signs orders, and can submit live trades. A compromised upstream release, package index, mirror, or transitive dependency would execute with access to those credentials and trading capabilities. No evidence shows that the currently named package is malicious. The finding concerns the unsafe and mutable dependency installation process. ### Attack Path 1. An attacker compromises a future package release, a transitive dependency, or the package index configured on the target host. 2. A user follows the unpinned installation command. 3. Pip resolves and installs the attacker-controlled version. 4. Python imports the package when a Skill command is executed. 5. Malicious package code executes in the Skill process and receives wallet keys or API credentials when the client is initialized. 6. The attacker can exfiltrate credentials, alter signed requests, or submit un ...[truncated 376 chars]
Remediation
## Remediation Suggestions 1. Pin `py-clob-client` to a specifically reviewed version. 2. Lock all transitive dependencies and install them with verified hashes using a requirements lock file and `pip --require-hashes`. 3. Document and enforce the expected trusted package index. 4. Install dependencies in a dedicated virtual environment instead of recommending `--break-system-packages`. 5. Review dependency updates before changing the lock file and use automated supply-chain vulnerability scanning. 6. Where feasible, isolate the signing component and minimize the amount of code that can access the private key. 7. Avoid exposing long-lived wallet keys to general-purpose dependency code; use narrowly scoped credentials or a dedicated signing service when supported.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (35)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
If the implementation truly lacks the advertised self-improvement, paper trading, and confirmation workflow, the primary issue is misleading documentation rather than direct exploitation. However, in a financial skill, false safety expectations still matter because users may provide secrets or rely on controls that do not exist.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If the implementation truly lacks the advertised self-improvement, paper trading, and confirmation workflow, the primary issue is misleading documentation rather than direct exploitation. However, in a financial skill, false safety expectations still matter because users may provide secrets or rely on controls that do not exist.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
If the implementation truly lacks the advertised self-improvement, paper trading, and confirmation workflow, the primary issue is misleading documentation rather than direct exploitation. However, in a financial skill, false safety expectations still matter because users may provide secrets or rely on controls that do not exist.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
If the implementation truly lacks the advertised self-improvement, paper trading, and confirmation workflow, the primary issue is misleading documentation rather than direct exploitation. However, in a financial skill, false safety expectations still matter because users may provide secrets or rely on controls that do not exist.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
If the implementation truly lacks the advertised self-improvement, paper trading, and confirmation workflow, the primary issue is misleading documentation rather than direct exploitation. However, in a financial skill, false safety expectations still matter because users may provide secrets or rely on controls that do not exist.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The skill instructs modification of the user's crontab, creating persistent background execution that can keep trading or processing credentials after the interactive session ends. Persistence is especially risky in a trading context because it can lead to unattended financial actions, ongoing API usage, and difficult-to-notice abuse if the skill or scripts are changed later.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
1000,
    "max_days_to_expiry": 30,
    "min_days_to_expiry": 1
  },
  "signal": {
    "method": "orderbook_imbalance",
    "params": { "threshold": 0.15, "max_entry_price": 0.60 }
  },
  "sizing": { "max_size_usdc": 5 },
  "targets": { "min_sample_size": 30, "min_edge": 0.05 }
}'
```

### 5. Set up crons

```bash
SKILL_PATH={baseDir}
STRATEGY_ID=<id from step 4>

# Signal scan: every 15 minutes
(crontab -l 2>/dev/null; echo "*/15 * * * * USER_ID=$TELEGRAM_USER_ID python3 $SKILL_PATH/scripts/signal_cron.py $STRATEGY_ID >> $SKILL_PATH/state/$TELEGRAM_USER_ID.$STRATEGY_ID.log 2>&1") | crontab -

# Performance review: daily at 09:00 UTC
(crontab -l 2>/dev/null; echo "0 9 * * * USER_ID=$TELEGRAM_USER_ID python3 $SKILL_PATH/scripts/review_cron.py $STRATEGY_ID >> $SKILL_PATH/state/$TELEGRAM_USER_ID.review.log 2>&1") | crontab -
```

## Strategy Lifecycle

```
dry_run → improving → pending_live → live
```

Go-live condition: `edge = win_rate - avg_entry_price >= min_edge` AND `total_pnl
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def run_script(cmd_args, user_id):
    env = os.environ.copy()
    env["USER_ID"] = user_id
    result = subprocess.run(
        ["python3", SCRIPT] + cmd_args,
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def run_script(cmd_args, user_id):
    env = os.environ.copy()
    env["USER_ID"] = user_id
    result = subprocess.run(
        ["python3", SCRIPT] + cmd_args,
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill requests and uses powerful capabilities (environment secrets, filesystem access, network, and shell) but does not declare any tool scope or permission boundaries. In a trading skill that stores private keys and invokes shell commands, this increases the chance of overbroad execution and makes sensitive actions harder to audit or constrain.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The manifest description says the skill is a "Self-evolving Polymarket trading bot" that can design strategy, run paper trading, auto-improve, and switch to live, but it does not specify how or when the skill should be invoked. For a markdown/manifest file, this broad description lacks explicit trigger phrases, scope limits, or negative examples, which could lead to unintended activation in general trading-related conversations.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill instructs storing private trading credentials in per-user JSON files without an explicit warning about plaintext secret storage or local compromise risk. In a financial automation skill, exposed private keys can directly enable unauthorized trading or fund loss, making the context significantly more dangerous.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The cron setup omits a clear warning that it creates persistent background tasks that continue after the user session and may affect system behavior, logs, network usage, and possibly live trading. Missing disclosure undermines informed consent and makes it easier for users to enable automation they do not fully understand.

Session Persistence

Medium
Category
Rogue Agent
Content
STRATEGY_ID=<id from step 4>

# Signal scan: every 15 minutes
(crontab -l 2>/dev/null; echo "*/15 * * * * USER_ID=$TELEGRAM_USER_ID python3 $SKILL_PATH/scripts/signal_cron.py $STRATEGY_ID >> $SKILL_PATH/state/$TELEGRAM_USER_ID.$STRATEGY_ID.log 2>&1") | crontab -

# Performance review: daily at 09:00 UTC
(crontab -l 2>/dev/null; echo "0 9 * * * USER_ID=$TELEGRAM_USER_ID python3 $SKILL_PATH/scripts/review_cron.py $STRATEGY_ID >> $SKILL_PATH/state/$TELEGRAM_USER_ID.review.log 2>&1") | crontab -
Confidence
97% confidence
Finding
This line appends a recurring cron job to the user's crontab, establishing persistence for repeated execution of trading-related logic. Persistent scheduled execution with access to user identifiers, local files, and potentially credentials can continue making or preparing financial actions without active oversight.

Session Persistence

Medium
Category
Rogue Agent
Content
(crontab -l 2>/dev/null; echo "*/15 * * * * USER_ID=$TELEGRAM_USER_ID python3 $SKILL_PATH/scripts/signal_cron.py $STRATEGY_ID >> $SKILL_PATH/state/$TELEGRAM_USER_ID.$STRATEGY_ID.log 2>&1") | crontab -

# Performance review: daily at 09:00 UTC
(crontab -l 2>/dev/null; echo "0 9 * * * USER_ID=$TELEGRAM_USER_ID python3 $SKILL_PATH/scripts/review_cron.py $STRATEGY_ID >> $SKILL_PATH/state/$TELEGRAM_USER_ID.review.log 2>&1") | crontab -
```

## Strategy Lifecycle
Confidence
97% confidence
Finding
This second cron entry creates additional daily persistence for automated review logic, again extending operation beyond the active session. Multiple scheduled jobs broaden the attack surface because compromised scripts or altered state files can be re-executed automatically and repeatedly.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The manifest describes a Polymarket trading bot that designs strategies, paper trades, auto-improves, and optionally switches to live trading. The troubleshooting guidance instructs the agent to run `pip3 install py-clob-client`, adding software-installation capability that is broader than the trading function itself and introduces host-modification behavior not otherwise scoped by the manifest.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_script(cmd_args, user_id):
    env = os.environ.copy()
    env["USER_ID"] = user_id
    result = subprocess.run(
        ["python3", SCRIPT] + cmd_args,
        capture_output=True, text=True, env=env,
    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def notify(user_id, message):
    try:
        subprocess.run(
            ["openclaw", "notify", "--user", user_id, "--message", message],
            capture_output=True, timeout=10,
        )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def notify(user_id, message):
    try:
        subprocess.run(
            ["openclaw", "notify", "--user", user_id, "--message", message],
            capture_output=True, timeout=10,
        )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The module docstring and command set expose manual commands for checking markets, viewing books, generating simple signals, placing/canceling orders, and viewing history/P&L. There is no code for collaborative strategy design, iterative self-improvement, measuring an edge target, or prompting to transition from paper trading to live once a threshold is met, so the implemented behavior materially differs from the manifest description.

Tainted flow: 'cred_path' from os.environ.get (line 62, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
def save_config(user_id, config):
    cred_path = os.path.join(CREDENTIALS_DIR, f"{user_id}.json")
    with open(cred_path, "w") as f:
        json.dump(config, f, indent=2)
    os.chmod(cred_path, 0o600)
    print(f"✅ Config saved to {cred_path}")
Confidence
95% confidence
Finding
The credential filename is built directly from USER_ID/TELEGRAM_USER_ID and then opened for writing without sanitization. An attacker who can control that environment variable can use path traversal sequences such as '../' to write arbitrary JSON files outside the credentials directory, which is especially dangerous because this script also stores private keys and API secrets.

Tainted flow: 'user_id' from input (line 812, user input) → open (file write)

Medium
Category
Data Flow
Content
def save_state(user_id, state):
    with open(get_state_path(user_id), "w") as f:
        json.dump(state, f, indent=2)
Confidence
89% confidence
Finding
State files are written using a path derived from user-controlled user_id with no validation. If an attacker can influence the identifier, they may overwrite arbitrary files reachable by the process via directory traversal, and because state writes occur during normal operation this can be triggered beyond initial setup.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The docstring says 'Show open positions (token holdings),' which implies account holdings or exposures. Instead, the function calls `client.get_orders(params={"status": "LIVE"})`, the same kind of open-order query used by `cmd_orders`, so the documentation actively misstates what the command returns.

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
The docstring says 'Show realized P&L summary' and defines net P&L as revenue minus cost, but the code computes totals across all grouped trades and then separately notes `open_cost` for positions where buys exceed sells. Because `total_net` still includes unmatched/open exposure, the displayed 'Realized P&L' can include unrealized or unsettled components, contradicting the function's stated intent.

Tainted flow: 'perf_path' from os.environ.get (line 690, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
"win_rate": round(wins / total, 4) if total > 0 else 0.0,
    }

    with open(perf_path, "w") as f:
        json.dump(perf, f, indent=2)

    print(f"\n📊 Settlement complete: {len(newly_settled)} settled, {len(still_pending)} still pending")
Confidence
91% confidence
Finding
The performance file path is derived from an environment-controlled user_id and then written without confinement checks. This creates another arbitrary file write primitive through path traversal, which can overwrite application or user files and is more concerning in a trading skill that handles sensitive financial state.

Static analysis

No suspicious patterns detected.