T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/config-manager.js:7
- Finding
- Provider API Keys Can Be Persisted in Plaintext Configuration Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/config-manager.js:7-13`, `scripts/init.js:35-37`, and `SKILL.md:32-38` **Vulnerability Type**: Plaintext sensitive credential storage **Risk Level**: High ### Vulnerable Code The configuration manager accepts a literal API key and retains it in the provider configuration: ```javascript function addProvider(config, providerInfo) { const provider = { name: providerInfo.name, api_key: providerInfo.api_key || `\${${providerInfo.name.toUpperCase()}_API_KEY}`, base_url: providerInfo.base_url || '', models: [] }; ``` The generic JSON writer serializes the complete configuration without redaction, encryption, or restrictive permissions: ```javascript function writeJSON(filePath, data) { fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8'); } ``` The Skill documentation explicitly allows collection of either a literal API key or an environment-variable name: ```markdown For each provider, collect: - Provider name - API key (or environment variable name) - Base URL (if custom endpoint) ``` ### Technical Analysis The secure default is an environment-variable or secret-manager reference, but `addProvider` accepts arbitrary `providerInfo.api_key` values. If a caller supplies a real credential, that value is placed directly in `config.providers[].api_key`. The documented workflow subsequently persists provider configuration in `.trae/config/providers.json` through `writeJSON`. Node.js creates the file using the process umask because no explicit mode is supplied. The implementation provides no encryption, credential validation, redaction, permission verification, or prohibition against literal secrets. Consequently, long-lived provider credentials can remain in a predictable local file. The file may also be copied into backups, support bundles, or source-control commits. ### Attack Path 1. A user follows the documented initialization workflow. 2. The user supplies a l ...[truncated 1398 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not accept or persist literal provider credentials. Store only an environment-variable name or secret-manager reference: ```javascript const envName = providerInfo.api_key_env || `${providerInfo.name.toUpperCase()}_API_KEY`; const provider = { name: providerInfo.name, api_key_env: envName, base_url: providerInfo.base_url || '', models: [] }; ``` 2. Resolve credentials only at request time: ```javascript const apiKey = process.env[provider.api_key_env]; if (!apiKey) { throw new Error(`Missing credential environment variable: ${provider.api_key_env}`); } ``` 3. Reject values that appear to be literal API keys rather than approved reference names. 4. Create sensitive configuration files with owner-only permissions: ```javascript fs.writeFileSync( filePath, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 } ); ``` 5. Verify and correct permissions on existing configuration files. Warn the user if a file is group-readable or world-readable. 6. Redact fields named `api_key`, `token`, `secret`, or similar from logging, display, diagnostics, backups, and error messages. 7. Document credential rotation procedures and advise users to revoke any key previously stored in plaintext. ]]>
