T09 · Insecure Skill Coding Practices
Error
- Location
- src/lib/config.ts:38
- Finding
- Wallet Private Key Is Collected Visibly and Stored in Plaintext<![CDATA[ ## Vulnerability Details **File Location**: `src/commands/setup.ts:16-33`; `src/lib/config.ts:38-41` **Vulnerability Type**: Plaintext sensitive-data storage and visible secret input **Risk Level**: High ### Vulnerable Code ```ts // src/commands/setup.ts:16-33 // 私钥 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:38-41 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 obtains the wallet private key through an ordinary `readline` question. Standard terminal input remains visible while the user types, allowing the secret to be exposed through shoulder surfing, terminal recording, screen sharing, or captured interactive-session output. The key is subsequently serialized into `config.json` as plaintext. `writeFileSync` is called without an explicit restrictive file mode such as `0o600`, so access depends on the process umask and any permissions already associated with the file. The audited project also did not include a `.gitignore` ent ...[truncated 1330 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not persist raw private keys in repository-local configuration files. 2. Prefer an operating-system credential store, encrypted keystore, hardware wallet, or external signing provider. 3. If file-based storage must remain supported: - Encrypt the key using a user-supplied passphrase and a modern authenticated encryption scheme. - Create the file with mode `0o600`. - Verify and reject unsafe permissions on existing files. - Use an atomic write strategy that preserves restrictive permissions. 4. Replace ordinary `readline` input with a secret-input mechanism that disables terminal echo. 5. Add `config.json` to `.gitignore` and packaging exclusions. 6. Store non-secret settings separately from wallet credentials. 7. Document that any previously exposed key must be retired; deleting the file alone does not invalidate a copied private key. ]]>
