T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/shared/storage/keys.js:48
- Finding
- Private Keys May Be Stored in Plaintext Without Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/shared/storage/keys.js:48-62`; `scripts/shared/storage/base.js:8-11,27-32` **Vulnerability Type**: Plaintext sensitive-data storage and unsafe file permissions **Risk Level**: High ### Vulnerable Code `scripts/shared/storage/keys.js:48-62`: ```js _encodeEntry({ alias, privateKeyHex, createdAt }) { const masterKey = getMasterKey(); if (masterKey) { return { version: 1, provider: "encrypted", data: { alias, key: encryptKey(privateKeyHex, masterKey), createdAt }, }; } return { version: 1, provider: "plain", data: { alias, key: privateKeyHex, createdAt }, }; } ``` `scripts/shared/storage/base.js:8-11,27-32`: ```js async ensureDirectory() { const dir = path.dirname(this.filePath); await fs.mkdir(dir, { recursive: true }); } ``` ```js 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 When `BILLIONS_NETWORK_MASTER_KMS_KEY` is not configured or is rejected as too short, `_encodeEntry` deliberately serializes the private key as a plain hexadecimal string. Because the master key is documented as optional, plaintext persistence is part of the normal default execution path. The shared storage implementation does not explicitly set restrictive permissions on `$HOME/.openclaw/billions`, `kms.json.tmp`, or `kms.json`. Effective permissions therefore depend on the process umask. Under common configurations, directories may be created as `0755` and files as `0644`, potentially allowing other local users or processes to read the private keys. The temporary file is exposed under the same permission model before it is renamed. The atomic rename reduces partial-write corruption but does not provid ...[truncated 1514 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Fail closed when no valid master key or secure platform keystore is available; do not silently fall back to plaintext private-key storage. 2. Prefer an operating-system keychain, hardware-backed keystore, or HSM rather than a JSON file. 3. Create `$HOME/.openclaw/billions` with mode `0700`. 4. Create temporary and final key files with mode `0600`, using exclusive creation where appropriate. 5. Explicitly apply and verify restrictive permissions after rename, including when the destination file already exists. 6. Ensure temporary files are cleaned up on write failures. 7. Avoid passing private keys through command-line arguments. Use protected standard input, an interactive hidden prompt, or a descriptor-based secret input mechanism. 8. Warn users and abort migration if an existing plaintext key file has unsafe ownership or permissions. 9. Provide a secure migration procedure that encrypts existing plaintext entries and securely removes obsolete plaintext copies. ]]>
