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. ]]>
