T09 · Insecure Skill Coding Practices
- Location
scripts/shared/storage/keys.js:48- Finding
Private Keys Are Stored in Plaintext by Default Without Enforced File Permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/shared/storage/keys.js:48-60;scripts/shared/storage/base.js:8-9, 27-32
Vulnerability Type: Plaintext storage of cryptographic private keys and insufficient filesystem permission enforcement
Risk Level: HighVulnerable Code
javascript _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 }, }; }javascript 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
KeysFileStorage._encodeEntry()stores private keys directly inkms.jsonwheneverBILLIONS_NETWORK_MASTER_KMS_KEYis missing, empty, or rejected as too short. Encryption is therefore optional rather than a security requirement.The common storage implementation creates the sensitive directory and files without explicit permission modes. Their permissions consequently depend on the process umask and pre-existing directory permissions. On a permissively configured multi-user host, the resulting key file or its temporary counterpart may be readable by other local users or processes.
The temporary file is predictable (
kms.json.tmp). Although the final rename improves write atomicity, the implementation does not use an exclusive create operation, reject symbolic links, or explicitly secure the temporary file.Attack Path
...[truncated 1230 chars]
- Remediation
View remediation
Remediation Suggestions
- Make encrypted storage mandatory for private keys. Fail closed when no master key or protected keystore is available instead of silently selecting plaintext storage.
- Prefer an operating-system credential store, hardware-backed key store, or dedicated secrets-management service.
- If password-derived encryption is required, derive keys with Argon2id or scrypt using a unique random salt and documented resource parameters. A single SHA-256 operation does not provide password-hardening.
- Create
$HOME/.openclaw/billionswith mode0700and sensitive files with mode0600. - Open temporary files with exclusive creation and no-follow protections, then securely rename them.
- Validate the ownership and permissions of existing directories and files before reading or writing keys.
- Avoid writing decrypted keys to disk during migration.
- Provide a secure migration mechanism that converts existing plaintext entries to encrypted entries and warns users about prior exposure.
