T09 · Insecure Skill Coding Practices
- Location
scripts/shared/storage/keys.js:45- Finding
Private Keys Stored Unencrypted by Default Without Explicit Restrictive Permissions
- Content
View full analysis
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: HighVulnerable Code
scripts/shared/storage/keys.js:45-57js _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-32js 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_KEYis absent or rejected,_encodeEntryintentionally stores the raw identity private key inkms.jsonusing theplainprovider. 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
View remediation
Remediation Suggestions
- 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.
- Prefer an operating-system credential store, hardware-backed keystore, or dedicated secret-management service over a JSON file.
- Create
$HOME/.openclaw/billionswith mode0700. - Create
kms.jsonand its temporary file with mode0600, using exclusive creation where appropriate. - Before reading or writing, verify that the directory and file are owned by the expected user and are not symbolic links.
- Use a securely generated temporary filename in the same protected directory and ensure cleanup after failed writes.
- Detect existing plaintext entries and migrate them to encrypted storage after explicit user confirmation.
- 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.
- Document key rotation and recovery procedures for identities previously stored in plaintext.
