@microsoft/agents-hosting 1.7.2 → 1.8.0-beta.36.gb033f0abc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +64 -2
  2. package/dist/package.json +3 -3
  3. package/dist/src/agent-client/agentClient.d.ts +18 -6
  4. package/dist/src/agent-client/agentClient.js +34 -18
  5. package/dist/src/agent-client/agentClient.js.map +1 -1
  6. package/dist/src/agent-client/agentResponseHandler.d.ts +14 -9
  7. package/dist/src/agent-client/agentResponseHandler.js +10 -5
  8. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  9. package/dist/src/agent-client/createAgentResponseHandler.d.ts +7 -2
  10. package/dist/src/agent-client/createAgentResponseHandler.js +55 -8
  11. package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -1
  12. package/dist/src/app/adaptiveCards/activityValueParsers.d.ts +1 -4
  13. package/dist/src/app/adaptiveCards/activityValueParsers.js +2 -5
  14. package/dist/src/app/adaptiveCards/activityValueParsers.js.map +1 -1
  15. package/dist/src/app/adaptiveCards/adaptiveCardsActions.d.ts +18 -0
  16. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +53 -23
  17. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
  18. package/dist/src/app/agentApplication.d.ts +3 -0
  19. package/dist/src/app/agentApplication.js +140 -81
  20. package/dist/src/app/agentApplication.js.map +1 -1
  21. package/dist/src/app/attachmentDownloader.d.ts +4 -1
  22. package/dist/src/app/attachmentDownloader.js +27 -5
  23. package/dist/src/app/attachmentDownloader.js.map +1 -1
  24. package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +1 -1
  25. package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +1 -1
  26. package/dist/src/app/auth/types.d.ts +3 -4
  27. package/dist/src/app/index.d.ts +2 -0
  28. package/dist/src/app/index.js.map +1 -1
  29. package/dist/src/app/streaming/streamingResponse.d.ts +56 -0
  30. package/dist/src/app/streaming/streamingResponse.js +250 -26
  31. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  32. package/dist/src/app/teamsAttachmentDownloader.d.ts +3 -1
  33. package/dist/src/app/teamsAttachmentDownloader.js +12 -1
  34. package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
  35. package/dist/src/auth/authConfiguration.js +12 -0
  36. package/dist/src/auth/authConfiguration.js.map +1 -1
  37. package/dist/src/auth/index.d.ts +2 -1
  38. package/dist/src/auth/index.js +2 -1
  39. package/dist/src/auth/index.js.map +1 -1
  40. package/dist/src/auth/jwt-middleware.d.ts +6 -0
  41. package/dist/src/auth/jwt-middleware.js +263 -13
  42. package/dist/src/auth/jwt-middleware.js.map +1 -1
  43. package/dist/src/auth/msal/msalConnectionSettings.d.ts +8 -0
  44. package/dist/src/auth/msal/msalHttpRetryHandlerHelper.d.ts +34 -0
  45. package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js +126 -0
  46. package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js.map +1 -0
  47. package/dist/src/auth/msal/msalTokenProvider.d.ts +1 -0
  48. package/dist/src/auth/msal/msalTokenProvider.js +23 -14
  49. package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
  50. package/dist/src/auth/settings.d.ts +18 -0
  51. package/dist/src/auth/settings.js +8 -2
  52. package/dist/src/auth/settings.js.map +1 -1
  53. package/dist/src/cloudAdapter.d.ts +29 -2
  54. package/dist/src/cloudAdapter.js +44 -2
  55. package/dist/src/cloudAdapter.js.map +1 -1
  56. package/dist/src/errorHelper.js +137 -101
  57. package/dist/src/errorHelper.js.map +1 -1
  58. package/dist/src/index.d.ts +1 -0
  59. package/dist/src/index.js +1 -0
  60. package/dist/src/index.js.map +1 -1
  61. package/dist/src/invoke/adaptiveCardInvokeValue.d.ts +5 -0
  62. package/dist/src/oauth/userTokenClient.d.ts +0 -1
  63. package/dist/src/oauth/userTokenClient.js +0 -1
  64. package/dist/src/oauth/userTokenClient.js.map +1 -1
  65. package/dist/src/observability/traces.d.ts +6 -0
  66. package/dist/src/observability/traces.js +20 -2
  67. package/dist/src/observability/traces.js.map +1 -1
  68. package/dist/src/outboundHostValidator.d.ts +47 -0
  69. package/dist/src/outboundHostValidator.js +149 -0
  70. package/dist/src/outboundHostValidator.js.map +1 -0
  71. package/dist/src/state/agentState.d.ts +5 -0
  72. package/dist/src/state/agentState.js +16 -3
  73. package/dist/src/state/agentState.js.map +1 -1
  74. package/package.json +3 -3
  75. package/src/agent-client/agentClient.ts +46 -23
  76. package/src/agent-client/agentResponseHandler.ts +14 -9
  77. package/src/agent-client/createAgentResponseHandler.ts +70 -9
  78. package/src/app/adaptiveCards/activityValueParsers.ts +3 -9
  79. package/src/app/adaptiveCards/adaptiveCardsActions.ts +52 -24
  80. package/src/app/agentApplication.ts +149 -77
  81. package/src/app/attachmentDownloader.ts +25 -5
  82. package/src/app/auth/handlers/agenticAuthorization.ts +1 -1
  83. package/src/app/auth/handlers/azureBotAuthorization.ts +1 -1
  84. package/src/app/auth/types.ts +3 -3
  85. package/src/app/index.ts +8 -0
  86. package/src/app/streaming/streamingResponse.ts +276 -22
  87. package/src/app/teamsAttachmentDownloader.ts +11 -1
  88. package/src/auth/authConfiguration.ts +12 -0
  89. package/src/auth/index.ts +2 -1
  90. package/src/auth/jwt-middleware.ts +298 -14
  91. package/src/auth/msal/msalConnectionSettings.ts +10 -0
  92. package/src/auth/msal/msalHttpRetryHandlerHelper.ts +161 -0
  93. package/src/auth/msal/msalTokenProvider.ts +32 -14
  94. package/src/auth/settings.ts +18 -3
  95. package/src/cloudAdapter.ts +56 -3
  96. package/src/errorHelper.ts +155 -114
  97. package/src/index.ts +1 -0
  98. package/src/invoke/adaptiveCardInvokeValue.ts +5 -0
  99. package/src/oauth/userTokenClient.ts +0 -1
  100. package/src/observability/traces.ts +19 -1
  101. package/src/outboundHostValidator.ts +172 -0
  102. package/src/state/agentState.ts +24 -6
