T09 · Insecure Skill Coding Practices
- Location
- scripts/shared/storage/keys.js:13
- Finding
- Unencrypted Private Keys Are Stored Without Explicit Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/shared/storage/keys.js:13-23`; `scripts/shared/storage/base.js:24-30` **Vulnerability Type**: Plaintext sensitive-data storage and unsafe temporary-file permissions **Risk Level**: High ### Vulnerable Code `scripts/shared/storage/keys.js:13-23`: ```js async importKey(args) { const keys = await this.readFile(); const index = keys.findIndex((entry) => 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); } ``` `scripts/shared/storage/base.js:24-30`: ```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 The key store serializes private keys directly into JSON and writes them to `$HOME/.openclaw/billions/kms.json`. The temporary file and final file are not created with an explicit `0600` mode, while the containing directory is not explicitly created with a `0700` mode. Protection therefore depends on the process umask and any permissions already present on the directory or files. The temporary file contains the same plaintext private keys as the final file. If a write or rename fails, that temporary file may remain on disk. No operating-system keychain, encrypted keystore, hardware-backed key store, or application-level encryption protects the key material. Although private-key storage is necessary for the declared signing functionality, plaintext storage without enforced access controls exceeds the minimum safe privilege model for long-lived identity credentials. ### Attack Path 1. A user creates or imports an identity through the Skill. 2. The Skill writes the identity private key into `kms.json.tmp` and renames it to `kms.json`. ...[truncated 1031 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store long-lived private keys in an operating-system keychain, hardware security module, hardware wallet, or encrypted keystore rather than plaintext JSON. 2. Create `$HOME/.openclaw/billions` with mode `0700` and verify that an existing directory is not group- or world-accessible. 3. Create key files and temporary files with mode `0600`, for example by passing `{ encoding: "utf-8", mode: 0o600 }` when creating them. 4. Check and correct the permissions of existing `kms.json` files before reading or updating them. 5. Use unique, unpredictable temporary filenames and ensure they are removed in a `finally` block after failures. 6. Consider authenticated encryption with a key obtained from a secure prompt or platform keychain if a dedicated secure storage provider is unavailable. 7. Avoid including private-key values in logs, thrown errors, backups, or diagnostic output. ]]>
