T09 · Insecure Skill Coding Practices
- Location
scripts/shared/storage/keys.js:45- Finding
Private keys may be stored in plaintext without restrictive filesystem permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/shared/storage/keys.js:45-61andscripts/shared/storage/base.js:25-31
Vulnerability Type: Plaintext storage of cryptographic private keys and insufficient file-permission enforcement
Risk Level: HighThe same vulnerable implementation is duplicated under
skills/verified-agent-identity/scripts/shared/storage/keys.jsandskills/verified-agent-identity/scripts/shared/storage/base.js.Vulnerable code in
scripts/shared/storage/keys.js: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 }, }; }Related storage code in
scripts/shared/storage/base.js:javascript 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
If
BILLIONS_NETWORK_MASTER_KMS_KEYis absent, invalid, or shorter than the required minimum,_encodeEntrydeliberately writes the rawprivateKeyHexvalue to$HOME/.openclaw/billions/kms.json.FileStoragedoes not explicitly set restrictive permissions on either the containing directory or the temporary and final files. Their effective permissions therefore depend on the process umask and pre-existing filesystem state. In environments with a permissive umask,kms.jsoncan be readable by other local users. An existing directory or file with overly broad permissions is also not corrected.The temporary path is predictable (
kms.json.tmp) and is created without an e ...[truncated 1985 chars]- Remediation
View remediation
Remediation Suggestions
- Make encrypted key storage mandatory. Refuse identity creation or key import when no valid master key or secure OS keystore is available; do not silently fall back to plaintext.
- Prefer an operating-system credential store, hardware-backed keystore, or dedicated KMS rather than an environment-variable-derived file-encryption key.
- If passphrases are supported, replace a single SHA-256 derivation with a password KDF such as Argon2id or scrypt using a unique random salt and suitable cost parameters.
- Create
$HOME/.openclaw/billionswith mode0700and verify that it is owned by the current user and is not a symbolic link. - Create temporary files with mode
0600, an unpredictable name, and exclusive creation. Atomically rename them only after flushing the data. - Explicitly enforce mode
0600onkms.jsonafter creation and migration, regardless of umask. - Reject symbolic links and unsafe pre-existing paths using appropriate
lstat, ownership, and file-type checks. - Warn users and require explicit confirmation before importing a private key through command-line arguments, because command-line values may be exposed through shell history or process inspection.
- Apply the same corrections to the duplicated files under
skills/verified-agent-identity/. - Add automated tests that verify plaintext fallback is impossible and that directory, temporary-file, and final-file permissions are owner-only.
