@@ -164,6 +164,12 @@ def __init__(self, config: TeamsAdapterConfig | None = None) -> None:
164164 "Use app_password (client secret) or federated (workload identity) authentication instead." ,
165165 )
166166
167+ if not self ._app_id :
168+ self ._logger .warn (
169+ "Teams app_id is empty — webhook verification will reject all incoming requests. "
170+ "Set TEAMS_APP_ID or pass app_id in config."
171+ )
172+
167173 self ._bot_user_id : str | None = self ._app_id or None
168174 self ._access_token : str | None = None
169175 self ._token_expiry : float = 0
@@ -207,10 +213,13 @@ async def handle_webhook(
207213 self ._logger .debug ("Teams webhook raw body" , {"body" : body [:500 ] if body else "" })
208214
209215 # ---- JWT verification (Bot Framework tokens) ----
210- if self ._app_id :
211- auth_result = await self ._verify_bot_framework_token (request )
212- if auth_result is not None :
213- return auth_result
216+ if not self ._app_id :
217+ self ._logger .warn ("Rejecting Teams webhook: app_id is not configured, cannot verify JWT" )
218+ return self ._make_response ("Unauthorized – Teams app_id not configured" , 401 )
219+
220+ auth_result = await self ._verify_bot_framework_token (request )
221+ if auth_result is not None :
222+ return auth_result
214223
215224 try :
216225 activity : dict [str , Any ] = json .loads (body )
@@ -259,10 +268,19 @@ async def _cache_user_context(self, activity: dict[str, Any]) -> None:
259268 ttl = CACHE_TTL_MS
260269 state = self ._chat .get_state ()
261270
262- # Cache serviceUrl
271+ # Cache serviceUrl (validate against SSRF allow-list first)
263272 service_url = activity .get ("serviceUrl" )
264273 if service_url and state :
265- await state .set (f"teams:serviceUrl:{ user_id } " , service_url , ttl )
274+ try :
275+ _validate_service_url (service_url )
276+ except ValidationError :
277+ self ._logger .warn (
278+ "Refusing to cache disallowed serviceUrl" ,
279+ {"serviceUrl" : service_url },
280+ )
281+ service_url = None
282+ if service_url :
283+ await state .set (f"teams:serviceUrl:{ user_id } " , service_url , ttl )
266284
267285 # Cache tenantId
268286 channel_data = activity .get ("channelData" , {})
@@ -1112,6 +1130,8 @@ async def open_dm(self, user_id: str) -> str:
11121130 if not service_url :
11131131 service_url = "https://smba.trafficmanager.net/teams/"
11141132
1133+ _validate_service_url (service_url )
1134+
11151135 import aiohttp
11161136
11171137 token = await self ._get_access_token ()
@@ -1693,6 +1713,7 @@ async def _verify_bot_framework_token(self, request: Any) -> Any | None:
16931713 signing_key .key ,
16941714 algorithms = ["RS256" ],
16951715 audience = self ._app_id ,
1716+ issuer = "https://api.botframework.com" ,
16961717 )
16971718 self ._logger .debug (
16981719 "Teams JWT verified" ,
0 commit comments