@microsoft/omnichannel-chat-widget 1.8.1-main.65a1ab5 → 1.8.1-main.6ec59e7

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.
@@ -19,6 +19,7 @@ _defineProperty(Constants, "magicCodeBroadcastChannel", "MagicCodeChannel");
19
19
  _defineProperty(Constants, "magicCodeResponseBroadcastChannel", "MagicCodeResponseChannel");
20
20
  _defineProperty(Constants, "systemMessageTag", "system");
21
21
  _defineProperty(Constants, "userMessageTag", "user");
22
+ _defineProperty(Constants, "channelMessageTag", "channel");
22
23
  _defineProperty(Constants, "historyMessageTag", "history");
23
24
  _defineProperty(Constants, "agentEndConversationMessageTag", "agentendconversation");
24
25
  _defineProperty(Constants, "supervisorForceCloseMessageTag", "supervisorforceclosedconversation");
@@ -256,6 +256,7 @@ exports.TelemetryEvent = TelemetryEvent;
256
256
  TelemetryEvent["UXNotificationPaneCompleted"] = "UXNotificationPaneCompleted";
257
257
  TelemetryEvent["UXOutOfOfficeHoursPaneStart"] = "UXOutOfOfficeHoursPaneStart";
258
258
  TelemetryEvent["UXOutOfOfficeHoursPaneCompleted"] = "UXOutOfOfficeHoursPaneCompleted";
259
+ TelemetryEvent["XSSTextDetected"] = "XSSTextDetected";
259
260
  TelemetryEvent["UXPostChatLoadingPaneStart"] = "UXPostChatLoadingPaneStart";
260
261
  TelemetryEvent["UXPostChatLoadingPaneCompleted"] = "UXPostChatLoadingPaneCompleted";
261
262
  TelemetryEvent["UXPrechatPaneStart"] = "UXPrechatPaneStart";
