@microsoft/omnichannel-chat-widget 1.8.4-main.f45c44b → 1.8.4-main.f8ae4f8

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.
@@ -11,6 +11,7 @@ var _appInsightsLogger = require("./loggers/appInsightsLogger");
11
11
  var _ariaTelemetryLogger = require("./loggers/ariaTelemetryLogger");
12
12
  var _consoleLogger = require("./loggers/consoleLogger");
13
13
  var _defaultAriaConfig = require("./defaultConfigs/defaultAriaConfig");
14
+ var _sanitizeSasInPayload = require("./sanitizeSasInPayload");
14
15
  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
15
16
  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
16
17
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
@@ -81,11 +82,15 @@ const RegisterLoggers = () => {
81
82
  };
82
83
  };
83
84
  const logTelemetry = telemetryEvent => {
85
+ // Redact Azure SAS credentials in any URL embedded in the payload (e.g.
86
+ // customer-supplied blob asset URLs in widget customization props) before
87
+ // it fans out to console, Aria, AppInsights, or any custom logger.
88
+ const sanitizedPayload = (0, _sanitizeSasInPayload.sanitizeSasInPayload)(telemetryEvent === null || telemetryEvent === void 0 ? void 0 : telemetryEvent.payload);
84
89
  loggers.map(logger => {
85
90
  var _payload, _telemetryInput$paylo;
86
91
  const logLevel = telemetryEvent.logLevel ?? _TelemetryConstants.LogLevel.INFO;
87
92
  const scenarioType = ((_payload = telemetryEvent.payload) === null || _payload === void 0 ? void 0 : _payload.scenarioType) ?? _TelemetryConstants.ScenarioType.UNDEFINED;
88
- const telemetryInput = parseInput(telemetryEvent === null || telemetryEvent === void 0 ? void 0 : telemetryEvent.payload, scenarioType);
93
+ const telemetryInput = parseInput(sanitizedPayload, scenarioType);
89
94
  telemetryInput.telemetryInfo = {
90
95
  telemetryInfo: _TelemetryHelper.TelemetryHelper.buildTelemetryEvent(logLevel, telemetryInput)
91
96
  };
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.sanitizeSasInPayload = void 0;
7
+ /**
8
+ * Redacts the values of Azure Shared Access Signature (SAS) query parameters in any
9
+ * URL embedded in a telemetry payload, so customer-supplied SAS URLs (e.g. blob asset
10
+ * URLs in widget customization props) cannot leak credentials through Aria/AppInsights.
11
+ *
12
+ * Trigger: any URL whose query string contains `sig=`. When detected, every well-known
13
+ * SAS parameter value in that query is replaced with `[REDACTED]`. Other URLs and other
14
+ * substrings of the payload pass through unchanged.
15
+ *
16
+ * Coverage of SAS parameter names:
17
+ * - Service/Blob SAS: sig, sv, se, st, sp, spr, sr, sip
18
+ * - Account SAS: ss, srt
19
+ * - User-delegation key SAS: skoid, sktid, skt, ske, sks, skv, saoid, suoid, sce, sdd
20
+ * - Response header overrides: rscc, rscd, rsce, rscl, rsct
21
+ *
22
+ * Reference: https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
23
+ */
24
+
25
+ const SAS_PARAM_NAMES = new Set(["sig", "sv", "se", "st", "sp", "spr", "sr", "sip", "ss", "srt", "skoid", "sktid", "skt", "ske", "sks", "skv", "saoid", "suoid", "sce", "sdd", "rscc", "rscd", "rsce", "rscl", "rsct"]);
26
+ const REDACTED_VALUE = "[REDACTED]";
27
+
28
+ // Matches any http(s) URL substring up to whitespace / closing quote / angle-bracket / paren.
29
+ const URL_REGEX = /\bhttps?:\/\/[^\s"'<>)]+/gi;
30
+ const redactQueryIfSas = url => {
31
+ const queryIdx = url.indexOf("?");
32
+ if (queryIdx < 0) {
33
+ return url;
34
+ }
35
+ // Separate the fragment (#...) before processing the query — fragments come
36
+ // after the query string in URL syntax and must not be folded into the last
37
+ // query parameter's value when we split on `&`.
38
+ const afterQuestionMark = url.slice(queryIdx + 1);
39
+ const fragIdx = afterQuestionMark.indexOf("#");
40
+ const query = fragIdx < 0 ? afterQuestionMark : afterQuestionMark.slice(0, fragIdx);
41
+ const fragment = fragIdx < 0 ? "" : afterQuestionMark.slice(fragIdx); // includes the leading '#'
42
+ if (!/(^|&)sig=/i.test(query)) {
43
+ // No SAS signature present — leave the URL alone so legitimate non-SAS query
44
+ // strings (e.g. ?utm_source=...) are not mangled.
45
+ return url;
46
+ }
47
+ const base = url.slice(0, queryIdx);
48
+ const sanitizedQuery = query.split("&").map(pair => {
49
+ const eqIdx = pair.indexOf("=");
50
+ if (eqIdx < 0) {
51
+ return pair;
52
+ }
53
+ const name = pair.slice(0, eqIdx).toLowerCase();
54
+ return SAS_PARAM_NAMES.has(name) ? `${pair.slice(0, eqIdx)}=${REDACTED_VALUE}` : pair;
55
+ }).join("&");
56
+ return `${base}?${sanitizedQuery}${fragment}`;
57
+ };
58
+ const sanitizeString = s => s.replace(URL_REGEX, redactQueryIfSas);
59
+
60
+ /**
61
+ * Deep-walk a value and redact SAS credentials in any string it contains.
62
+ * Returns a new object/array (originals are not mutated); non-string leaves are
63
+ * passed through. Tree-shaped, JSON-like values only — not designed for class
64
+ * instances, prototype-bound objects, or cyclic graphs.
65
+ */
66
+ const sanitizeSasInPayload = value => {
67
+ if (typeof value === "string") {
68
+ return sanitizeString(value);
69
+ }
70
+ if (Array.isArray(value)) {
71
+ return value.map(sanitizeSasInPayload);
72
+ }
73
+ if (value !== null && typeof value === "object") {
74
+ const out = {};
75
+ for (const [k, v] of Object.entries(value)) {
76
+ out[k] = sanitizeSasInPayload(v);
77
+ }
78
+ return out;
79
+ }
80
+ return value;
81
+ };
82
+ exports.sanitizeSasInPayload = sanitizeSasInPayload;
@@ -173,14 +173,16 @@ const createMarkdown = (disableMarkdownMessageFormatting, disableNewLineMarkdown
173
173
 
174
174
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
175
  md.render = function (text, env) {
176
+ const safeEnv = env ?? {};
176
177
  const processedText = preprocessText(text);
177
- return md.renderer.render(md.parse(processedText, env), md.options, env);
178
+ return md.renderer.render(md.parse(processedText, safeEnv), md.options, safeEnv);
178
179
  };
179
180
 
180
181
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
181
182
  md.renderInline = function (text, env) {
183
+ const safeEnv = env ?? {};
182
184
  const processedText = preprocessText(text);
183
- return md.renderer.render(md.parseInline(processedText, env), md.options, env);
185
+ return md.renderer.render(md.parseInline(processedText, safeEnv), md.options, safeEnv);
184
186
  };
185
187
  });
186
188
 
@@ -304,24 +304,28 @@ const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) =>
304
304
  const conversationId = (state === null || state === void 0 ? void 0 : (_state$domainStates2 = state.domainStates) === null || _state$domainStates2 === void 0 ? void 0 : (_state$domainStates2$ = _state$domainStates2.chatToken) === null || _state$domainStates2$ === void 0 ? void 0 : _state$domainStates2$.chatId) || "unknown";
305
305
  _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
306
306
  Event: _TelemetryConstants.TelemetryEvent.HTMLSanitized,
307
- Description: "HTML content would be sanitized by stricter allowlist (monitor-only)",
308
- ElapsedTimeInMilliseconds: executionTimeMs,
309
- CustomProperties: {
310
- OrganizationId: orgId,
311
- ConversationId: conversationId,
312
- RemovedTags: uniqueTags.join(", "),
313
- RemovedAttributes: uniqueAttrs.join(", "),
314
- Phase: "Monitor"
315
- }
307
+ Description: JSON.stringify({
308
+ message: "HTML content would be sanitized by stricter allowlist (monitor-only)",
309
+ removedTags: uniqueTags,
310
+ removedAttributes: uniqueAttrs,
311
+ phase: "Monitor",
312
+ organizationId: orgId,
313
+ conversationId: conversationId
314
+ }),
315
+ ElapsedTimeInMilliseconds: executionTimeMs
316
316
  });
