@microsoft/omnichannel-chat-components 1.1.16 → 1.1.17-0

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.
@@ -23,7 +23,7 @@ const HiddenTextStyles = {
23
23
  exports.HiddenTextStyles = HiddenTextStyles;
24
24
  const KeyCodes = (_class = class KeyCodes {}, _defineProperty(_class, "ENTER", "Enter"), _defineProperty(_class, "ESCAPE", "Escape"), _defineProperty(_class, "SPACE", "Space"), _defineProperty(_class, "DeclineCallHotKey", "D"), _defineProperty(_class, "AcceptAudioCallHotKey", "S"), _defineProperty(_class, "AcceptVideoCallHotKey", "A"), _defineProperty(_class, "ToggleMicHotKey", "M"), _defineProperty(_class, "ToggleCameraHotKey", "O"), _defineProperty(_class, "EndCallHotKey", "H"), _class);
25
25
  exports.KeyCodes = KeyCodes;
26
- const Regex = (_class2 = class Regex {}, _defineProperty(_class2, "EmailRegex", "(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-zA-Z0-9-]*[a-zA-Z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"), _defineProperty(_class2, "URLRegex", /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi), _class2);
26
+ const Regex = (_class2 = class Regex {}, _defineProperty(_class2, "EmailRegex", "(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-zA-Z0-9-]*[a-zA-Z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"), _defineProperty(_class2, "URLRegex", /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,}|mailto:[^\s]+|tel:[^\s]+|sms:[^\s]+)/gi), _class2);
27
27
  exports.Regex = Regex;
28
28
  let ElementType;
29
29
  exports.ElementType = ElementType;
@@ -131,20 +131,48 @@ const addNoreferrerNoopenerTag = htmlNode => {
131
131
  }
132
132
  }
133
133
  }
134
- };
134
+ }; // Escape HTML special characters to prevent XSS in string concatenation
135
+
135
136
 
136
137
  exports.addNoreferrerNoopenerTag = addNoreferrerNoopenerTag;
137
138
 
139
+ const escapeHTML = str => {
140
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
141
+ }; // Escape only characters dangerous in href attribute context
142
+
143
+
144
+ const escapeHrefAttribute = url => {
145
+ return url.replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
146
+ }; // Validate and sanitize URL to prevent javascript: and data: protocols
147
+
148
+
149
+ const isValidURL = url => {
150
+ const trimmedUrl = url.trim().toLowerCase(); // Block dangerous protocols
151
+
152
+ if (trimmedUrl.startsWith("javascript:") || trimmedUrl.startsWith("data:") || trimmedUrl.startsWith("vbscript:") || trimmedUrl.startsWith("file:")) {
153
+ return false;
154
+ } // Allow http, https, protocol-relative URLs, and safe contact schemes
155
+
156
+
157
+ return trimmedUrl.startsWith("http://") || trimmedUrl.startsWith("https://") || trimmedUrl.startsWith("www.") || trimmedUrl.startsWith("mailto:") || trimmedUrl.startsWith("tel:") || trimmedUrl.startsWith("sms:");
158
+ };
159
+
138
160
  const replaceURLWithAnchor = (text, openInNewTab) => {
139
161
  if (text) {
140
162
  const modifiedText = text.replace(_Constants.Regex.URLRegex, function (url) {
141
- if (openInNewTab) {
142
- // eslint-disable-next-line quotes
143
- return '<a href="' + url + '" rel="noreferrer noopener" target="_blank">' + url + '</a>';
144
- } // eslint-disable-next-line quotes
163
+ // Validate URL to prevent dangerous protocols
164
+ if (!isValidURL(url)) {
165
+ return escapeHTML(url); // Return escaped text, not a link
166
+ }
167
+
168
+ const escapedUrl = escapeHrefAttribute(url);
169
+ const displayText = escapeHTML(url);
145
170
 
171
+ if (openInNewTab) {
172
+ return `<a href="${escapedUrl}" rel="noreferrer noopener" target="_blank">${displayText}</a>`;
173
+ }
146
174
 
147
- return '<a href="' + url + '">' + url + '</a>';
175
+ return `<a href="${escapedUrl}">${displayText}</a>`;
148
176
  });
149
177
  return modifiedText;
150
178
  }
@@ -13,7 +13,7 @@ export const HiddenTextStyles = {
13
13
  whiteSpace: "nowrap"
14
14
  };
15
15
  export const KeyCodes = (_class = class KeyCodes {}, _defineProperty(_class, "ENTER", "Enter"), _defineProperty(_class, "ESCAPE", "Escape"), _defineProperty(_class, "SPACE", "Space"), _defineProperty(_class, "DeclineCallHotKey", "D"), _defineProperty(_class, "AcceptAudioCallHotKey", "S"), _defineProperty(_class, "AcceptVideoCallHotKey", "A"), _defineProperty(_class, "ToggleMicHotKey", "M"), _defineProperty(_class, "ToggleCameraHotKey", "O"), _defineProperty(_class, "EndCallHotKey", "H"), _class);
16
- export const Regex = (_class2 = class Regex {}, _defineProperty(_class2, "EmailRegex", "(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-zA-Z0-9-]*[a-zA-Z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"), _defineProperty(_class2, "URLRegex", /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi), _class2);
16
+ export const Regex = (_class2 = class Regex {}, _defineProperty(_class2, "EmailRegex", "(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-zA-Z0-9-]*[a-zA-Z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"), _defineProperty(_class2, "URLRegex", /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,}|mailto:[^\s]+|tel:[^\s]+|sms:[^\s]+)/gi), _class2);
17
17
  export let ElementType;