@@ -15,6 +15,7 @@ import crypto from 'crypto'
15
15
  import { AuthenticationTraceDefinitions } from '../../observability'
16
16
  import { ExceptionHelper } from '@microsoft/agents-activity'
17
17
  import { Errors } from '../../errorHelper'
18
+ import { MsalHttpRetryHandlerHelper, normalizeMsalRetryCount } from './msalHttpRetryHandlerHelper'
18
19
 
19
20
  const audience = 'api://AzureADTokenExchange'
20
21
  const logger = debug('agents:msal')
@@ -203,12 +204,14 @@ export class MsalTokenProvider implements AuthProvider {
203
204
  }
204
205
 
205
206
  private getClientSecretClient (authConfig: AuthConfiguration): ConfidentialClientApplication {
207
+ const retryCount = normalizeMsalRetryCount(authConfig.msalRetryCount)
206
208
  const cacheKey = MsalTokenProvider.cacheKey(
207
209
  'confidential-client',
208
210
  AuthType.ClientSecret,
209
211
  authConfig.clientId,
210
212
  resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
211
- MsalTokenProvider.digest(authConfig.clientSecret)
213
+ MsalTokenProvider.digest(authConfig.clientSecret),
214
+ retryCount
212
215
  )
213
216
 
214
217
  return this.getOrCreateConfidentialClient(cacheKey, () => new ConfidentialClientApplication({
@@ -217,11 +220,12 @@ export class MsalTokenProvider implements AuthProvider {
217
220
  authority: resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
218
221
  clientSecret: authConfig.clientSecret
219
222
  },
220
- system: this.sysOptions
223
+ system: this.getSystemOptions(authConfig)
221
224
  }))
222
225
  }
223
226
 
224
227
  private getCertificateClient (authConfig: AuthConfiguration): ConfidentialClientApplication {
228
+ const retryCount = normalizeMsalRetryCount(authConfig.msalRetryCount)
225
229
  const cacheKey = MsalTokenProvider.cacheKey(
226
230
  'confidential-client',
227
231
  authConfig.authType ?? AuthType.Certificate,
@@ -229,7 +233,8 @@ export class MsalTokenProvider implements AuthProvider {
229
233
  resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
230
234
  this.getFileCacheIdentity(authConfig.certPemFile),
231
235
  this.getFileCacheIdentity(authConfig.certKeyFile),
232
- authConfig.sendX5C
236
+ authConfig.sendX5C,
237
+ retryCount
233
238
  )
234
239
 
235
240
  return this.getOrCreateConfidentialClient(cacheKey, () => {
@@ -258,7 +263,7 @@ export class MsalTokenProvider implements AuthProvider {
258
263
  x5c: pemFile.toString()
259
264
  }
260
265
  },
261
- system: this.sysOptions
266
+ system: this.getSystemOptions(authConfig)
262
267
  })
263
268
  })
264
269
  }
@@ -434,7 +439,7 @@ export class MsalTokenProvider implements AuthProvider {
434
439
  clientAssertion: appToken,
435
440
  authority: this.resolveAuthority(tenantId),
436
441
  },
437
- system: this.sysOptions
442
+ system: this.getSystemOptions(this.connectionSettings)
438
443
  })
439
444
 
