← Back to PRs

#21050: security(voice-call): path-based stream token for Twilio WebSocket auth

by richvincent open 2026-02-19 16:03 View on GitHub →
channel: voice-call size: XS
## Summary - **Problem**: Twilio strips query parameters from `<Stream>` TwiML WebSocket URLs before connecting. The previous implementation passed `?token=SECRET` in the URL, but Twilio never forwarded it, leaving the `/voice/stream` endpoint effectively unauthenticated. Token validation was commented out as a workaround. - **Impact**: HIGH severity (CVSS 7.5) — unauthenticated WebSocket endpoint allows eavesdropping and audio injection into active voice calls. - **Solution**: Embed the token in the URL path instead of query params (`wss://host/voice/stream/{TOKEN}`). - **What changed**: Token generation, extraction, and URL routing updated across `twilio.ts`, `media-stream.ts`, `webhook.ts`; token validation restored. ## Change Type - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Docs - [x] Security hardening - [ ] Chore/infra ## Scope - [ ] Gateway / orchestration - [ ] Skills / tool execution - [ ] Auth / tokens - [ ] Memory / storage - [x] Integrations - [ ] API / contracts - [ ] UI / DX - [ ] CI/CD / infra ## Security Impact - New permissions/capabilities? No - Secrets/tokens handling changed? Yes — token moved from query param to path segment - New/changed network calls? No - Command/tool execution surface changed? No - Data access scope changed? No - **Mitigation**: Restores authentication to an endpoint that was previously unauthenticated due to Twilio's query param stripping behavior. ## User-visible / Behavior Changes - Voice call WebSocket URLs now use path-based token format - Backward compatible: webhook handler accepts both legacy `/voice/stream` and new `/voice/stream/{TOKEN}` paths during migration - Token validation now correctly rejects unauthorized WebSocket connections ## Verification ### Environment - OS: Linux - Runtime: Node 22+ - Provider: Twilio voice integration - Test suite: `pnpm vitest run extensions/voice-call/src/providers/twilio.test.ts` ### Steps 1. Trigger Twilio voice call via API 2. Observe TwiML `<Stream>` element in webhook response 3. Verify URL format is `wss://host/voice/stream/{TOKEN}` (not `?token=...`) 4. Attempt WebSocket connection with invalid token → expect rejection ### Expected - Token validation correctly rejects unauthorized connections - Valid tokens allow WebSocket upgrade ### Actual - Before: Token validation disabled (commented out), endpoint unauthenticated - After: Token validation works correctly with path-based format ## Evidence - [x] Tests updated and passing (`twilio.test.ts` expects path-based format) - [x] Code review verified path extraction logic ## Compatibility / Migration - Backward compatible? Yes — webhook accepts both path formats during migration - Config/env changes? No - Migration needed? No — auto-detects token from path ## Risks and Mitigations - **Risk**: Existing voice calls in progress at deploy time may have old query-param URLs - **Mitigation**: Webhook handler accepts both formats; token extraction tries path first, falls back to query param - **Risk**: Breaking change if downstream systems expect query param format - **Mitigation**: Twilio never forwarded query params, so no downstream system could have relied on them ## Related - Twilio TwiML `<Stream>` documentation confirms query parameters are not forwarded to WebSocket connections - Identified in penetration testing assessment (VD-6) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> <!-- greptile_comment --> <h3>Greptile Summary</h3> Fixes critical authentication bypass in Twilio voice call WebSocket endpoint by moving authentication token from query parameter to URL path. **Key Changes:** - Token now embedded in path (`/voice/stream/{token}`) instead of query param - Twilio strips query params before WebSocket connection, previous implementation left endpoint unauthenticated - Token validation restored and working correctly with timing-safe comparison - Tests updated to verify path-based token format **Security Impact:** - **Before**: WebSocket endpoint was effectively unauthenticated (token validation commented out as workaround) - **After**: Proper authentication enforced - unauthorized connections rejected **Implementation Quality:** - Secure token generation using `crypto.randomBytes(16).toString("base64url")` - Timing-safe token comparison in `isValidStreamToken()` prevents timing attacks - Proper token lifecycle management (cleanup on call end/hangup) - Graceful path matching allows upgrade handler to accept both formats without crashing <h3>Confidence Score: 5/5</h3> - This PR is safe to merge - it fixes a critical security vulnerability with a clean, well-tested implementation. - Score reflects the security-critical nature of the fix combined with high implementation quality: proper cryptographic practices (timing-safe comparison, secure random token generation), clean code changes with clear comments, updated tests, and thorough PR documentation. The fix directly addresses a HIGH severity vulnerability (unauthenticated WebSocket endpoint) that was identified in penetration testing. - No files require special attention - all changes are straightforward and well-implemented. <sub>Last reviewed commit: 7508a3d</sub> <!-- greptile_other_comments_section --> <sub>(5/5) You can turn off certain types of comments like style [here](https://app.greptile.com/review/github)!</sub> <!-- /greptile_comment -->

Most Similar PRs