T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/shared/storage/base.js:27
- Finding
- Unencrypted Private Keys Stored Without Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: - `scripts/shared/storage/base.js:8-10, 27-31` - `scripts/shared/storage/keys.js:6-22` - `skills/web3dropper-verified-agent/scripts/shared/storage/base.js:8-10, 27-31` - `skills/web3dropper-verified-agent/scripts/shared/storage/keys.js:6-22` **Vulnerability Type**: Plaintext sensitive-data storage with insufficient access controls **Risk Level**: High ### Vulnerable Code ```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); } ``` ```js /** * File-based storage for cryptographic keys. * Implements AbstractPrivateKeyStore interface from js-sdk. * Stores keys in JSON format as an array of {alias, privateKeyHex} objects. */ class KeysFileStorage extends FileStorage { constructor(filename = "kms.json") { super(filename); } 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); } } ``` ### Technical Analysis The Skill deliberately persists raw private keys in `$HOME/.openclaw/billions/kms.json`. This storage is necessary for persistent identity signing, but the implementation does not apply the minimum protections appropriate for cryptographic key material. `fs.mkdir()` and `fs.writeFile()` are called without explicit modes. Their resulting permissions depend on the process umask. Under a common `022` umask, the directory may be created as `0755` and the temporary key file as `0644`, permitting other local accounts to traverse the dir ...[truncated 1432 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create the sensitive directory with mode `0700` and verify or repair the mode if it already exists: ```js await fs.mkdir(dir, { recursive: true, mode: 0o700 }); await fs.chmod(dir, 0o700); ``` 2. Create temporary key files with mode `0600`, using exclusive creation where practical: ```js await fs.writeFile(tempPath, json, { encoding: "utf-8", mode: 0o600, flag: "wx", }); ``` 3. Reject symbolic links and verify that the directory and destination are owned by the current user before reading or writing them. 4. Use randomized temporary filenames in the same protected directory, flush data as required, rename atomically, and enforce `0600` again on the final file. 5. Prefer encryption at rest through an operating-system keychain, hardware-backed keystore, or dedicated KMS. If file encryption is used, the decryption key must not be stored beside `kms.json`. 6. Apply the same correction to both shipped copies of the storage implementation. 7. Add automated tests that run under permissive umasks and verify that key directories and files remain inaccessible to group and other users. ]]>
