T09 · Insecure Skill Coding Practices
- Location
- scripts/auth.js:52
- Finding
- Authentication Relay and Ed25519 Signing Oracle Through Unrestricted API Base<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth.js:52-105`, `scripts/auth.js:255-263` **Vulnerability Type**: Unrestricted authentication endpoint / challenge relay **Risk Level**: High ### Vulnerable Code ```javascript export async function authenticate(botProfileId, privateKeyBase64, apiBase = DEFAULT_API_BASE) { if (!botProfileId || typeof botProfileId !== 'string') { throw new Error('botProfileId is required'); } if (!privateKeyBase64 || typeof privateKeyBase64 !== 'string') { throw new Error('privateKey (base64) is required'); } // Step 1: Request challenge let challengeRes; try { challengeRes = await fetch(`${apiBase}/bots/auth/challenge`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ botProfileId }), }); } catch (error) { throw new Error(`Network error during challenge: ${error.message}`); } // ... const { nonceId, nonce } = challengeData; if (!nonceId || !nonce) { throw new Error('Unexpected challenge response: missing nonceId or nonce'); } // Step 2: Sign nonce const nonceBytes = Buffer.from(nonce, 'base64'); const privateKeyBytes = Buffer.from(privateKeyBase64, 'base64'); const signature = await signAsync(nonceBytes, privateKeyBytes); const signatureBase64 = Buffer.from(signature).toString('base64'); // Step 3: Verify let verifyRes; try { verifyRes = await fetch(`${apiBase}/bots/auth/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ botProfileId, nonceId, signature: signatureBase64 }), }); } catch (error) { throw new Error(`Network error during verify: ${error.message}`); } } ``` ```javascript case '--api-base': result.apiBase = args[++i]; break; ``` ### Technical Analysis The authentication implementation accepts an unrestricted `apiBase` and signs any Base64 value returned as `nonce`. It does not require HTTPS ...[truncated 2616 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove `--api-base` from production-facing authentication commands and use the fixed API endpoint. 2. If alternate endpoints are required for development, parse the URL and enforce: - `https:` only; - an explicit hostname allowlist; - an exact expected path prefix; - no embedded username or password; - no unapproved ports. 3. Require a separate, explicit development-mode flag before permitting non-production endpoints. Display a prominent warning and prohibit use of production credentials in that mode. 4. Add domain separation to the signed payload, for example by signing a canonical structure containing: - protocol identifier; - expected origin; - bot profile ID; - nonce ID; - nonce; - expiration time. 5. Validate the nonce encoding, decoded size, nonce ID format, and challenge expiration before signing. 6. Where supported by the server, bind challenges to a client session or ephemeral key so that a challenge cannot be relayed by another origin. 7. Add tests proving that HTTP URLs, arbitrary hosts, malformed URLs, user-info URLs, and unapproved ports are rejected. 8. Revoke tokens and rotate bot keys if authentication has previously been performed against an untrusted API base. ]]>