317
317
 
318
318
  // Log to console in development for debugging
319
319
  if (process.env.NODE_ENV === "development") {
320
- console.warn("[Monitor] Stricter HTML sanitization would remove:", {
320
+ console.warn("[Monitor] Stricter HTML sanitization telemetry:", {
321
+ description: JSON.parse(JSON.stringify({
322
+ message: "HTML content would be sanitized by stricter allowlist (monitor-only)",
323
+ removedTags: uniqueTags,
324
+ removedAttributes: uniqueAttrs,
325
+ phase: "Monitor"
326
+ })),
321
327
  orgId,
322
328
  conversationId,
323
- removedTags: uniqueTags,
324
- removedAttributes: uniqueAttrs,
325
329
  executionTimeMs
326
330
  });
327
331
  }
@@ -221,7 +221,11 @@ const PreChatSurveyPaneStateful = props => {
221
221
  }
222
222
  }, []);
223
223
  return /*#__PURE__*/_react.default.createElement("div", {
224
- onFocusCapture: announceFocusedElement
224
+ onFocusCapture: announceFocusedElement,
225
+ style: {
226
+ width: "100%",
227
+ height: "100%"
228
+ }
225
229
  }, /*#__PURE__*/_react.default.createElement("div", {
226
230
  role: "status",
227
231
  "aria-live": "polite",
@@ -12,7 +12,6 @@ const defaultGeneralPreChatSurveyPaneStyleProps = {
12
12
  borderColor: "#F1F1F1",
13
13
  overflowY: "auto",
14
14
  height: "inherit",
15
- width: "inherit",
16
- overscrollBehavior: "none"
15
+ width: "inherit"
17
16
  };
18
17
  exports.defaultGeneralPreChatSurveyPaneStyleProps = defaultGeneralPreChatSurveyPaneStyleProps;
@@ -11,6 +11,7 @@ import { appInsightsLogger } from "./loggers/appInsightsLogger";
11
11
  import { ariaTelemetryLogger } from "./loggers/ariaTelemetryLogger";
12
12
  import { consoleLogger } from "./loggers/consoleLogger";
13
13
  import { defaultAriaConfig } from "./defaultConfigs/defaultAriaConfig";
14
+ import { sanitizeSasInPayload } from "./sanitizeSasInPayload";
14
15
  export let TelemetryTimers = /*#__PURE__*/_createClass(function TelemetryTimers() {
15
16
  _classCallCheck(this, TelemetryTimers);
16
17
  });
@@ -72,11 +73,15 @@ export const RegisterLoggers = () => {
72
73
  };
73
74
  };
74
75
  const logTelemetry = telemetryEvent => {
76
+ // Redact Azure SAS credentials in any URL embedded in the payload (e.g.
77
+ // customer-supplied blob asset URLs in widget customization props) before
78
+ // it fans out to console, Aria, AppInsights, or any custom logger.
79
+ const sanitizedPayload = sanitizeSasInPayload(telemetryEvent === null || telemetryEvent === void 0 ? void 0 : telemetryEvent.payload);
75
80
  loggers.map(logger => {
76
81
  var _payload, _telemetryInput$paylo;
77
82
  const logLevel = telemetryEvent.logLevel ?? LogLevel.INFO;
78
83
  const scenarioType = ((_payload = telemetryEvent.payload) === null || _payload === void 0 ? void 0 : _payload.scenarioType) ?? ScenarioType.UNDEFINED;
79
- const telemetryInput = parseInput(telemetryEvent === null || telemetryEvent === void 0 ? void 0 : telemetryEvent.payload, scenarioType);
84
+ const telemetryInput = parseInput(sanitizedPayload, scenarioType);
80
85
  telemetryInput.telemetryInfo = {
81
86
  telemetryInfo: TelemetryHelper.buildTelemetryEvent(logLevel, telemetryInput)
82
87
  };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Redacts the values of Azure Shared Access Signature (SAS) query parameters in any
3
+ * URL embedded in a telemetry payload, so customer-supplied SAS URLs (e.g. blob asset
4
+ * URLs in widget customization props) cannot leak credentials through Aria/AppInsights.
5
+ *
6
+ * Trigger: any URL whose query string contains `sig=`. When detected, every well-known
7
+ * SAS parameter value in that query is replaced with `[REDACTED]`. Other URLs and other
8
+ * substrings of the payload pass through unchanged.
9
+ *
10
+ * Coverage of SAS parameter names:
11
+ * - Service/Blob SAS: sig, sv, se, st, sp, spr, sr, sip
12
+ * - Account SAS: ss, srt
13
+ * - User-delegation key SAS: skoid, sktid, skt, ske, sks, skv, saoid, suoid, sce, sdd
14
+ * - Response header overrides: rscc, rscd, rsce, rscl, rsct
15
+ *
16
+ * Reference: https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
17
+ */
18
+
19
+ const SAS_PARAM_NAMES = new Set(["sig", "sv", "se", "st", "sp", "spr", "sr", "sip", "ss", "srt", "skoid", "sktid", "skt", "ske", "sks", "skv", "saoid", "suoid", "sce", "sdd", "rscc", "rscd", "rsce", "rscl", "rsct"]);
20
+ const REDACTED_VALUE = "[REDACTED]";
21
+
22
+ // Matches any http(s) URL substring up to whitespace / closing quote / angle-bracket / paren.
23
+ const URL_REGEX = /\bhttps?:\/\/[^\s"'<>)]+/gi;
24
+ const redactQueryIfSas = url => {
25
+ const queryIdx = url.indexOf("?");
26
+ if (queryIdx < 0) {
27
+ return url;
28
+ }
29
+ // Separate the fragment (#...) before processing the query — fragments come
30
+ // after the query string in URL syntax and must not be folded into the last
31
+ // query parameter's value when we split on `&`.
32
+ const afterQuestionMark = url.slice(queryIdx + 1);
33
+ const fragIdx = afterQuestionMark.indexOf("#");
34
+ const query = fragIdx < 0 ? afterQuestionMark : afterQuestionMark.slice(0, fragIdx);
35
+ const fragment = fragIdx < 0 ? "" : afterQuestionMark.slice(fragIdx); // includes the leading '#'
36
+ if (!/(^|&)sig=/i.test(query)) {
37
+ // No SAS signature present — leave the URL alone so legitimate non-SAS query
38
+ // strings (e.g. ?utm_source=...) are not mangled.
39
+ return url;
40
+ }
41
+ const base = url.slice(0, queryIdx);
42
+ const sanitizedQuery = query.split("&").map(pair => {
43
+ const eqIdx = pair.indexOf("=");
44
+ if (eqIdx < 0) {
45
+ return pair;
46
+ }
47
+ const name = pair.slice(0, eqIdx).toLowerCase();
48
+ return SAS_PARAM_NAMES.has(name) ? `${pair.slice(0, eqIdx)}=${REDACTED_VALUE}` : pair;
49
+ }).join("&");
50
+ return `${base}?${sanitizedQuery}${fragment}`;
51
+ };
52
+ const sanitizeString = s => s.replace(URL_REGEX, redactQueryIfSas);
53
+
54
+ /**
55
+ * Deep-walk a value and redact SAS credentials in any string it contains.
56
+ * Returns a new object/array (originals are not mutated); non-string leaves are
57
+ * passed through. Tree-shaped, JSON-like values only — not designed for class
58
+ * instances, prototype-bound objects, or cyclic graphs.
59
+ */
60
+ export const sanitizeSasInPayload = value => {
61
+ if (typeof value === "string") {
62
+ return sanitizeString(value);
63
+ }
64
+ if (Array.isArray(value)) {
65
+ return value.map(sanitizeSasInPayload);
66
+ }
67
+ if (value !== null && typeof value === "object") {
68
+ const out = {};
69
+ for (const [k, v] of Object.entries(value)) {
70
+ out[k] = sanitizeSasInPayload(v);
71
+ }
72
+ return out;
73
+ }
74
+ return value;
75
+ };
@@ -167,14 +167,16 @@ export const createMarkdown = (disableMarkdownMessageFormatting, disableNewLineM
167
167
 
168
168
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
169
169
  md.render = function (text, env) {
170
+ const safeEnv = env ?? {};
170
171
  const processedText = preprocessText(text);
171
- return md.renderer.render(md.parse(processedText, env), md.options, env);
172
+ return md.renderer.render(md.parse(processedText, safeEnv), md.options, safeEnv);
172
173
  };
173
174
 
174
175
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
176
  md.renderInline = function (text, env) {
177
+ const safeEnv = env ?? {};
176
178
  const processedText = preprocessText(text);
177
- return md.renderer.render(md.parseInline(processedText, env), md.options, env);
179
+ return md.renderer.render(md.parseInline(processedText, safeEnv), md.options, safeEnv);
178
180
  };
179
181
  });
180
182
 
@@ -299,24 +299,28 @@ export const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endCh
299
299
  const conversationId = (state === null || state === void 0 ? void 0 : (_state$domainStates2 = state.domainStates) === null || _state$domainStates2 === void 0 ? void 0 : (_state$domainStates2$ = _state$domainStates2.chatToken) === null || _state$domainStates2$ === void 0 ? void 0 : _state$domainStates2$.chatId) || "unknown";
300
300
  TelemetryHelper.logActionEvent(LogLevel.INFO, {
301
301
  Event: TelemetryEvent.HTMLSanitized,
302
- Description: "HTML content would be sanitized by stricter allowlist (monitor-only)",
303
- ElapsedTimeInMilliseconds: executionTimeMs,
304
- CustomProperties: {
305
- OrganizationId: orgId,
306
- ConversationId: conversationId,
307
- RemovedTags: uniqueTags.join(", "),
308
- RemovedAttributes: uniqueAttrs.join(", "),
309
- Phase: "Monitor"
310
- }
302
+ Description: JSON.stringify({
303
+ message: "HTML content would be sanitized by stricter allowlist (monitor-only)",
304
+ removedTags: uniqueTags,
305
+ removedAttributes: uniqueAttrs,
306
+ phase: "Monitor",
307
+ organizationId: orgId,
308
+ conversationId: conversationId
309
+ }),
310
+ ElapsedTimeInMilliseconds: executionTimeMs
311
311
  });
312
312
 
313
313
  // Log to console in development for debugging
314
314
  if (process.env.NODE_ENV === "development") {
315
- console.warn("[Monitor] Stricter HTML sanitization would remove:", {
315
+ console.warn("[Monitor] Stricter HTML sanitization telemetry:", {
316
+ description: JSON.parse(JSON.stringify({
317
+ message: "HTML content would be sanitized by stricter allowlist (monitor-only)",
318
+ removedTags: uniqueTags,
319
+ removedAttributes: uniqueAttrs,
320
+ phase: "Monitor"
321
+ })),
316
322
  orgId,
317
323
  conversationId,
318
- removedTags: uniqueTags,
319
- removedAttributes: uniqueAttrs,
320
324
  executionTimeMs
321
325
  });
322
326
  }
@@ -212,7 +212,11 @@ export const PreChatSurveyPaneStateful = props => {
212
212
  }
213
213
  }, []);
214
214
  return /*#__PURE__*/React.createElement("div", {
215
- onFocusCapture: announceFocusedElement
215
+ onFocusCapture: announceFocusedElement,
216
+ style: {
217
+ width: "100%",
218
+ height: "100%"
219
+ }
216
220
  }, /*#__PURE__*/React.createElement("div", {
217
221
  role: "status",
218
222
  "aria-live": "polite",
@@ -6,6 +6,5 @@ export const defaultGeneralPreChatSurveyPaneStyleProps = {
6
6
  borderColor: "#F1F1F1",
7
7
  overflowY: "auto",
8
8
  height: "inherit",
9
- width: "inherit",
10
- overscrollBehavior: "none"
9
+ width: "inherit"
11
10
  };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Redacts the values of Azure Shared Access Signature (SAS) query parameters in any
3
+ * URL embedded in a telemetry payload, so customer-supplied SAS URLs (e.g. blob asset
4
+ * URLs in widget customization props) cannot leak credentials through Aria/AppInsights.
5
+ *
6
+ * Trigger: any URL whose query string contains `sig=`. When detected, every well-known
7
+ * SAS parameter value in that query is replaced with `[REDACTED]`. Other URLs and other
8
+ * substrings of the payload pass through unchanged.
9
+ *
10
+ * Coverage of SAS parameter names:
11
+ * - Service/Blob SAS: sig, sv, se, st, sp, spr, sr, sip
12
+ * - Account SAS: ss, srt
13
+ * - User-delegation key SAS: skoid, sktid, skt, ske, sks, skv, saoid, suoid, sce, sdd
14
+ * - Response header overrides: rscc, rscd, rsce, rscl, rsct
15
+ *
16
+ * Reference: https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
17
+ */
18
+ /**
19
+ * Deep-walk a value and redact SAS credentials in any string it contains.
20
+ * Returns a new object/array (originals are not mutated); non-string leaves are
21
+ * passed through. Tree-shaped, JSON-like values only — not designed for class
22
+ * instances, prototype-bound objects, or cyclic graphs.
23
+ */
24
+ export declare const sanitizeSasInPayload: <T>(value: T) => T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/omnichannel-chat-widget",
3
- "version": "1.8.4-main.f45c44b",
3
+ "version": "1.8.4-main.f8ae4f8",
4
4
  "description": "Microsoft Omnichannel Chat Widget",
5
5
  "main": "lib/cjs/index.js",
6
6
  "types": "lib/types/index.d.ts",
@@ -95,8 +95,8 @@
95
95
  "dependencies": {
96
96
  "@azure/core-tracing": "^1.2.0",
97
97
  "@microsoft/applicationinsights-web": "^3.3.6",
98
- "@microsoft/omnichannel-chat-components": "1.1.17-main.f21df63",
99
- "@microsoft/omnichannel-chat-sdk": "1.11.9-main.a5570e5",
98
+ "@microsoft/omnichannel-chat-components": "1.1.17-main.05edfd3",
99
+ "@microsoft/omnichannel-chat-sdk": "1.11.9-main.47a6498",
100
100
  "@opentelemetry/api": "^1.9.0",
101
101
  "abort-controller": "^3",
102
102
  "abort-controller-es5": "^2.0.1",