T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/shared/storage/keys.js:50
- Finding
- Private keys are stored in plaintext when no master key is configured<![CDATA[ ## Vulnerability Details **File Location**: `scripts/shared/storage/keys.js:50-65` and duplicated implementation at `skills/verified-agent-identity/scripts/shared/storage/keys.js:50-65` **Vulnerability Type**: Plaintext storage of cryptographic private keys **Risk Level**: High ### Vulnerable Code ```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 }, }; } ``` ### Technical Analysis The key-storage implementation treats encryption as optional. If `BILLIONS_NETWORK_MASTER_KMS_KEY` is absent, whitespace-only, or shorter than the accepted minimum, `_encodeEntry` serializes `privateKeyHex` directly into `kms.json`. Although the Skill documentation warns that keys may be stored in plaintext, documentation does not mitigate the exposure. Private keys are identity authentication credentials and should not be written unencrypted to persistent storage by default. This behavior violates secure secret-storage and fail-closed principles. The encryption key is also derived by applying a single SHA-256 operation to the environment variable rather than using a password-based key derivation function. A low-entropy configured value would consequently be more susceptible to offline guessing if an encrypted key file were obtained. ### Attack Path 1. A user creates or imports an identity without setting a valid `BILLIONS_NETWORK_MASTER_KMS_KEY`. 2. The Skill stores the private key under the `provider: "plain"` format in `~/.openclaw/billions/kms.json`. 3. An attacker obtains read access through another local account, malware, an exposed backup, an overly broad support bundle, or accidental file disclosure. 4. The attacker extracts `privateKeyHex`. 5. The attac ...[truncated 666 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Fail closed when no valid encryption key or secure key-store provider is available; do not create or import an identity in plaintext mode. 2. Store private keys in an operating-system keychain, hardware-backed key store, HSM, or dedicated secrets manager where possible. 3. If file encryption must be supported, use a memory-hard password KDF such as Argon2id or scrypt with a unique random salt and documented minimum entropy requirements. 4. Retain authenticated encryption such as AES-256-GCM, but version the complete KDF and cipher parameters in the stored record. 5. Add a migration command that detects `provider: "plain"` and securely rewrites existing entries after encryption is configured. 6. Warn users without printing private-key material, and require explicit migration or key rotation for previously exposed plaintext keys. 7. Apply restrictive filesystem permissions in addition to encryption. ]]>
