Back to skill

Security audit

Neckr0ik Polymarket Paper

Security checks for vulnerabilities and agentic risk

Overview

This is a local paper-trading simulator, but its account-file handling can be tricked into reading or overwriting JSON files outside its own data folder.

Review before installing. The skill appears to be a local simulator, not malware, but it should validate account IDs and monetary inputs before use. Treat generated account and trading history files as private, and do not run it with untrusted account identifiers or crafted account JSON files.

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
scripts/paper_trading.py:222
Finding
Path Traversal Through Unvalidated Account Identifiers<![CDATA[ ## Vulnerability Details **File Location**: `scripts/paper_trading.py:176-178, 222-229` **Vulnerability Type**: Path traversal enabling unintended local file access and overwrite **Risk Level**: High ### Vulnerable Code ```python def _save_account(self, account: Account): """Save account to file.""" account_file = self.accounts_dir / f"{account.account_id}.json" data = { "account_id": account.account_id, # Remaining account fields omitted here } account_file.write_text(json.dumps(data, indent=2)) ``` ```python def _load_account(self, account_id: str) -> Optional[Account]: """Load account from file.""" account_file = self.accounts_dir / f"{account_id}.json" if not account_file.exists(): return None data = json.loads(account_file.read_text()) ``` ### Technical Analysis The account identifier supplied through the `--account` CLI option is used directly to construct a filesystem path. The application neither restricts the identifier to the generated 12-character hexadecimal format nor verifies that the resolved path remains inside `self.accounts_dir`. Because `pathlib.Path` does not remove traversal components when joining paths, an identifier such as `../../target` causes the application to access a path equivalent to: ```text ~/.polymarket-paper/accounts/../../target.json ``` The loader consequently permits reading a JSON file outside the intended account directory if that file exists and follows the expected account schema. The write path creates an additional hazard. `_load_account()` accepts the `account_id` stored inside the loaded JSON file, and mutating operations later pass the resulting object to `_save_account()`. A crafted account document can therefore place traversal components in its internal `account_id` field and direct the subsequent write outside the account directory. ### Attack Path 1. An attacker creates or identifies a JSON file accessibl ...[truncated 1528 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce the generated account identifier format before any filesystem access: ```python import re if not re.fullmatch(r"[a-f0-9]{12}", account_id): raise ValueError("Invalid account ID") ``` 2. Resolve the final path and verify that it remains inside the account directory: ```python base = self.accounts_dir.resolve() account_file = (base / f"{account_id}.json").resolve() if account_file.parent != base: raise ValueError("Account path escapes account directory") ``` 3. When loading an account, require the identifier stored in the document to exactly match the validated identifier requested by the caller: ```python if data.get("account_id") != account_id: raise ValueError("Account ID mismatch") ``` 4. Centralize account-path construction in one validated helper and use it for both reads and writes. 5. Open account files with restrictive permissions and use atomic replacement to reduce corruption risks. 6. Treat account files as untrusted input and validate their complete schema, including field types, numeric ranges, enumeration values, and identifier formats. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/paper_trading.py:288
Finding
Negative and Non-Finite Monetary Values Permit Account and Leaderboard Manipulation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/paper_trading.py:288-329, 504, 521` **Vulnerability Type**: Improper numeric input validation and business-logic abuse **Risk Level**: Medium ### Vulnerable Code ```python def place_trade(self, account_id: str, market_id: str, side: str, amount: float) -> Trade: """Place a paper trade.""" account = self._load_account(account_id) if not account: raise ValueError(f"Account not found: {account_id}") # Check cash if account.cash < amount: raise ValueError(f"Insufficient cash. Have ${account.cash:.2f}, need ${amount:.2f}") # Get market market = None for m in self.sample_markets: if m["id"] == market_id: market = m break if not market: raise ValueError(f"Market not found: {market_id}") # Get price prices = self.get_market_prices(market_id) price = prices["yes_price"] if side.upper() == "YES" else prices["no_price"] # Calculate shares shares = amount / price # Calculate fee fee = amount * self.TRADING_FEE # Execute trade trade_id = hashlib.md5(f"{account_id}-{market_id}-{time.time()}".encode()).hexdigest()[:12] trade = Trade( trade_id=trade_id, market_id=market_id, market_question=market["question"], side=TradeSide(side.upper()), amount=amount, price=price, shares=shares, fee=fee, timestamp=time.time(), ) # Update account account.cash -= amount account.trades.append(trade) ``` The command-line arguments accept arbitrary floating-point values: ```python create_parser.add_argument('--initial', type=float, help='Initial cash') ``` ```python trade_parser.add_argument('--amount', type=float, required=True) ``` ### Technical Analysis The application checks only whether the account has less cash than the requested trade amount. It does not re ...[truncated 2488 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require all monetary inputs to be finite and strictly positive: ```python import math def validate_positive_amount(value: float, field: str) -> float: if not math.isfinite(value) or value <= 0: raise ValueError(f"{field} must be a finite value greater than zero") return value ``` 2. Validate the trade amount inside `place_trade()` rather than relying solely on CLI validation: ```python amount = validate_positive_amount(amount, "Trade amount") ``` 3. Apply equivalent validation to `initial_cash`, including a documented maximum: ```python if not math.isfinite(initial_cash) or initial_cash <= 0: raise ValueError("Initial cash must be finite and greater than zero") ``` 4. Define reasonable upper bounds for initial balances, trades, shares, and payouts to prevent extreme-value abuse. 5. Validate numeric fields loaded from account JSON before performing calculations. Reject `NaN`, positive infinity, negative infinity, negative balances, and impossible position values. 6. Add invariant checks before persistence, including: - `cash` must be finite and non-negative. - Trade amounts, prices, shares, and fees must be finite and non-negative. - Initial cash must be finite and positive. - Resolution payouts must be finite and non-negative. 7. Add regression tests covering negative values, zero, `nan`, `inf`, `-inf`, excessively large values, and repeated trade-and-resolution attempts. ]]>
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 (2)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill advertises commands that likely require network access and file read/write behavior, but it does not declare any tool scope or permissions boundaries. That makes the effective capability set opaque to users and hosting agents, increasing the risk of over-privileged execution or unintended access if the implementation performs broader filesystem or network actions than expected.

Missing User Warnings

Low
Confidence
83% confidence
Finding
This markdown file documents an `export` command that writes trading history to a user-specified output file, but it does not warn that the exported data may contain sensitive portfolio or activity information. Under the markdown-specific warning rule, data-affecting behavior should include some disclosure when it can impact user privacy.

Static analysis

No suspicious patterns detected.