T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/register.mjs:65
- Finding
- Insecure API Credential Storage and Partial Secret Disclosure## Vulnerability Details **File Locations**: - `scripts/register.mjs:30-36` - `scripts/register.mjs:65-74` - `scripts/check.mjs:22-24` **Vulnerability Type**: Insecure storage and disclosure of API credentials **Risk Level**: Medium ### Technical Analysis The registration script stores an API key in `~/.openclaw/credentials/quack.json` without explicitly applying restrictive filesystem permissions: ```js // Save credentials if (data.apiKey || data.agentId) { mkdirSync(CREDS_DIR, { recursive: true }); const creds = { agentId: data.agentId || args.agentId, apiKey: data.apiKey || null, badge: data.badge || null, quckGrant: data.quckGrant || 0, registeredAt: new Date().toISOString(), }; writeFileSync(CREDS_FILE, JSON.stringify(creds, null, 2)); console.log(`Credentials saved to ${CREDS_FILE}`); } ``` Because neither `mkdirSync` nor `writeFileSync` specifies a mode, effective permissions depend on the process umask and existing path permissions. Common defaults may create the directory as `0755` and the file as `0644`, potentially allowing other local users to read the complete API key on a shared system. The registration script also prints the first 12 characters of an existing API key: ```js if (existsSync(CREDS_FILE)) { const existing = JSON.parse(readFileSync(CREDS_FILE, 'utf8')); console.log(`Already registered as ${existing.agentId}`); console.log(`API Key: ${existing.apiKey?.substring(0, 12)}...`); console.log('To re-register, delete ~/.openclaw/credentials/quack.json first.'); return; } ``` The status-checking script repeats this disclosure: ```js if (creds.apiKey) { console.log(` API Key: ${creds.apiKey.substring(0, 12)}...`); } ``` Terminal output may be retained in shell logs, CI logs, agent transcripts, or monitoring systems. Revealing a key prefix is unnecessary for checking registration status and can facilitate credential correlation, identification, or attacks against weak or predictable token ...[truncated 1763 chars]
- Remediation
- ## Remediation Suggestions 1. Create the credentials directory with owner-only permissions: ```js mkdirSync(CREDS_DIR, { recursive: true, mode: 0o700, }); ``` 2. Write the credential file with mode `0600`: ```js writeFileSync( CREDS_FILE, JSON.stringify(creds, null, 2), { encoding: 'utf8', mode: 0o600, }, ); ``` 3. Explicitly tighten permissions on pre-existing paths, because creation modes do not correct permissions on files or directories that already exist: ```js import { chmodSync } from 'fs'; chmodSync(CREDS_DIR, 0o700); chmodSync(CREDS_FILE, 0o600); ``` 4. Avoid following attacker-controlled symbolic links. Validate the destination and use exclusive or atomic file creation where practical, such as writing securely to a same-directory temporary file and renaming it into place. 5. Remove all API-key prefix output. Report only whether a credential exists: ```js console.log(`API Key: ${existing.apiKey ? 'configured' : 'not configured'}`); ``` ```js if (creds.apiKey) { console.log(' API Key: configured'); } ``` 6. Document that the file contains a sensitive bearer credential and should not be copied into source control, logs, diagnostics, backups without access controls, or agent transcripts. 7. Consider using an operating-system credential store instead of a plaintext JSON file when the supported runtime environment provides one.
