T09 · Insecure Skill Coding Practices
Error
- Location
- store.js:15
- Finding
- Mailbox credentials and access tokens are stored in plaintext despite an encryption claim<![CDATA[ ## Vulnerability Details **File Location**: `store.js:15-23`, `store.js:31-42`, `store.js:45-61`; related configuration storage at `config.js:38-43` **Vulnerability Type**: Plaintext storage of sensitive credentials **Risk Level**: High ### Vulnerable Code ```js db.exec(` CREATE TABLE IF NOT EXISTS accounts ( email TEXT PRIMARY KEY, password TEXT, email_type TEXT DEFAULT 'gmail', auth_type TEXT DEFAULT 'password', access_token TEXT, refresh_token TEXT, token_expires INTEGER DEFAULT 0, created_at INTEGER ); `); ``` ```js function addAccount(email, password, emailType) { db.prepare(` INSERT INTO accounts (email, password, email_type, auth_type, created_at) VALUES (?, ?, ?, 'password', ?) ON CONFLICT(email) DO UPDATE SET password = excluded.password, email_type = excluded.email_type, auth_type = 'password', access_token = NULL, refresh_token = NULL, token_expires = 0 `).run(email, password, emailType || 'gmail', Date.now()); } ``` ```js function addOAuthAccount(email, emailType, accessToken, refreshToken, tokenExpires) { db.prepare(` INSERT INTO accounts (email, password, email_type, auth_type, access_token, refresh_token, token_expires, created_at) VALUES (?, '', ?, 'oauth', ?, ?, ?, ?) ON CONFLICT(email) DO UPDATE SET email_type = excluded.email_type, auth_type = 'oauth', access_token = excluded.access_token, refresh_token = excluded.refresh_token, token_expires = excluded.token_expires `).run(email, emailType || 'outlook', accessToken, refreshToken, tokenExpires, Date.now()); } ``` ```js function set(key, value) { const file = loadFileConfig(); file[key] = value; const dir = path.dirname(CONFIG_FILE); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(CONFIG_FILE, JSON.stringify(file, null, 2)); _cache = file; } ``` ### Technical Analysis The SQLite schema stores mailbox ...[truncated 1560 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Store passwords and refresh tokens in an operating-system credential manager or secret-management service. - If local database storage is unavoidable, use authenticated encryption such as AES-GCM with a key stored separately from the database. - Encrypt each sensitive value independently and include account identity as authenticated associated data. - Create secret files with owner-only permissions, such as mode `0600`, and verify directory permissions. - Securely migrate and remove existing plaintext records, including residual SQLite WAL and backup files. - Avoid retaining access tokens when they can be regenerated from a securely protected refresh token. - Correct the documentation so that it accurately describes storage protections. - Add automated tests that verify secrets do not appear as plaintext in database or configuration files. ]]>