@@ -64,8 +64,8 @@ const RegisterLoggers = () => {
64
64
  if (((_TelemetryManager$Int22 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int22 === void 0 ? void 0 : (_TelemetryManager$Int23 = _TelemetryManager$Int22.appInsightsConfig) === null || _TelemetryManager$Int23 === void 0 ? void 0 : _TelemetryManager$Int23.appInsightsDisabled) === false) {
65
65
  var _TelemetryManager$Int24;
66
66
  if ((_TelemetryManager$Int24 = TelemetryManager.InternalTelemetryData) !== null && _TelemetryManager$Int24 !== void 0 && _TelemetryManager$Int24.appInsightsConfig.appInsightsKey) {
67
- var _TelemetryManager$Int25, _TelemetryManager$Int26, _TelemetryManager$Int27;
68
- loggers.push((0, _appInsightsLogger.appInsightsLogger)((_TelemetryManager$Int25 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int25 === void 0 ? void 0 : _TelemetryManager$Int25.appInsightsConfig.appInsightsKey, ((_TelemetryManager$Int26 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int26 === void 0 ? void 0 : (_TelemetryManager$Int27 = _TelemetryManager$Int26.ariaConfig) === null || _TelemetryManager$Int27 === void 0 ? void 0 : _TelemetryManager$Int27.disableCookieUsage) ?? _defaultAriaConfig.defaultAriaConfig.disableCookieUsage));
67
+ var _TelemetryManager$Int25;
68
+ loggers.push((0, _appInsightsLogger.appInsightsLogger)((_TelemetryManager$Int25 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int25 === void 0 ? void 0 : _TelemetryManager$Int25.appInsightsConfig.appInsightsKey));
69
69
  }
70
70
  }
71
71
  }
@@ -25,14 +25,14 @@ var AllowedKeys;
25
25
  AllowedKeys["ElapsedTimeInMilliseconds"] = "DurationInMilliseconds";
26
26
  })(AllowedKeys || (AllowedKeys = {}));
27
27
  let initializationPromise = null;
28
- const appInsightsLogger = (appInsightsKey, disableCookiesUsage) => {
28
+ const appInsightsLogger = appInsightsKey => {
29
29
  const isValidKey = key => {
30
30
  const INSTRUMENTATION_KEY_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
31
31
  const INSTRUMENTATION_KEY_REGEX = new RegExp(`^${INSTRUMENTATION_KEY_PATTERN}$`);
32
32
  const CONNECTION_STRING_REGEX = new RegExp(`^InstrumentationKey=${INSTRUMENTATION_KEY_PATTERN};IngestionEndpoint=https://[a-zA-Z0-9\\-\\.]+\\.applicationinsights\\.azure\\.com/.*`);
33
33
  return INSTRUMENTATION_KEY_REGEX.test(key) || CONNECTION_STRING_REGEX.test(key);
34
34
  };
35
- const initializeAppInsights = async (appInsightsKey, disableCookiesUsage) => {
35
+ const initializeAppInsights = async appInsightsKey => {
36
36
  if (!window.appInsights && appInsightsKey) {
37
37
  if (!isValidKey(appInsightsKey)) {
38
38
  _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.ERROR, {
@@ -54,8 +54,7 @@ const appInsightsLogger = (appInsightsKey, disableCookiesUsage) => {
54
54
  connectionString: appInsightsKey
55
55
  } : {
56
56
  instrumentationKey: appInsightsKey
57
- }),
58
- disableCookiesUsage: disableCookiesUsage
57
+ })
59
58
  };
60
59
 
61
60
  // Initialize Application Insights instance
@@ -82,7 +81,7 @@ const appInsightsLogger = (appInsightsKey, disableCookiesUsage) => {
82
81
  };
83
82
  const logger = async () => {
84
83
  if (!initializationPromise) {
85
- initializationPromise = initializeAppInsights(appInsightsKey, disableCookiesUsage);
84
+ initializationPromise = initializeAppInsights(appInsightsKey);
86
85
  }
87
86
  await initializationPromise;
88
87
  return window.appInsights;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.detectAndCleanXSS = void 0;
7
+ var _dompurify = _interopRequireDefault(require("dompurify"));
8
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
+ /**
10
+ * Detects potential Cross-Site Scripting (XSS) attacks in text input and sanitizes the content.
11
+ *
12
+ * This function performs comprehensive XSS detection using pattern matching for common attack vectors
13
+ * and then sanitizes the input using DOMPurify with strict configuration. It's designed to protect
14
+ * against various XSS techniques including script injection, event handler injection, style-based
15
+ * attacks, and encoded payloads.
16
+ *
17
+ * Security patterns detected:
18
+ * - JavaScript protocol URLs (javascript:)
19
+ * - HTML event handlers (onmouseover, onclick, etc.)
20
+ * - Script tags (<script>)
21
+ * - CSS expression() functions
22
+ * - CSS url() functions
23
+ * - Position-based CSS attacks (position: fixed/absolute)
24
+ * - VBScript protocol URLs
25
+ * - Data URLs with HTML content
26
+ * - Fragment identifiers with escaped quotes
27
+ * - HTML entity-encoded angle brackets
28
+ *
29
+ * @param text - The input text to be analyzed and sanitized
30
+ * @returns An object containing:
31
+ * - cleanText: The sanitized version of the input text with all HTML tags and attributes removed
32
+ * - isXSSDetected: Boolean flag indicating whether potential XSS patterns were found in the original text
33
+ */
34
+ const detectAndCleanXSS = text => {
35
+ // Comprehensive array of regular expressions to detect common XSS attack patterns
36
+ const xssPatterns = [/javascript\s*:/gi,
37
+ // JavaScript protocol URLs (with optional spaces)
38
+ /vbscript\s*:/gi,
39
+ // VBScript protocol URLs (with optional spaces)
40
+ /on\w+\s*=/gi,
41
+ // HTML event handlers (onmouseover, onclick, onload, etc.)
42
+ /<\s*script/gi,
43
+ // Script tag opening (with optional spaces)
44
+ /expression\s*\(/gi,
45
+ // CSS expression() function (IE-specific)
46
+ /url\s*\(/gi,
47
+ // CSS url() function
48
+ /style\s*=.*position\s*:\s*fixed/gi,
49
+ // CSS position fixed attacks
50
+ /style\s*=.*position\s*:\s*absolute/gi,
51
+ // CSS position absolute attacks
52
+ /data\s*:\s*text\s*\/\s*html/gi,
53
+ // Data URLs containing HTML
54
+ /#.*\\"/gi,
55
+ // Fragment identifiers with escaped quotes
56
+ /&gt;.*&lt;/gi // HTML entity-encoded angle brackets indicating tag structure
57
+ ];
58
+
59
+ // Check if any XSS patterns are detected in the input text
60
+ const isXSSDetected = xssPatterns.some(pattern => pattern.test(text));
61
+
62
+ // Clean the text using DOMPurify with strict config
63
+ const cleanText = _dompurify.default.sanitize(text, {
64
+ ALLOWED_TAGS: [],
65
+ // No HTML tags allowed in title
66
+ ALLOWED_ATTR: [],
67
+ KEEP_CONTENT: true,
68
+ // Keep text content
69
+ ALLOW_DATA_ATTR: false,
70
+ ALLOW_UNKNOWN_PROTOCOLS: false,
71
+ SANITIZE_DOM: true,
72
+ FORCE_BODY: false
73
+ });
74
+ return {
75
+ cleanText,
76
+ isXSSDetected
77
+ };
78
+ };
79
+ exports.detectAndCleanXSS = detectAndCleanXSS;
@@ -315,13 +315,7 @@ const canConnectToExistingChat = async (props, facadeChatSDK, state, dispatch, s
315
315
 
316
316
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
317
  const setCustomContextParams = async (state, props) => {
318
- var _props$chatConfig, _props$chatConfig$Liv, _state$domainStates8, _persistedState$domai8;
319
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
- const isAuthenticatedChat = props !== null && props !== void 0 && (_props$chatConfig = props.chatConfig) !== null && _props$chatConfig !== void 0 && (_props$chatConfig$Liv = _props$chatConfig.LiveChatConfigAuthSettings) !== null && _props$chatConfig$Liv !== void 0 && _props$chatConfig$Liv.msdyn_javascriptclientfunction ? true : false;
321
- //Should not set custom context for auth chat
322
- if (isAuthenticatedChat) {
323
- return;
324
- }
318
+ var _state$domainStates8, _persistedState$domai8;
325
319
  if (state !== null && state !== void 0 && (_state$domainStates8 = state.domainStates) !== null && _state$domainStates8 !== void 0 && _state$domainStates8.customContext) {
326
320
  var _state$domainStates9;
327
321
  optionalParams = Object.assign({}, optionalParams, {
@@ -7,15 +7,16 @@ exports.default = exports.OutOfOfficeHoursPaneStateful = void 0;
7
7
  var _TelemetryConstants = require("../../common/telemetry/TelemetryConstants");
8
8
  var _react = _interopRequireWildcard(require("react"));
9
9
  var _utils = require("../../common/utils");
10
- var _dompurify = _interopRequireDefault(require("dompurify"));
11
10
  var _omnichannelChatComponents = require("@microsoft/omnichannel-chat-components");
12
11
  var _TelemetryHelper = require("../../common/telemetry/TelemetryHelper");
13
12
  var _defaultgeneralOOOHPaneStyleProps = require("./common/defaultStyleProps/defaultgeneralOOOHPaneStyleProps");
13
+ var _xssUtils = require("../../common/utils/xssUtils");
14
14
  var _useChatContextStore = _interopRequireDefault(require("../../hooks/useChatContextStore"));
15
15
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
16
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
17
17
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
18
18
  let uiTimer;
19
+ const OOOHPaneTitleText = "Thanks for contacting us. You have reached us outside of our operating hours. An agent will respond when we open.";
19
20
  const OutOfOfficeHoursPaneStateful = props => {
20
21
  var _props$styleProps;
21
22
  (0, _react.useEffect)(() => {
@@ -54,8 +55,28 @@ const OutOfOfficeHoursPaneStateful = props => {
54
55
  ElapsedTimeInMilliseconds: uiTimer.milliSecondsElapsed
55
56
  });
56
57
  }, []);
58
+
59
+ // Enhanced titleText sanitization
57
60
  if (controlProps !== null && controlProps !== void 0 && controlProps.titleText) {
58
- controlProps.titleText = _dompurify.default.sanitize(controlProps.titleText);
61
+ const {
62
+ cleanText,
63
+ isXSSDetected
64
+ } = (0, _xssUtils.detectAndCleanXSS)(controlProps.titleText);
65
+ if (!isXSSDetected) {
66
+ // replace with the sanitized text
67
+ controlProps.titleText = cleanText;
68
+ } else {
69
+ _TelemetryHelper.TelemetryHelper.logLoadingEventToAllTelemetry(_TelemetryConstants.LogLevel.WARN, {
70
+ Event: _TelemetryConstants.TelemetryEvent.XSSTextDetected,
71
+ Description: "Potential XSS attempt detected in titleText",
72
+ CustomProperties: {
73
+ originalText: controlProps.titleText.substring(0, 100),
74
+ // Log first 100 chars for analysis
75
+ cleanedText: cleanText.substring(0, 100)
76
+ }
77
+ });
78
+ controlProps.titleText = OOOHPaneTitleText;
79
+ }
59
80
  }
60
81
  return /*#__PURE__*/_react.default.createElement(_omnichannelChatComponents.OutOfOfficeHoursPane, {
61
82
  componentOverrides: props.componentOverrides,
@@ -73,11 +73,13 @@ const polyfillMessagePayloadForEvent = (activity, payload, conversationId) => {
73
73
  };
74
74
  exports.polyfillMessagePayloadForEvent = polyfillMessagePayloadForEvent;
75
75
  const getScenarioType = activity => {
76
- var _activity$from3, _activity$channelData4, _activity$channelData5;
77
- if ((activity === null || activity === void 0 ? void 0 : (_activity$from3 = activity.from) === null || _activity$from3 === void 0 ? void 0 : _activity$from3.role) === _Constants2.Constants.userMessageTag) {
76
+ var _activity$from3, _activity$channelData4;
77
+ const role = activity === null || activity === void 0 ? void 0 : (_activity$from3 = activity.from) === null || _activity$from3 === void 0 ? void 0 : _activity$from3.role;
78
+ const tags = activity === null || activity === void 0 ? void 0 : (_activity$channelData4 = activity.channelData) === null || _activity$channelData4 === void 0 ? void 0 : _activity$channelData4.tags;
79
+ if (role === _Constants2.Constants.userMessageTag) {
78
80
  return _Constants.ScenarioType.UserSendMessageStrategy;
79
81
  }
80
- if (activity !== null && activity !== void 0 && (_activity$channelData4 = activity.channelData) !== null && _activity$channelData4 !== void 0 && (_activity$channelData5 = _activity$channelData4.tags) !== null && _activity$channelData5 !== void 0 && _activity$channelData5.includes(_Constants2.Constants.systemMessageTag)) {
82
+ if (tags && tags.includes(_Constants2.Constants.systemMessageTag) || role === _Constants2.Constants.channelMessageTag) {
81
83
  return _Constants.ScenarioType.SystemMessageStrategy;
82
84
  }
83
85
  return _Constants.ScenarioType.ReceivedMessageStrategy;
@@ -12,6 +12,7 @@ _defineProperty(Constants, "magicCodeBroadcastChannel", "MagicCodeChannel");
12
12
  _defineProperty(Constants, "magicCodeResponseBroadcastChannel", "MagicCodeResponseChannel");
13
13
  _defineProperty(Constants, "systemMessageTag", "system");
14
14
  _defineProperty(Constants, "userMessageTag", "user");
15
+ _defineProperty(Constants, "channelMessageTag", "channel");
15
16
  _defineProperty(Constants, "historyMessageTag", "history");
16
17
  _defineProperty(Constants, "agentEndConversationMessageTag", "agentendconversation");
17
18
  _defineProperty(Constants, "supervisorForceCloseMessageTag", "supervisorforceclosedconversation");
@@ -250,6 +250,7 @@ export let TelemetryEvent;
250
250
  TelemetryEvent["UXNotificationPaneCompleted"] = "UXNotificationPaneCompleted";
251
251
  TelemetryEvent["UXOutOfOfficeHoursPaneStart"] = "UXOutOfOfficeHoursPaneStart";
252
252
  TelemetryEvent["UXOutOfOfficeHoursPaneCompleted"] = "UXOutOfOfficeHoursPaneCompleted";
253
+ TelemetryEvent["XSSTextDetected"] = "XSSTextDetected";
253
254
  TelemetryEvent["UXPostChatLoadingPaneStart"] = "UXPostChatLoadingPaneStart";
254
255
  TelemetryEvent["UXPostChatLoadingPaneCompleted"] = "UXPostChatLoadingPaneCompleted";
255
256
  TelemetryEvent["UXPrechatPaneStart"] = "UXPrechatPaneStart";
@@ -55,8 +55,8 @@ export const RegisterLoggers = () => {
55
55
  if (((_TelemetryManager$Int22 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int22 === void 0 ? void 0 : (_TelemetryManager$Int23 = _TelemetryManager$Int22.appInsightsConfig) === null || _TelemetryManager$Int23 === void 0 ? void 0 : _TelemetryManager$Int23.appInsightsDisabled) === false) {
56
56
  var _TelemetryManager$Int24;
57
57
  if ((_TelemetryManager$Int24 = TelemetryManager.InternalTelemetryData) !== null && _TelemetryManager$Int24 !== void 0 && _TelemetryManager$Int24.appInsightsConfig.appInsightsKey) {
58
- var _TelemetryManager$Int25, _TelemetryManager$Int26, _TelemetryManager$Int27;
59
- loggers.push(appInsightsLogger((_TelemetryManager$Int25 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int25 === void 0 ? void 0 : _TelemetryManager$Int25.appInsightsConfig.appInsightsKey, ((_TelemetryManager$Int26 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int26 === void 0 ? void 0 : (_TelemetryManager$Int27 = _TelemetryManager$Int26.ariaConfig) === null || _TelemetryManager$Int27 === void 0 ? void 0 : _TelemetryManager$Int27.disableCookieUsage) ?? defaultAriaConfig.disableCookieUsage));
58
+ var _TelemetryManager$Int25;
59
+ loggers.push(appInsightsLogger((_TelemetryManager$Int25 = TelemetryManager.InternalTelemetryData) === null || _TelemetryManager$Int25 === void 0 ? void 0 : _TelemetryManager$Int25.appInsightsConfig.appInsightsKey));
60
60
  }
61
61
  }
62
62
  }
@@ -16,14 +16,14 @@ var AllowedKeys;
16
16
  AllowedKeys["ElapsedTimeInMilliseconds"] = "DurationInMilliseconds";
17
17
  })(AllowedKeys || (AllowedKeys = {}));
18
18
  let initializationPromise = null;
19
- export const appInsightsLogger = (appInsightsKey, disableCookiesUsage) => {
19
+ export const appInsightsLogger = appInsightsKey => {
20
20
  const isValidKey = key => {
21
21
  const INSTRUMENTATION_KEY_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
22
22
  const INSTRUMENTATION_KEY_REGEX = new RegExp(`^${INSTRUMENTATION_KEY_PATTERN}$`);
23
23
  const CONNECTION_STRING_REGEX = new RegExp(`^InstrumentationKey=${INSTRUMENTATION_KEY_PATTERN};IngestionEndpoint=https://[a-zA-Z0-9\\-\\.]+\\.applicationinsights\\.azure\\.com/.*`);
24
24
  return INSTRUMENTATION_KEY_REGEX.test(key) || CONNECTION_STRING_REGEX.test(key);
25
25
  };
26
- const initializeAppInsights = async (appInsightsKey, disableCookiesUsage) => {
26
+ const initializeAppInsights = async appInsightsKey => {
27
27
  if (!window.appInsights && appInsightsKey) {
28
28
  if (!isValidKey(appInsightsKey)) {
29
29
  TelemetryHelper.logActionEvent(LogLevel.ERROR, {
@@ -45,8 +45,7 @@ export const appInsightsLogger = (appInsightsKey, disableCookiesUsage) => {
45
45
  connectionString: appInsightsKey
46
46
  } : {
47
47
  instrumentationKey: appInsightsKey
48
- }),
49
- disableCookiesUsage: disableCookiesUsage
48
+ })
50
49
  };
51
50
 
52
51
  // Initialize Application Insights instance
@@ -73,7 +72,7 @@ export const appInsightsLogger = (appInsightsKey, disableCookiesUsage) => {
73
72
  };
74
73
  const logger = async () => {
75
74
  if (!initializationPromise) {
76
- initializationPromise = initializeAppInsights(appInsightsKey, disableCookiesUsage);
75
+ initializationPromise = initializeAppInsights(appInsightsKey);
77
76
  }
78
77
  await initializationPromise;
79
78
  return window.appInsights;
@@ -0,0 +1,72 @@
1
+ import DOMPurify from "dompurify";
2
+
3
+ /**
4
+ * Detects potential Cross-Site Scripting (XSS) attacks in text input and sanitizes the content.
5
+ *
6
+ * This function performs comprehensive XSS detection using pattern matching for common attack vectors
7
+ * and then sanitizes the input using DOMPurify with strict configuration. It's designed to protect
8
+ * against various XSS techniques including script injection, event handler injection, style-based
9
+ * attacks, and encoded payloads.
10
+ *
11
+ * Security patterns detected:
12
+ * - JavaScript protocol URLs (javascript:)
13
+ * - HTML event handlers (onmouseover, onclick, etc.)
14
+ * - Script tags (<script>)
15
+ * - CSS expression() functions
16
+ * - CSS url() functions
17
+ * - Position-based CSS attacks (position: fixed/absolute)
18
+ * - VBScript protocol URLs
19
+ * - Data URLs with HTML content
20
+ * - Fragment identifiers with escaped quotes
21
+ * - HTML entity-encoded angle brackets
22
+ *
23
+ * @param text - The input text to be analyzed and sanitized
24
+ * @returns An object containing:
25
+ * - cleanText: The sanitized version of the input text with all HTML tags and attributes removed
26
+ * - isXSSDetected: Boolean flag indicating whether potential XSS patterns were found in the original text
27
+ */
28
+ export const detectAndCleanXSS = text => {
29
+ // Comprehensive array of regular expressions to detect common XSS attack patterns
30
+ const xssPatterns = [/javascript\s*:/gi,
31
+ // JavaScript protocol URLs (with optional spaces)
32
+ /vbscript\s*:/gi,
33
+ // VBScript protocol URLs (with optional spaces)
34
+ /on\w+\s*=/gi,
35
+ // HTML event handlers (onmouseover, onclick, onload, etc.)
36
+ /<\s*script/gi,
37
+ // Script tag opening (with optional spaces)
38
+ /expression\s*\(/gi,
39
+ // CSS expression() function (IE-specific)
40
+ /url\s*\(/gi,
41
+ // CSS url() function
42
+ /style\s*=.*position\s*:\s*fixed/gi,
43
+ // CSS position fixed attacks
44
+ /style\s*=.*position\s*:\s*absolute/gi,
45
+ // CSS position absolute attacks
46
+ /data\s*:\s*text\s*\/\s*html/gi,
47
+ // Data URLs containing HTML
48
+ /#.*\\"/gi,
49
+ // Fragment identifiers with escaped quotes
50
+ /&gt;.*&lt;/gi // HTML entity-encoded angle brackets indicating tag structure
51
+ ];
52
+
53
+ // Check if any XSS patterns are detected in the input text
54
+ const isXSSDetected = xssPatterns.some(pattern => pattern.test(text));
55
+
56
+ // Clean the text using DOMPurify with strict config
57
+ const cleanText = DOMPurify.sanitize(text, {
58
+ ALLOWED_TAGS: [],
59
+ // No HTML tags allowed in title
60
+ ALLOWED_ATTR: [],
61
+ KEEP_CONTENT: true,
62
+ // Keep text content
63
+ ALLOW_DATA_ATTR: false,
64
+ ALLOW_UNKNOWN_PROTOCOLS: false,
65
+ SANITIZE_DOM: true,
66
+ FORCE_BODY: false
67
+ });
68
+ return {
69
+ cleanText,
70
+ isXSSDetected
71
+ };
72
+ };
@@ -307,13 +307,7 @@ const canConnectToExistingChat = async (props, facadeChatSDK, state, dispatch, s
307
307
 
308
308
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
309
309
  const setCustomContextParams = async (state, props) => {
310
- var _props$chatConfig, _props$chatConfig$Liv, _state$domainStates8, _persistedState$domai8;
311
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
312
- const isAuthenticatedChat = props !== null && props !== void 0 && (_props$chatConfig = props.chatConfig) !== null && _props$chatConfig !== void 0 && (_props$chatConfig$Liv = _props$chatConfig.LiveChatConfigAuthSettings) !== null && _props$chatConfig$Liv !== void 0 && _props$chatConfig$Liv.msdyn_javascriptclientfunction ? true : false;
313
- //Should not set custom context for auth chat
314
- if (isAuthenticatedChat) {
315
- return;
316
- }
310
+ var _state$domainStates8, _persistedState$domai8;
317
311
  if (state !== null && state !== void 0 && (_state$domainStates8 = state.domainStates) !== null && _state$domainStates8 !== void 0 && _state$domainStates8.customContext) {
318
312
  var _state$domainStates9;
319
313
  optionalParams = Object.assign({}, optionalParams, {
@@ -1,12 +1,13 @@
1
1
  import { LogLevel, TelemetryEvent } from "../../common/telemetry/TelemetryConstants";
2
2
  import React, { useEffect } from "react";
3
3
  import { createTimer, findAllFocusableElement } from "../../common/utils";
4
- import DOMPurify from "dompurify";
5
4
  import { OutOfOfficeHoursPane } from "@microsoft/omnichannel-chat-components";
6
5
  import { TelemetryHelper } from "../../common/telemetry/TelemetryHelper";
7
6
  import { defaultGeneralStyleProps } from "./common/defaultStyleProps/defaultgeneralOOOHPaneStyleProps";
7
+ import { detectAndCleanXSS } from "../../common/utils/xssUtils";
8
8
  import useChatContextStore from "../../hooks/useChatContextStore";
9
9
  let uiTimer;
10
+ const OOOHPaneTitleText = "Thanks for contacting us. You have reached us outside of our operating hours. An agent will respond when we open.";
10
11
  export const OutOfOfficeHoursPaneStateful = props => {
11
12
  var _props$styleProps;
12
13
  useEffect(() => {
@@ -45,8 +46,28 @@ export const OutOfOfficeHoursPaneStateful = props => {
45
46
  ElapsedTimeInMilliseconds: uiTimer.milliSecondsElapsed
46
47
  });
47
48
  }, []);
49
+
50
+ // Enhanced titleText sanitization
48
51
  if (controlProps !== null && controlProps !== void 0 && controlProps.titleText) {
49
- controlProps.titleText = DOMPurify.sanitize(controlProps.titleText);
52
+ const {
53
+ cleanText,
54
+ isXSSDetected
55
+ } = detectAndCleanXSS(controlProps.titleText);
56
+ if (!isXSSDetected) {
57
+ // replace with the sanitized text
58
+ controlProps.titleText = cleanText;
59
+ } else {
60
+ TelemetryHelper.logLoadingEventToAllTelemetry(LogLevel.WARN, {
61
+ Event: TelemetryEvent.XSSTextDetected,
62
+ Description: "Potential XSS attempt detected in titleText",
63
+ CustomProperties: {
64
+ originalText: controlProps.titleText.substring(0, 100),
65
+ // Log first 100 chars for analysis
66
+ cleanedText: cleanText.substring(0, 100)
67
+ }
68
+ });
69
+ controlProps.titleText = OOOHPaneTitleText;
70
+ }
50
71
  }
51
72
  return /*#__PURE__*/React.createElement(OutOfOfficeHoursPane, {
52
73
  componentOverrides: props.componentOverrides,
@@ -64,11 +64,13 @@ export const polyfillMessagePayloadForEvent = (activity, payload, conversationId
64
64
  };
65
65
  };
66
66
  export const getScenarioType = activity => {
67
- var _activity$from3, _activity$channelData4, _activity$channelData5;
68
- if ((activity === null || activity === void 0 ? void 0 : (_activity$from3 = activity.from) === null || _activity$from3 === void 0 ? void 0 : _activity$from3.role) === Constants.userMessageTag) {
67
+ var _activity$from3, _activity$channelData4;
68
+ const role = activity === null || activity === void 0 ? void 0 : (_activity$from3 = activity.from) === null || _activity$from3 === void 0 ? void 0 : _activity$from3.role;
69
+ const tags = activity === null || activity === void 0 ? void 0 : (_activity$channelData4 = activity.channelData) === null || _activity$channelData4 === void 0 ? void 0 : _activity$channelData4.tags;
70
+ if (role === Constants.userMessageTag) {
69
71
  return ScenarioType.UserSendMessageStrategy;
70
72
  }
71
- if (activity !== null && activity !== void 0 && (_activity$channelData4 = activity.channelData) !== null && _activity$channelData4 !== void 0 && (_activity$channelData5 = _activity$channelData4.tags) !== null && _activity$channelData5 !== void 0 && _activity$channelData5.includes(Constants.systemMessageTag)) {
73
+ if (tags && tags.includes(Constants.systemMessageTag) || role === Constants.channelMessageTag) {
72
74
  return ScenarioType.SystemMessageStrategy;
73
75
  }
74
76
  return ScenarioType.ReceivedMessageStrategy;
@@ -3,6 +3,7 @@ export declare class Constants {
3
3
  static readonly magicCodeResponseBroadcastChannel = "MagicCodeResponseChannel";
4
4
  static readonly systemMessageTag = "system";
5
5
  static readonly userMessageTag = "user";
6
+ static readonly channelMessageTag = "channel";
6
7
  static readonly historyMessageTag = "history";
7
8
  static readonly agentEndConversationMessageTag = "agentendconversation";
8
9
  static readonly supervisorForceCloseMessageTag = "supervisorforceclosedconversation";
@@ -238,6 +238,7 @@ export declare enum TelemetryEvent {
238
238
  UXNotificationPaneCompleted = "UXNotificationPaneCompleted",
239
239
  UXOutOfOfficeHoursPaneStart = "UXOutOfOfficeHoursPaneStart",
240
240
  UXOutOfOfficeHoursPaneCompleted = "UXOutOfOfficeHoursPaneCompleted",
241
+ XSSTextDetected = "XSSTextDetected",
241
242
  UXPostChatLoadingPaneStart = "UXPostChatLoadingPaneStart",
242
243
  UXPostChatLoadingPaneCompleted = "UXPostChatLoadingPaneCompleted",
243
244
  UXPrechatPaneStart = "UXPrechatPaneStart",
@@ -4,7 +4,7 @@ declare global {
4
4
  appInsights?: any;
5
5
  }
6
6
  }
7
- export declare const appInsightsLogger: (appInsightsKey: string, disableCookiesUsage: boolean) => IChatSDKLogger;
7
+ export declare const appInsightsLogger: (appInsightsKey: string) => IChatSDKLogger;
8
8
  export interface ICustomProperties {
9
9
  [key: string]: any;
10
10
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Detects potential Cross-Site Scripting (XSS) attacks in text input and sanitizes the content.
3
+ *
4
+ * This function performs comprehensive XSS detection using pattern matching for common attack vectors
5
+ * and then sanitizes the input using DOMPurify with strict configuration. It's designed to protect
6
+ * against various XSS techniques including script injection, event handler injection, style-based
7
+ * attacks, and encoded payloads.
8
+ *
9
+ * Security patterns detected:
10
+ * - JavaScript protocol URLs (javascript:)
11
+ * - HTML event handlers (onmouseover, onclick, etc.)
12
+ * - Script tags (<script>)
13
+ * - CSS expression() functions
14
+ * - CSS url() functions
15
+ * - Position-based CSS attacks (position: fixed/absolute)
16
+ * - VBScript protocol URLs
17
+ * - Data URLs with HTML content
18
+ * - Fragment identifiers with escaped quotes
19
+ * - HTML entity-encoded angle brackets
20
+ *
21
+ * @param text - The input text to be analyzed and sanitized
22
+ * @returns An object containing:
23
+ * - cleanText: The sanitized version of the input text with all HTML tags and attributes removed
24
+ * - isXSSDetected: Boolean flag indicating whether potential XSS patterns were found in the original text
25
+ */
26
+ export declare const detectAndCleanXSS: (text: string) => {
27
+ cleanText: string;
28
+ isXSSDetected: boolean;
29
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/omnichannel-chat-widget",
3
- "version": "1.8.1-main.65a1ab5",
3
+ "version": "1.8.1-main.6ec59e7",
4
4
  "description": "Microsoft Omnichannel Chat Widget",
5
5
  "main": "lib/cjs/index.js",
6
6
  "types": "lib/types/index.d.ts",