T09 · Insecure Skill Coding Practices
Warning
- Location
- src/config.js:74
- Finding
- Plaintext Storage and Exposure of Webhook Credentials<![CDATA[ ## Vulnerability Details **File Location**: `src/config.js:74-79, 171-173` **Vulnerability Type**: Plaintext storage of sensitive credentials **Risk Level**: Medium ### Vulnerable Code ```javascript function saveConfig(config) { try { const dir = path.dirname(CONFIG_PATH); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2)); return true; } catch (e) { console.error('[Config] Save error:', e.message); return false; } } ``` ```javascript if (newConfig.webhook !== undefined) { config.webhook = newConfig.webhook; } ``` The public configuration API also returns the complete configuration object, including the webhook value: ```javascript async function config(newConfig = null) { if (newConfig) { // Support location string for convenience if (typeof newConfig.location === 'string') { const parsed = parseLocation(newConfig.location); if (parsed) { newConfig.location = parsed; } } const config = await setConfig(newConfig); return { success: true, message: '✅ Configuration updated', config }; } return await getConfig(); } ``` ### Technical Analysis Webhook URLs frequently contain bearer-style access tokens or other credentials in their query strings. The implementation copies the supplied webhook URL directly into the configuration and serializes it to `config.json` without encryption, redaction, or an explicitly restrictive file mode. Although `.gitignore` excludes `config.json`, this only reduces accidental version-control commits. It does not protect the credential from other local users, processes, backups, support bundles, filesystem snapshots, or package copies. The `config()` API also returns the stored value without redaction. The project documentation is inconsistent. `SECURITY.md` correctly states that version 1.1.1 stores webhook URLs in plaintext, while `SKILL.md` and `skill.json` ...[truncated 1233 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove the webhook option if it is not used. The current monitoring implementation only constructs a webhook payload and does not transmit it. 2. Prefer an environment variable or platform-managed secret store instead of persistent JSON storage. 3. If file storage is unavoidable: - Store the secret in a separate file. - Create the file with mode `0600`. - Verify ownership and permissions before every read. - Avoid placing the secret inside the distributable project directory. 4. Redact the webhook from all API responses: ```javascript function redactConfig(config) { return { ...config, webhook: config.webhook ? '[REDACTED]' : null }; } ``` 5. Never log the complete webhook URL or include it in thrown errors. 6. Provide a dedicated operation for replacing or deleting the secret rather than returning it through the general configuration API. 7. Correct `SKILL.md` and `skill.json` to state that the webhook is stored in plaintext unless secure storage is actually implemented. 8. Treat any webhook previously stored by the affected version as potentially exposed and rotate its token. ]]>
