#21050: security(voice-call): path-based stream token for Twilio WebSocket auth
channel: voice-call
size: XS
Cluster:
Voice Call Security Enhancements
## 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
#7704: fix(voice-call): add authentication to WebSocket media stream endpoint
by coygeek · 2026-02-03
88.1%
#12471: fix(voice-call): pass stream auth token via TwiML Parameter for Twi...
by Yida-Dev · 2026-02-09
83.0%
#10238: Security: Fix TwiML injection via unescaped locale/language/voice p...
by StreetJammer · 2026-02-06
77.1%
#18273: fix: extract token from URL query string for Control UI websocket auth
by MisterGuy420 · 2026-02-16
76.6%
#21197: Security/Voice Call: enforce exact webhook path matching
by bmendonca3 · 2026-02-19
76.3%
#20422: Fix/tailscale device pairing
by slagyr · 2026-02-18
74.3%
#21110: fix(tts): deliver audio via structured mediaUrl instead of MEDIA: t...
by hydro13 · 2026-02-19
74.2%
#8922: feat(voice-call): Add ElevenLabs WebSocket streaming TTS
by mikiships · 2026-02-04
74.0%
#19785: fix(gateway): support query parameter tokens for webhooks
by cfdude · 2026-02-18
73.9%
#21532: Security/Voice Call: block signed webhook replay
by bmendonca3 · 2026-02-20
73.0%