T09 · Insecure Skill Coding Practices
- Location
scripts/shared/storage/base.js:26- Finding
Plaintext Private Keys Are Stored Without Enforced Owner-Only Permissions
- Content
View full analysis
entry.alias === args.alias); if (index >= 0) { keys[index].privateKeyHex = args.key; } else { keys.push({ alias: args.alias, privateKeyHex: args.key }); } await this.writeFile(keys); } ``` ```javascript // scripts/shared/storage/base.js:26-31 async writeFile(data) { await this.ensureDirectory(); const json = JSON.stringify(data, null, 2); const tempPath = `${this.filePath}.tmp`; await fs.writeFile(tempPath, json, "utf-8"); await fs.rename(tempPath, this.filePath); } ``` ### Technical Analysis The key store serializes raw private keys into `$HOME/.openclaw/billions/kms.json`. The common storage implementation creates the directory and temporary file without explicitly enforcing owner-only permissions. The resulting permissions depend on the process umask. Under a common `0022` umask, newly created files may be readable by other local accounts. The temporary file also contains the complete plaintext key before being renamed. No ownership, mode, or symbolic-link checks are performed. This behavior is necessary only to the extent that the Skill needs persistent signing capability. Storing unencrypted private keys with process-default permissions exceeds the minimum safe privilege required for that functionality. ### Attack Path 1. A user creates or imports an identity. 2. `KeysFileStorage.importKey()` places the raw private key in an in-memory JSON object. 3. `FileStorage.writeFile()` writes the object to `$HOME/.openclaw/billions/kms.json.tmp` without an explicit restrictive mode. 4. The temporary file is renamed to `kms.json`, ...[truncated 1033 chars]- Remediation
View remediation
