T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/auth.mjs:17
- Finding
- API Credentials Are Stored and Exposed Insecurely<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth.mjs:17-18`, `scripts/auth.mjs:50-74`, `scripts/auth.mjs:90-97`, `scripts/auth.mjs:105-116` **Vulnerability Type**: Plaintext credential storage and credential exposure through stdout and URL query parameters **Risk Level**: High ### Vulnerable Code ```js function saveConfig(config) { writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n"); } ``` ```js if (cmd === "trial") { const existing = getKey(); if (existing) { console.error(`Already have key: ${mask(existing)}`); process.stdout.write(existing); process.exit(0); } const resp = await fetch(`${API_BASE}/temp-token/provision`, { method: "POST", headers: { "Content-Type": "application/json" }, }); const data = await resp.json(); const config = loadConfig(); config.apiKey = data.api_key; saveConfig(config); console.error(`Trial key provisioned ($${data.balance_usd} credit)`); console.error(`Upgrade anytime: node auth.mjs login`); process.stdout.write(data.api_key); } ``` ```js config.apiKey = data.api_key; saveConfig(config); key = data.api_key; const bindUrl = `${WEB_BASE}/login?temp=${encodeURIComponent(key)}`; console.error(`\nOpen this URL to sign in:\n ${bindUrl}\n`); ``` ```js const resp = await fetch(`${API_BASE}/temp-token/poll-bind`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ temp_api_key: key }), }); if (result.status === "bound" && result.permanent_api_key) { const config = loadConfig(); config.apiKey = result.permanent_api_key; saveConfig(config); console.error(`\nAuthentication complete! Key saved to config.json`); process.exit(0); } ``` ### Technical Analysis Trial and permanent API credentials are written directly to `config.json` using `writeFileSync` without an explicit restrictive file mode. The resulting permissions depend on the process umask and surrounding environment, which may permit other lo ...[truncated 1888 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store long-lived credentials in the operating system’s credential manager rather than in the project directory. 2. If file storage is unavoidable, create a dedicated credential file with mode `0o600`, verify its ownership and permissions, and exclude it from source control and backups: ```js writeFileSync(credentialsPath, serializedConfig, { encoding: "utf8", mode: 0o600, flag: "w" }); ``` 3. Never print complete API keys to stdout or stderr. Return only a success indication or a consistently masked identifier. 4. Replace the browser URL’s API key with a short-lived, single-use, narrowly scoped binding code that cannot invoke model APIs. 5. Expire binding codes quickly and invalidate them immediately after successful use. 6. Prefer an `Authorization` header over credentials in JSON bodies where the API supports it, reducing accidental request-body logging. 7. Clearly document where credentials are stored, how they are protected, and how users can revoke them. 8. Ensure logout revokes the server-side credential where supported rather than only overwriting the local value. ]]>
