T09 · Insecure Skill Coding Practices
- Location
- scripts/shared/storage/keys.js:45
- Finding
- Private Keys Stored Unencrypted by Default Without Explicit Restrictive Permissions## Vulnerability Details **File Location**: `scripts/shared/storage/keys.js:45-57`; `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:45-57` ```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:9-12, 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 absent or rejected, `_encodeEntry` intentionally stores the raw identity private key in `kms.json` using the `plain` provider. Encryption is therefore optional and disabled in the default configuration. The generic storage writer also creates directories and temporary files without explicit permission modes. Consequently, effective access depends on the process umask and any existing directory permissions. The temporary file receives the same sensitive content before being renamed and is not explicitly restricted to the owning user. This design creates a direct local key-disclosure risk. Although local storage is necessary for the declared identity functionality, plaintext storage and reliance on ambient umask settings ...[truncated 1327 chars]
- Remediation
- ## Remediation Suggestions 1. Require encrypted private-key storage and fail closed when no valid master key or platform keystore is available. Do not silently fall back to plaintext. 2. Prefer an operating-system credential store, hardware-backed keystore, or dedicated secret-management service over a JSON file. 3. Create `$HOME/.openclaw/billions` with mode `0700`. 4. Create `kms.json` and its temporary file with mode `0600`, using exclusive creation where appropriate. 5. Before reading or writing, verify that the directory and file are owned by the expected user and are not symbolic links. 6. Use a securely generated temporary filename in the same protected directory and ensure cleanup after failed writes. 7. Detect existing plaintext entries and migrate them to encrypted storage after explicit user confirmation. 8. Reject weak master keys rather than treating them as if no key were configured. Use a password-based key derivation function such as scrypt or Argon2id when the master key is human-generated. 9. Document key rotation and recovery procedures for identities previously stored in plaintext.
