T09 · Insecure Skill Coding Practices
Error
- Location
- src/commands/setup.ts:18
- Finding
- Wallet Private Key Is Collected with Echo and Stored in a Plaintext Configuration File<![CDATA[ ## Vulnerability Details **File Location**: `src/commands/setup.ts:18-32`; `src/lib/config.ts:39-45` **Vulnerability Type**: Plaintext sensitive-data storage and insecure secret entry **Risk Level**: High ### Vulnerable Code ```ts // src/commands/setup.ts:18-32 const currentKey = existing.privateKey ? `${existing.privateKey.slice(0, 6)}...${existing.privateKey.slice(-4)}` : "(not set)" log(`Current private key: ${currentKey}`) const keyInput = (await question(rl, "Private key (Enter to keep current): ")).trim() const privateKey = keyInput || existing.privateKey // 网络 log(`\nCurrent network: ${existing.network}`) const netInput = (await question(rl, "Network (testnet/mainnet, Enter to keep): ")).trim() const network = (netInput === "testnet" || netInput === "mainnet") ? netInput : existing.network // RPC URL log(`\nCurrent RPC URL: ${existing.rpcUrl ?? "(default)"}`) const rpcInput = (await question(rl, "Custom RPC URL (Enter for default): ")).trim() const rpcUrl = rpcInput || existing.rpcUrl const config = { privateKey, network, ...(rpcUrl ? { rpcUrl } : {}) } writeConfig(config) ``` ```ts // src/lib/config.ts:39-45 export function writeConfig(config: Partial<Config>): void { const existing = readConfigFile() ?? {} const merged = { ...existing, ...config } writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2) + "\n") } ``` ### Technical Analysis The setup command accepts a raw blockchain private key through a normal `readline` prompt. Standard terminal input remains visible while the user types, allowing shoulder surfing, terminal recording, or session logging to capture the secret. The unencrypted private key is subsequently written into `config.json` at the project root. The write operation does not explicitly request owner-only permissions such as mode `0o600`, inspect the permissions of an existing file, or use an operating-system credential store. Its effective accessibility therefore depends on the process umask and existing ...[truncated 1479 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not store raw private keys in `config.json`. Prefer an operating-system keychain, hardware wallet, external signer, or encrypted Ethereum keystore protected by a separate passphrase. 2. Retain `FOMO3D_PRIVATE_KEY` only as an explicitly documented fallback for controlled environments, and warn users that environment variables may be exposed through process-management or diagnostic tooling. 3. Disable terminal echo while reading secrets, then restore terminal state reliably in a `finally` block. 4. If file-based storage must remain available: - create the file with mode `0o600`; - verify and reject unsafe permissions on existing files; - use atomic writes through a securely created temporary file; - never preserve an old plaintext key unintentionally. 5. Add `config.json` to `.gitignore` and provide a safe `config.example.json` containing no credentials. 6. Document wallet isolation and recommend a dedicated low-value gaming wallet rather than a wallet holding unrelated assets. 7. Validate private-key format before storage and avoid displaying any key fragments unless the user explicitly requests them. ]]>
