T09 · Insecure Skill Coding Practices
Error
- Location
- src/api.ts:291
- Finding
- Authenticated API requests can be redirected to an arbitrary endpoint<![CDATA[ ## Vulnerability Details **File Location**: `src/api.ts:291`, `src/api.ts:314-319`, and `src/api.ts:410-428` **Bundled Location**: `voiceai-vo.cjs:4333`, `voiceai-vo.cjs:4348-4353`, and `voiceai-vo.cjs:4410-4426` **Vulnerability Type**: Unvalidated service endpoint override causing credential and data disclosure **Risk Level**: High ### Vulnerable Code ```ts constructor(options: { apiKey?: string; mock?: boolean }) { this.apiKey = options.apiKey ?? null; this.mock = options.mock ?? false; this.baseUrl = process.env.VOICEAI_API_BASE ?? BASE_URL; } private endpoint(path: string): string { return `${this.baseUrl}/api/${API_VERSION}${path}`; } ``` The voice-list request forwards the bearer credential to the selected endpoint: ```ts const url = `${this.endpoint('/tts/voices')}?${params.toString()}`; const res = await fetch(url, { headers: { Authorization: `Bearer ${this.apiKey}`, 'User-Agent': 'voiceai-creator-voiceover-pipeline/0.1.0', }, }); ``` The TTS request forwards both the bearer credential and script content: ```ts const res = await fetch(this.endpoint('/tts/speech'), { method: 'POST', headers: { Authorization: `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', 'User-Agent': 'voiceai-creator-voiceover-pipeline/0.1.0', }, body: JSON.stringify(body), }); if (!res.ok) { const errBody = await res.text().catch(() => ''); if (res.status === 401) throw new Error('Voice.ai: Invalid or missing API key (401).'); if (res.status === 402) throw new Error('Voice.ai: Insufficient credits (402). Check your dashboard.'); if (res.status === 429) throw new Error('Voice.ai: Rate limited (429). Wait and retry.'); throw new Error(`Voice.ai TTS error ${res.status}: ${errBody}`); } return Buffer.from(await res.arrayBuffer()); ``` ### Technical Analysis The client uses `VOICEAI_API_BASE` without validating its URL scheme, hostname, port, or origin. All API paths are then constructed from this value, ...[truncated 2420 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove `VOICEAI_API_BASE` from production builds unless custom endpoints are a documented requirement. 2. For normal production use, hard-code or strictly allowlist the expected origin: ```ts const ALLOWED_API_ORIGINS = new Set(['https://dev.voice.ai']); function validateApiBase(raw: string): string { const url = new URL(raw); if (url.protocol !== 'https:') { throw new Error('VOICEAI_API_BASE must use HTTPS.'); } if (!ALLOWED_API_ORIGINS.has(url.origin)) { throw new Error(`Unapproved Voice.ai API origin: ${url.origin}`); } if (url.username || url.password || url.search || url.hash) { throw new Error('VOICEAI_API_BASE must not contain credentials, query parameters, or fragments.'); } return url.origin; } ``` 3. Never forward a production Voice.ai credential to a non-Voice.ai origin. If development endpoints are required, use a separate development credential variable and require an explicit development flag. 4. Display the effective API origin before any authenticated request when an override is active, and require interactive confirmation where practical. 5. Explicitly load `.env` from a trusted Skill directory rather than the caller’s arbitrary working directory, or document and verify the expected configuration path. 6. Add automated tests proving that HTTP URLs, lookalike domains, embedded credentials, unexpected ports, and unapproved hosts are rejected. 7. Apply outbound network policy or egress allowlisting at deployment level as defense in depth. ]]>