18
18
 
19
19
  (function (ElementType) {
@@ -93,17 +93,45 @@ export const addNoreferrerNoopenerTag = htmlNode => {
93
93
  }
94
94
  }
95
95
  }
96
+ }; // Escape HTML special characters to prevent XSS in string concatenation
97
+
98
+ const escapeHTML = str => {
99
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
100
+ }; // Escape only characters dangerous in href attribute context
101
+
102
+
103
+ const escapeHrefAttribute = url => {
104
+ return url.replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
105
+ }; // Validate and sanitize URL to prevent javascript: and data: protocols
106
+
107
+
108
+ const isValidURL = url => {
109
+ const trimmedUrl = url.trim().toLowerCase(); // Block dangerous protocols
110
+
111
+ if (trimmedUrl.startsWith("javascript:") || trimmedUrl.startsWith("data:") || trimmedUrl.startsWith("vbscript:") || trimmedUrl.startsWith("file:")) {
112
+ return false;
113
+ } // Allow http, https, protocol-relative URLs, and safe contact schemes
114
+
115
+
116
+ return trimmedUrl.startsWith("http://") || trimmedUrl.startsWith("https://") || trimmedUrl.startsWith("www.") || trimmedUrl.startsWith("mailto:") || trimmedUrl.startsWith("tel:") || trimmedUrl.startsWith("sms:");
96
117
  };
118
+
97
119
  export const replaceURLWithAnchor = (text, openInNewTab) => {
98
120
  if (text) {
99
121
  const modifiedText = text.replace(Regex.URLRegex, function (url) {
100
- if (openInNewTab) {
101
- // eslint-disable-next-line quotes
102
- return '<a href="' + url + '" rel="noreferrer noopener" target="_blank">' + url + '</a>';
103
- } // eslint-disable-next-line quotes
122
+ // Validate URL to prevent dangerous protocols
123
+ if (!isValidURL(url)) {
124
+ return escapeHTML(url); // Return escaped text, not a link
125
+ }
104
126
 
127
+ const escapedUrl = escapeHrefAttribute(url);
128
+ const displayText = escapeHTML(url);
129
+
130
+ if (openInNewTab) {
131
+ return `<a href="${escapedUrl}" rel="noreferrer noopener" target="_blank">${displayText}</a>`;
132
+ }
105
133
 
106
- return '<a href="' + url + '">' + url + '</a>';
134
+ return `<a href="${escapedUrl}">${displayText}</a>`;
107
135
  });
108
136
  return modifiedText;
109
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/omnichannel-chat-components",
3
- "version": "1.1.16",
3
+ "version": "1.1.17-0",
4
4
  "description": "Microsoft Omnichannel Chat Components",
5
5
  "main": "lib/cjs/index.js",
6
6
  "types": "lib/types/index.d.ts",