T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/shared/storage/keys.js:50
- Finding
- Private Keys Are Stored in Plaintext by Default Without Enforced Owner-Only Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/shared/storage/keys.js:50-56`, `scripts/shared/storage/crypto.js:11-25`, `scripts/shared/storage/base.js:9-12,27-32` **Vulnerability Type**: Plaintext sensitive-data storage and insufficient filesystem permission enforcement **Risk Level**: High ### Vulnerable Code `scripts/shared/storage/keys.js:50-56`: ```js return { version: 1, provider: "plain", data: { alias, key: privateKeyHex, createdAt }, }; ``` The corresponding plaintext decoding behavior is: ```js if (entry.provider === "plain") { return { alias, privateKeyHex: key, createdAt }; } ``` `scripts/shared/storage/crypto.js:11-25`: ```js function getMasterKey() { const rawKey = process.env.BILLIONS_NETWORK_MASTER_KMS_KEY; if (typeof rawKey !== "string") { return null; } const trimmedKey = rawKey.trim(); const MIN_MASTER_KEY_LENGTH = 16; if (trimmedKey.length < MIN_MASTER_KEY_LENGTH) { return null; } return trimmedKey; } ``` `scripts/shared/storage/base.js:9-12,27-32`: ```js async ensureDirectory() { const dir = path.dirname(this.filePath); await fs.mkdir(dir, { recursive: true }); } 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 Skill stores identity private keys under `$HOME/.openclaw/billions/kms.json`. Encryption is optional: when `BILLIONS_NETWORK_MASTER_KMS_KEY` is missing or shorter than 16 characters, `getMasterKey()` returns `null`, and `KeysFileStorage` serializes the raw private key as a plaintext hexadecimal string. An invalid but configured master key is treated identically to an absent key. The operation does not fail or warn that sensitive key material will be stored without encryption. The storage layer also does not explicitly enforce mode `0700` on the cont ...[truncated 1967 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Fail closed for private-key creation** - Require a valid master key before creating or importing long-lived private keys. - If plaintext storage must remain available for compatibility, require an explicit insecure-development flag and display a prominent warning. - Treat a configured but invalid master key as an error rather than silently falling back to plaintext. 2. **Enforce restrictive filesystem permissions** - Create `$HOME/.openclaw/billions` with mode `0700`. - Create key files and temporary files with mode `0600`. - Validate and correct permissions on existing files before reading or updating them. - Refuse to use paths owned by another account or paths that resolve through unsafe symbolic links. 3. **Harden temporary-file handling** - Use a randomized, exclusively created temporary file in the same directory. - Open it with an exclusive-creation flag and mode `0600`. - Flush file contents before atomic rename where durability is required. - Clean up temporary files on failure. 4. **Improve master-key handling** - Clearly report whether encrypted storage is active. - Use a proper password-based KDF such as Argon2id or scrypt with a random salt if human-memorable passphrases are accepted. - Prefer a platform secret store or hardware-backed key manager where available. 5. **Migrate existing installations** - Detect plaintext entries at startup. - Provide a safe migration operation that encrypts all plaintext keys after a master key is configured. - Warn users that previously exposed keys may require rotation, since encryption after exposure cannot restore confidentiality. ]]>
