#12471: fix(voice-call): pass stream auth token via TwiML Parameter for Twilio Media Streams
channel: voice-call
stale
Cluster:
Voice Call Security Enhancements
## Summary
Fixes #12340
Twilio Media Streams **strips query parameters from WebSocket URLs** when connecting to the stream endpoint. This means the auth token embedded in `<Stream url="wss://...?token=abc">` never reaches the server, causing every media stream connection to be rejected with `"invalid token"`.
### Root cause
1. `getStreamUrlForCall()` appends `?token=<random>` to the WebSocket URL
2. `getStreamConnectXml()` places this URL in `<Stream url="...">`
3. **Twilio initiates the WebSocket connection but drops the query string**
4. `getStreamToken()` reads `?token=` from the upgrade request URL -> `undefined`
5. `isValidStreamToken(callSid, undefined)` -> `false` -> stream rejected
### Fix
- **TwiML**: Add `<Parameter name="token" value="...">` inside `<Stream>` -- Twilio forwards `<Parameter>` values in the WebSocket `start` message's `customParameters` object
- **Token resolution**: Fall back to `start.customParameters.token` in `handleStart()` when the URL query token is missing
- **Type**: Add `customParameters?: Record<string, string>` to `TwilioMediaMessage.start`
The URL query token is kept as the primary path for backward compatibility; `customParameters` acts as a fallback.
## Test plan
- [x] New test: `getStreamConnectXml` includes `<Parameter name="token">` tag
- [x] New test: `getStreamConnectXml` omits `<Parameter>` when URL has no token
- [x] New test: streaming TwiML for inbound calls includes `<Parameter>`
- [x] All existing voice-call tests pass (8/8)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR updates the Twilio Media Streams integration to avoid relying on WebSocket URL query parameters for authentication. `TwilioProvider.getStreamConnectXml()` now adds a `<Parameter name="token" ... />` inside the `<Stream>` verb (so Twilio forwards it in the Media Streams `start.customParameters` payload), and `MediaStreamHandler.handleStart()` falls back to `start.customParameters.token` when the upgrade URL token is missing. Tests were added to confirm the token parameter is included/omitted.
<h3>Confidence Score: 4/5</h3>
- This PR is largely safe to merge, but the TwiML generation should be adjusted to avoid emitting whitespace text inside `<Stream>` when no `<Parameter>` is present.
- Core approach (passing token via `<Parameter>` and reading it from `start.customParameters`) is sound and minimally invasive. The main remaining concern is the exact TwiML shape produced by `getStreamConnectXml()` and the tests not strongly validating XML structure, which could cause Twilio to reject the response in some environments.
- extensions/voice-call/src/providers/twilio.ts, extensions/voice-call/src/providers/twilio.test.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21050: security(voice-call): path-based stream token for Twilio WebSocket ...
by richvincent · 2026-02-19
83.0%
#7704: fix(voice-call): add authentication to WebSocket media stream endpoint
by coygeek · 2026-02-03
80.5%
#8297: fix(voice-call): prevent empty TwiML for non-in-progress outbound c...
by vishaltandale00 · 2026-02-03
73.6%
#8922: feat(voice-call): Add ElevenLabs WebSocket streaming TTS
by mikiships · 2026-02-04
72.3%
#19673: fix(telegram): avoid starting streaming replies with only 1-2 words
by emanuelst · 2026-02-18
71.7%
#19648: fix: suppress silent-reply partial tokens during streaming
by bradleypriest · 2026-02-18
71.6%
#21110: fix(tts): deliver audio via structured mediaUrl instead of MEDIA: t...
by hydro13 · 2026-02-19
70.8%
#19785: fix(gateway): support query parameter tokens for webhooks
by cfdude · 2026-02-18
70.4%
#10238: Security: Fix TwiML injection via unescaped locale/language/voice p...
by StreetJammer · 2026-02-06
70.4%
#10231: fix(voice-call): escape locale/language params in TwiML to prevent ...
by coygeek · 2026-02-06
69.9%