440
445
  const token = await cca.acquireTokenByClientCredential({
@@ -613,7 +618,7 @@ export class MsalTokenProvider implements AuthProvider {
613
618
  managedIdentityIdParams: {
614
619
  userAssignedClientId: this.connectionSettings.clientId
615
620
  },
616
- system: this.sysOptions
621
+ system: this.getSystemOptions(this.connectionSettings)
617
622
  })
618
623
  const tokenResult = await msiApp.acquireToken({ resource })
619
624
  if (!tokenResult?.accessToken) {
@@ -638,7 +643,10 @@ export class MsalTokenProvider implements AuthProvider {
638
643
  if (!this.connectionSettings.federatedClientId && !this.connectionSettings.FICClientId) {
639
644
  throw ExceptionHelper.generateException(Error, Errors.FICClientIdRequired)
640
645
  }
641
- clientAssertion = await this.fetchExternalToken(this.connectionSettings.federatedClientId as string || this.connectionSettings.FICClientId as string)
646
+ clientAssertion = await this.fetchExternalToken(
647
+ this.connectionSettings.federatedClientId as string || this.connectionSettings.FICClientId as string,
648
+ this.connectionSettings
649
+ )
642
650
  break
643
651
  case AuthType.Certificate:
644
652
  case AuthType.CertificateSubjectName:
@@ -688,6 +696,13 @@ export class MsalTokenProvider implements AuthProvider {
688
696
  }
689
697
  }
690
698
 
699
+ private getSystemOptions (authConfig?: AuthConfiguration): NodeSystemOptions {
700
+ return {
701
+ ...this.sysOptions,
702
+ networkClient: new MsalHttpRetryHandlerHelper(undefined, normalizeMsalRetryCount(authConfig?.msalRetryCount))
703
+ }
704
+ }
705
+
691
706
  /**
692
707
  * Generates the client assertion using the provided certificate.
693
708
  * @param authConfig The authentication configuration.
@@ -742,7 +757,7 @@ export class MsalTokenProvider implements AuthProvider {
742
757
  managedIdentityIdParams: {
743
758
  userAssignedClientId: authConfig.clientId || ''
744
759
  },
745
- system: this.sysOptions
760
+ system: this.getSystemOptions(authConfig)
746
761
  })
747
762
  const token = await mia.acquireToken({
748
763
  resource: scope
@@ -758,7 +773,7 @@ export class MsalTokenProvider implements AuthProvider {
758
773
  */
759
774
  private async acquireTokenWithSystemAssignedIdentity (authConfig: AuthConfiguration, scope: string) {
760
775
  const mia = new ManagedIdentityApplication({
761
- system: this.sysOptions
776
+ system: this.getSystemOptions(authConfig)
762
777
  })
763
778
  const token = await mia.acquireToken({
764
779
  resource: scope
@@ -812,14 +827,17 @@ export class MsalTokenProvider implements AuthProvider {
812
827
  */
813
828
  private async acquireAccessTokenViaFIC (authConfig: AuthConfiguration, scope: string) : Promise<string> {
814
829
  const scopes = [`${scope}/.default`]
815
- const clientAssertion = await this.fetchExternalToken(authConfig.federatedClientId as string || authConfig.FICClientId as string)
830
+ const clientAssertion = await this.fetchExternalToken(
831
+ authConfig.federatedClientId as string || authConfig.FICClientId as string,
832
+ authConfig
833
+ )
816
834
  const cca = new ConfidentialClientApplication({
817
835
  auth: {
818
836
  clientId: authConfig.clientId as string,
819
837
  authority: `${authConfig.authorityEndpoint ?? authConfig.authority}/${authConfig.tenantId}`,
820
838
  clientAssertion
821
839
  },
822
- system: this.sysOptions
840
+ system: this.getSystemOptions(authConfig)
823
841
  })
824
842
  const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
825
843
  logger.debug('got token using FIC client assertion')
@@ -845,7 +863,7 @@ export class MsalTokenProvider implements AuthProvider {
845
863
  authority: `https://login.microsoftonline.com/${authConfig.tenantId}`,
846
864
  clientAssertion
847
865
  },
848
- system: this.sysOptions
866
+ system: this.getSystemOptions(authConfig)
849
867
  })
850
868
  const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
851
869
  logger.debug('got token using WID client assertion')
@@ -860,12 +878,12 @@ export class MsalTokenProvider implements AuthProvider {
860
878
  * @param FICClientId The FIC client ID.
861
879
  * @returns A promise that resolves to the external token.
862
880
  */
863
- private async fetchExternalToken (FICClientId: string) : Promise<string> {
881
+ private async fetchExternalToken (FICClientId: string, authConfig?: AuthConfiguration) : Promise<string> {
864
882
  const managedIdentityClientAssertion = new ManagedIdentityApplication({
865
883
  managedIdentityIdParams: {
866
884
  userAssignedClientId: FICClientId
867
885
  },
868
- system: this.sysOptions
886
+ system: this.getSystemOptions(authConfig ?? this.connectionSettings)
869
887
  }
870
888
  )
871
889
  const response = await managedIdentityClientAssertion.acquireToken({
@@ -170,6 +170,12 @@ export function resolveAuthority (authority?: string, tenantId?: string): string
170
170
  return `${base}/${tenantId ?? 'botframework.com'}`
171
171
  }
172
172
 
173
+ function getEffectiveTenant (tenantId: string | undefined, authority: string): string | undefined {
174
+ const resolvedAuthority = new URL(trimTrailingSlashes(authority))
175
+ const embeddedTenant = resolvedAuthority.pathname.split('/').filter(Boolean).pop()
176
+ return embeddedTenant ?? tenantId
177
+ }
178
+
173
179
  function trimTrailingSlashes (value: string): string {
174
180
  let end = value.length
175
181
  while (end > 0 && value.charCodeAt(end - 1) === 47) {
@@ -179,14 +185,14 @@ function trimTrailingSlashes (value: string): string {
179
185
  return end === value.length ? value : value.slice(0, end)
180
186
  }
181
187
 
182
- function getDefaultIssuers (tenantId: string, authority: string) : string[] {
188
+ export function getDefaultIssuers (tenantId: string, authority: string) : string[] {
183
189
  // Convert empty string to undefined so resolveAuthority applies its 'botframework.com' default
184
- const t = tenantId || undefined
190
+ const t = getEffectiveTenant(tenantId || undefined, authority)
185
191
  if (!t) {
186
192
  logger.warn('tenantId is not configured, defaulting to botframework.com')
187
193
  }
188
194
  return [
189
- 'https://api.botframework.com',
195
+ /login\.microsoftonline\.us/i.test(authority) ? 'https://api.botframework.us' : 'https://api.botframework.com',
190
196
  `${resolveAuthority('https://sts.windows.net', t)}/`,
191
197
  `${resolveAuthority(authority, t)}/v2.0`
192
198
  ]
@@ -223,6 +229,15 @@ export interface ConnectionSettingsBase {
223
229
  */
224
230
  issuers?: string[]
225
231
 
232
+ /**
233
+ * Whether to validate the token issuer against {@link issuers}.
234
+ *
235
+ * @remarks
236
+ * Disabled by default for backward compatibility. Tenant-to-issuer binding is always applied
237
+ * independently when both claims contain comparable tenant GUIDs.
238
+ */
239
+ validateIssuer?: boolean
240
+
226
241
  /**
227
242
  * The connection name for the authentication configuration.
228
243
  */
@@ -7,7 +7,7 @@ import { AgentHandler, INVOKE_RESPONSE_KEY } from './activityHandler'
7
7
  import { BaseAdapter } from './baseAdapter'
8
8
  import { TurnContext } from './turnContext'
9
9
  import { Request } from './auth/request'
10
- import { WebResponse } from './interfaces/webResponse'
10
+ import { NextFunction, WebResponse } from './interfaces/webResponse'
11
11
  import { ConnectorClient } from './connector-client/connectorClient'
12
12
  import { AuthConfiguration, getAuthConfigWithDefaults } from './auth/authConfiguration'
13
13
  import { AuthProvider } from './auth/authProvider'
@@ -32,6 +32,8 @@ import { parseBooleanEnv, suggestClosest } from './utils/env'
32
32
  import { trace } from '@microsoft/agents-telemetry'
33
33
  import { AdapterTraceDefinitions } from './observability'
34
34
  import { applyAgenticHeaders } from './getProductInfo'
35
+ import { createOutboundHostValidator, OutboundUrlPolicy } from './outboundHostValidator'
36
+ import { authorizeJWT } from './auth/jwt-middleware'
35
37
 
36
38
  const logger = debug('agents:cloud-adapter')
37
39
 
@@ -80,6 +82,9 @@ export interface CloudAdapterOptions {
80
82
  * for one service URL tries to send activities targeting a different one.
81
83
  *
82
84
  * Env var: `CloudAdapterOptions__validateServiceUrl`.
85
+ *
86
+ * @deprecated Configure {@link OutboundUrlPolicy} instead. This option
87
+ * remains available for backward compatibility with claim-only validation.
83
88
  */
84
89
  validateServiceUrl?: boolean
85
90
  }
@@ -238,6 +243,7 @@ function truncateActivityForLog (activity: unknown, max = 1024): string {
238
243
 
239
244
  export class CloudAdapter extends BaseAdapter {
240
245
  protected readonly authConfig: AuthConfiguration
246
+ private readonly jwtMiddleware: ReturnType<typeof authorizeJWT>
241
247
  protected _agentName?: string
242
248
 
243
249
  /**
@@ -246,6 +252,7 @@ export class CloudAdapter extends BaseAdapter {
246
252
  connectionManager: Connections
247
253
 
248
254
  private readonly _options: Required<CloudAdapterOptions>
255
+ private readonly _hostValidator: OutboundUrlPolicy
249
256
 
250
257
  /**
251
258
  * Creates an instance of CloudAdapter.
@@ -253,12 +260,17 @@ export class CloudAdapter extends BaseAdapter {
253
260
  * @param authProvider - No longer used.
254
261
  * @param userTokenClient - No longer used.
255
262
  * @param options - Optional runtime behavior overrides. See {@link CloudAdapterOptions}.
263
+ * @param outboundHostValidator - Optional policy for validating outbound `Activity.serviceUrl` hosts.
264
+ * When omitted, the policy is loaded from `OutboundHostValidator__*` environment variables
265
+ * and remains disabled by default.
256
266
  */
257
- constructor (authConfig?: AuthConfiguration, authProvider?: AuthProvider, userTokenClient?: UserTokenClient, options?: CloudAdapterOptions) {
267
+ constructor (authConfig?: AuthConfiguration, authProvider?: AuthProvider, userTokenClient?: UserTokenClient, options?: CloudAdapterOptions, outboundHostValidator?: OutboundUrlPolicy) {
258
268
  super()
259
269
  this.authConfig = authConfig = getAuthConfigWithDefaults(authConfig)
270
+ this.jwtMiddleware = authorizeJWT(authConfig)
260
271
  this.connectionManager = new MsalConnectionManager(undefined, undefined, authConfig)
261
272
  this._options = resolveCloudAdapterOptions(options)
273
+ this._hostValidator = outboundHostValidator ?? createOutboundHostValidator()
262
274
 
263
275
  // Install a CloudAdapter-aware default `onTurnError` that honors
264
276
  // `emitStackTrace`. The base class default only logs the message; we
@@ -321,6 +333,8 @@ export class CloudAdapter extends BaseAdapter {
321
333
  identity: JwtPayload,
322
334
  headers?: HeaderPropagationCollection
323
335
  ): Promise<ConnectorClient> {
336
+ this.ensureOutboundServiceUrlAllowed(serviceUrl)
337
+
324
338
  return trace(AdapterTraceDefinitions.createConnectorClient, async ({ record }) => {
325
339
  record({ serviceUrl, scopes: [scope] })
326
340
 
@@ -349,6 +363,8 @@ export class CloudAdapter extends BaseAdapter {
349
363
  identity: JwtPayload,
350
364
  activity: Activity,
351
365
  headers?: HeaderPropagationCollection) {
366
+ this.ensureOutboundServiceUrlAllowed(activity.serviceUrl)
367
+
352
368
  return trace(AdapterTraceDefinitions.createConnectorClient, async ({ record }) => {
353
369
  if (!identity?.aud) {
354
370
  // anonymous
@@ -425,6 +441,24 @@ export class CloudAdapter extends BaseAdapter {
425
441
  } as JwtPayload
426
442
  }
427
443
 
444
+ /**
445
+ * Authorizes an incoming web request using this adapter's resolved authentication configuration.
446
+ * @param req The incoming request.
447
+ * @param res The outgoing response.
448
+ * @param next Callback invoked after successful authorization.
449
+ */
450
+ public async authorizeRequest (req: Request, res: WebResponse, next: NextFunction): Promise<void> {
451
+ await this.jwtMiddleware(req, res, next)
452
+ }
453
+
454
+ /**
455
+ * Gets the client ID used by this adapter's default authentication connection.
456
+ * @returns The configured client ID, or `undefined` for anonymous development configuration.
457
+ */
458
+ public getClientId (): string | undefined {
459
+ return this.authConfig.clientId
460
+ }
461
+
428
462
  /**
429
463
  * Sets the agent name for M365 agent header propagation.
430
464
  * @param agentName The human-friendly agent name to set for header propagation.
@@ -692,6 +726,8 @@ export class CloudAdapter extends BaseAdapter {
692
726
  * should be rejected with a 400.
693
727
  */
694
728
  private validateServiceUrl (identity: JwtPayload | undefined, activity: Activity): boolean {
729
+ if (!this.isOutboundServiceUrlAllowed(activity.serviceUrl)) return false
730
+
695
731
  if (!identity) return true
696
732
  if (!activity.serviceUrl) return true
697
733
 
@@ -709,7 +745,7 @@ export class CloudAdapter extends BaseAdapter {
709
745
 
710
746
  const safeClaim = sanitizeForLog(claimValue)
711
747
  const safeServiceUrl = sanitizeForLog(activity.serviceUrl)
712
- if (this._options.validateServiceUrl) {
748
+ if (this._hostValidator.enabled || this._options.validateServiceUrl) {
713
749
  logger.error(`Invalid service URL Claim='${safeClaim}', ServiceUrl='${safeServiceUrl}'`)
714
750
  return false
715
751
  }
@@ -717,6 +753,23 @@ export class CloudAdapter extends BaseAdapter {
717
753
  return true
718
754
  }
719
755
 
756
+ /**
757
+ * Applies the shared outbound host policy.
758
+ */
759
+ private isOutboundServiceUrlAllowed (serviceUrl: string | undefined): boolean {
760
+ if (!this._hostValidator.enabled || !serviceUrl || this._hostValidator.isAllowed(serviceUrl)) return true
761
+
762
+ logger.warn(`ServiceUrl host is not in the configured allowed hosts. ServiceUrl='${sanitizeForLog(serviceUrl)}'`)
763
+ return false
764
+ }
765
+
766
+ /** Ensures connector-client creation cannot bypass the outbound host policy. */
767
+ private ensureOutboundServiceUrlAllowed (serviceUrl: string | undefined): void {
768
+ if (!this.isOutboundServiceUrlAllowed(serviceUrl)) {
769
+ throw ExceptionHelper.generateException(Error, Errors.OutboundServiceUrlNotAllowed)
770
+ }
771
+ }
772
+
720
773
  /**
721
774
  * Updates an activity.
722
775
  * @param context - The TurnContext for the current turn.
@@ -156,6 +156,14 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
156
156
  description: 'serviceUrl must be a non-empty string'
157
157
  },
158
158
 
159
+ /**
160
+ * Error thrown when the outbound service URL is denied by the configured host policy.
161
+ */
162
+ OutboundServiceUrlNotAllowed: {
163
+ code: -120175,
164
+ description: 'serviceUrl host is not in the configured allowed hosts.'
165
+ },
166
+
159
167
  /**
160
168
  * Error thrown when conversationParameters must be defined.
161
169
  */
@@ -416,6 +424,22 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
416
424
  description: 'Identity is required to get the token provider.'
417
425
  },
418
426
 
427
+ /**
428
+ * Error thrown when an MSAL HTTP request cannot be completed.
429
+ */
430
+ MsalHttpRequestFailed: {
431
+ code: -120391,
432
+ description: 'MSAL HTTP request failed: {message}'
433
+ },
434
+
435
+ /**
436
+ * Error thrown when an MSAL HTTP response body cannot be parsed as JSON.
437
+ */
438
+ MsalResponseUnparsable: {
439
+ code: -120392,
440
+ description: 'MSAL returned an unparsable response body'
441
+ },
442
+
419
443
  FailedToAcquireTokenUsingCertificate: {
420
444
  code: -120395,
421
445
  description: 'Failed to acquire token using certificate'
@@ -673,108 +697,6 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
673
697
  description: 'Token request timed out after {timeoutMs} ms'
674
698
  },
675
699
 
676
- // ============================================================================
677
- // Entra Agent ID Sidecar Authentication Errors (-120800 to -120849)
678
- // ============================================================================
679
-
680
- /**
681
- * Error thrown when the Entra sidecar auth provider is used without connection settings.
682
- */
683
- SidecarConnectionSettingsRequired: {
684
- code: -120800,
685
- description: 'Connection settings must be provided when using the Entra sidecar auth provider'
686
- },
687
-
688
- /**
689
- * Error thrown when the resolved sidecar base URL is not a valid absolute URL.
690
- */
691
- SidecarBaseUrlInvalid: {
692
- code: -120801,
693
- description: 'The resolved sidecar base URL `{url}` is not a valid absolute URL'
694
- },
695
-
696
- /**
697
- * Error thrown when the resolved sidecar base URL does not use the http or https scheme.
698
- */
699
- SidecarBaseUrlInsecureScheme: {
700
- code: -120802,
701
- description: 'The resolved sidecar base URL `{url}` must use the http or https scheme'
702
- },
703
-
704
- /**
705
- * Error thrown when the resolved sidecar base URL contains userinfo (credentials).
706
- */
707
- SidecarBaseUrlUserInfo: {
708
- code: -120803,
709
- description: 'The resolved sidecar base URL `{url}` must not contain userinfo (credentials)'
710
- },
711
-
712
- /**
713
- * Error thrown when the resolved sidecar base URL points to a non-loopback, non-private address.
714
- */
715
- SidecarBaseUrlNotLocal: {
716
- code: -120804,
717
- description: 'The resolved sidecar base URL `{url}` points to a non-loopback, non-private address. The Entra Agent ID sidecar (agent container) must be reachable only from within the agent\'s network boundary. Set `bypassLocalNetworkRestriction` to true in the connection configuration to override this safety check (UNSAFE: only for carefully validated private-network configurations)'
718
- },
719
-
720
- /**
721
- * Error thrown when both AgentUsername and AgentUserId are supplied on a sidecar request (mutually exclusive).
722
- */
723
- SidecarUserIdentityMutuallyExclusive: {
724
- code: -120805,
725
- description: 'AgentUsername and AgentUserId are mutually exclusive; set only one on the sidecar request options'
726
- },
727
-
728
- /**
729
- * Error thrown when a sidecar request fails after exhausting all retry attempts.
730
- */
731
- SidecarRequestFailed: {
732
- code: -120806,
733
- description: 'Sidecar request failed after {attempts} attempt(s): {message}'
734
- },
735
-
736
- /**
737
- * Error thrown when the sidecar responds with a non-success status code.
738
- */
739
- SidecarErrorResponse: {
740
- code: -120807,
741
- description: 'Sidecar returned error status {status}: {message}'
742
- },
743
-
744
- /**
745
- * Error thrown when the sidecar response is missing the authorizationHeader field.
746
- */
747
- SidecarResponseMissingAuthorizationHeader: {
748
- code: -120808,
749
- description: 'Sidecar response missing `authorizationHeader` field'
750
- },
751
-
752
- /**
753
- * Error thrown when the sidecar response body cannot be parsed.
754
- */
755
- SidecarResponseUnparsable: {
756
- code: -120809,
757
- description: 'Sidecar returned an unparsable response body'
758
- },
759
-
760
- /**
761
- * Error thrown when acquireTokenOnBehalfOf is called on the sidecar provider, which does not support OBO in Phase 1.
762
- */
763
- OnBehalfOfNotSupportedBySidecar: {
764
- code: -120810,
765
- description: 'acquireTokenOnBehalfOf is not supported by the Entra sidecar auth provider in Phase 1'
766
- },
767
-
768
- /**
769
- * Error thrown when the sidecar returns a token with an authorization scheme other than Bearer.
770
- * The hosting stack always transmits tokens as `Bearer {token}`, so a non-Bearer scheme cannot be
771
- * honored and is rejected rather than silently sent as an invalid Authorization header.
772
- */
773
- SidecarUnsupportedAuthScheme: {
774
- code: -120811,
775
- description: 'The Entra Agent ID sidecar returned an unsupported authorization scheme `{scheme}`. Only the Bearer scheme is supported'
776
- },
777
-
778
700
  // ============================================================================
779
701
  // Agent and Client Errors (-120600 to -120650)
780
702
  // ============================================================================
@@ -1012,32 +934,105 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
1012
934
  },
1013
935
 
1014
936
  // ============================================================================
1015
- // Hosting / Web layer errors (-120800 to -120840)
937
+ // Entra Agent ID Sidecar Authentication Errors (-120800 to -120811)
1016
938
  // ============================================================================
1017
939
 
1018
940
  /**
1019
- * Error thrown by CloudAdapter.process when the incoming request lacks a parsed body.
1020
- * Indicates the hosting layer (e.g. express.json() or Fastify's JSON parser) was not configured.
941
+ * Error thrown when the Entra sidecar auth provider is used without connection settings.
1021
942
  */
1022
- MissingRequestBody: {
943
+ SidecarConnectionSettingsRequired: {
1023
944
  code: -120800,
1024
- description: '`request.body` parameter required; ensure your hosting layer parses JSON request bodies before invoking the adapter (e.g., express.json() with Express or Fastify\'s built-in JSON parser).'
945
+ description: 'Connection settings must be provided when using the Entra sidecar auth provider'
1025
946
  },
1026
947
 
1027
948
  /**
1028
- * Error thrown by verifyToken when the provided JWT cannot be decoded.
949
+ * Error thrown when the resolved sidecar base URL is not a valid absolute URL.
1029
950
  */
1030
- InvalidJwtToken: {
951
+ SidecarBaseUrlInvalid: {
952
+ code: -120801,
953
+ description: 'The resolved sidecar base URL `{url}` is not a valid absolute URL'
954
+ },
955
+
956
+ /**
957
+ * Error thrown when the resolved sidecar base URL does not use the http or https scheme.
958
+ */
959
+ SidecarBaseUrlInsecureScheme: {
960
+ code: -120802,
961
+ description: 'The resolved sidecar base URL `{url}` must use the http or https scheme'
962
+ },
963
+
964
+ /**
965
+ * Error thrown when the resolved sidecar base URL contains userinfo (credentials).
966
+ */
967
+ SidecarBaseUrlUserInfo: {
968
+ code: -120803,
969
+ description: 'The resolved sidecar base URL `{url}` must not contain userinfo (credentials)'
970
+ },
971
+
972
+ /**
973
+ * Error thrown when the resolved sidecar base URL points to a non-loopback, non-private address.
974
+ */
975
+ SidecarBaseUrlNotLocal: {
976
+ code: -120804,
977
+ description: 'The resolved sidecar base URL `{url}` points to a non-loopback, non-private address. The Entra Agent ID sidecar (agent container) must be reachable only from within the agent\'s network boundary. Set `bypassLocalNetworkRestriction` to true in the connection configuration to override this safety check (UNSAFE: only for carefully validated private-network configurations)'
978
+ },
979
+
980
+ /**
981
+ * Error thrown when both AgentUsername and AgentUserId are supplied on a sidecar request (mutually exclusive).
982
+ */
983
+ SidecarUserIdentityMutuallyExclusive: {
984
+ code: -120805,
985
+ description: 'AgentUsername and AgentUserId are mutually exclusive; set only one on the sidecar request options'
986
+ },
987
+
988
+ /**
989
+ * Error thrown when a sidecar request fails after exhausting all retry attempts.
990
+ */
991
+ SidecarRequestFailed: {
992
+ code: -120806,
993
+ description: 'Sidecar request failed after {attempts} attempt(s): {message}'
994
+ },
995
+
996
+ /**
997
+ * Error thrown when the sidecar responds with a non-success status code.
998
+ */
999
+ SidecarErrorResponse: {
1000
+ code: -120807,
1001
+ description: 'Sidecar returned error status {status}: {message}'
1002
+ },
1003
+
1004
+ /**
1005
+ * Error thrown when the sidecar response is missing the authorizationHeader field.
1006
+ */
1007
+ SidecarResponseMissingAuthorizationHeader: {
1008
+ code: -120808,
1009
+ description: 'Sidecar response missing `authorizationHeader` field'
1010
+ },
1011
+
1012
+ /**
1013
+ * Error thrown when the sidecar response body cannot be parsed.
1014
+ */
1015
+ SidecarResponseUnparsable: {
1016
+ code: -120809,
1017
+ description: 'Sidecar returned an unparsable response body'
1018
+ },
1019
+
1020
+ /**
1021
+ * Error thrown when acquireTokenOnBehalfOf is called on the sidecar provider, which does not support OBO in Phase 1.
1022
+ */
1023
+ OnBehalfOfNotSupportedBySidecar: {
1031
1024
  code: -120810,
1032
- description: 'invalid token'
1025
+ description: 'acquireTokenOnBehalfOf is not supported by the Entra sidecar auth provider in Phase 1'
1033
1026
  },
1034
1027
 
1035
1028
  /**
1036
- * Error thrown by verifyToken when the token audience does not match any configured connection clientId.
1029
+ * Error thrown when the sidecar returns a token with an authorization scheme other than Bearer.
1030
+ * The hosting stack always transmits tokens as `Bearer {token}`, so a non-Bearer scheme cannot be
1031
+ * honored and is rejected rather than silently sent as an invalid Authorization header.
1037
1032
  */
1038
- JwtAudienceMismatch: {
1039
- code: -120820,
1040
- description: 'Audience mismatch'
1033
+ SidecarUnsupportedAuthScheme: {
1034
+ code: -120811,
1035
+ description: 'The Entra Agent ID sidecar returned an unsupported authorization scheme `{scheme}`. Only the Bearer scheme is supported'
1041
1036
  },
1042
1037
 
1043
1038
  // ============================================================================
@@ -1102,6 +1097,52 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
1102
1097
  description: "You must configure the Application with an 'adapter' before calling Application.continueConversationAsync()"
1103
1098
  },
1104
1099
 
1100
+ // ============================================================================
1101
+ // Hosting / Web layer errors (-120910 to -120940)
1102
+ // ============================================================================
1103
+
1104
+ /**
1105
+ * Error thrown by CloudAdapter.process when the incoming request lacks a parsed body.
1106
+ * Indicates the hosting layer (e.g. express.json() or Fastify's JSON parser) was not configured.
1107
+ */
1108
+ MissingRequestBody: {
1109
+ code: -120910,
1110
+ description: '`request.body` parameter required; ensure your hosting layer parses JSON request bodies before invoking the adapter (e.g., express.json() with Express or Fastify\'s built-in JSON parser).'
1111
+ },
1112
+
1113
+ /**
1114
+ * Error thrown by verifyToken when the provided JWT cannot be decoded.
1115
+ */
1116
+ InvalidJwtToken: {
1117
+ code: -120920,
1118
+ description: 'invalid token'
1119
+ },
1120
+
1121
+ /**
1122
+ * Error thrown by verifyToken when the token audience does not match any configured connection clientId.
1123
+ */
1124
+ JwtAudienceMismatch: {
1125
+ code: -120930,
1126
+ description: 'Audience mismatch'
1127
+ },
1128
+
1129
+ /**
1130
+ * Error thrown by verifyToken when the token issuer is not in the connection's allowed issuer list.
1131
+ */
1132
+ JwtIssuerMismatch: {
1133
+ code: -120830,
1134
+ description: 'Issuer mismatch'
1135
+ },
1136
+
1137
+ /**
1138
+ * Error thrown by verifyToken when an Entra token's tenant id (`tid`) claim does not match the
1139
+ * tenant GUID embedded in its issuer (`iss`) claim.
1140
+ */
1141
+ JwtTenantMismatch: {
1142
+ code: -120840,
1143
+ description: 'Tenant mismatch'
1144
+ },
1145
+
1105
1146
  // ============================================================================
1106
1147
  // Adaptive Cards Errors (-120950 to -120980)
1107
1148
  // ============================================================================
package/src/index.ts CHANGED
@@ -32,5 +32,6 @@ export * from './storage/storage'
32
32
  export * from './headerPropagation'
33
33
  export * from './interfaces'
34
34
  export * from './httpClient'
35
+ export * from './outboundHostValidator'
35
36
 
36
37
  export * from './agent-client'
@@ -22,4 +22,9 @@ export interface AdaptiveCardInvokeValue {
22
22
  * The state of the adaptive card.
23
23
  */
24
24
  state: string
25
+ /**
26
+ * Indicates whether Teams initiated the action while rendering a refreshable
27
+ * card or a user explicitly selected an action.
28
+ */
29
+ trigger?: 'automatic' | 'manual'
25
30
  }