{"version":3,"file":"sanitize.cjs","sources":["../../../src/text/sanitize.ts"],"sourcesContent":["import { sanitizeUrl as braintreeSanitizeUrl } from '@braintree/sanitize-url';\nimport DOMPurify from 'dompurify';\nimport * as xss from 'xss';\n\nconst XSSWL = Object.keys(xss.whiteList).reduce<xss.IWhiteList>((acc, element) => {\n  acc[element] = xss.whiteList[element]?.concat(['class', 'style']);\n  return acc;\n}, {});\n\n// Add iframe tags to XSSWL.\n// We don't allow the sandbox attribute, since it can be overridden, instead we add it below.\nXSSWL.iframe = ['src', 'width', 'height'];\n\nconst sanitizeTextPanelWhitelist = new xss.FilterXSS({\n  // Add sandbox attribute to iframe tags if an attribute is allowed.\n  onTagAttr(tag, name, value, isWhiteAttr) {\n    if (tag === 'iframe') {\n      return isWhiteAttr\n        ? ` ${name}=\"${xss.escapeAttrValue(sanitizeUrl(value))}\" sandbox credentialless referrerpolicy=no-referrer`\n        : '';\n    }\n    return;\n  },\n  onTag(tag, html, options) {\n    if (html === '<input disabled=\"\" type=\"checkbox\">' || html === '<input checked=\"\" disabled=\"\" type=\"checkbox\">') {\n      return html;\n    }\n    return;\n  },\n  whiteList: XSSWL,\n  css: {\n    whiteList: {\n      ...xss.getDefaultCSSWhiteList(),\n      'flex-direction': true,\n      'flex-wrap': true,\n      'flex-basis': true,\n      'flex-grow': true,\n      'flex-shrink': true,\n      'flex-flow': true,\n      gap: true,\n      order: true,\n      'justify-content': true,\n      'justify-items': true,\n      'justify-self': true,\n      'align-items': true,\n      'align-content': true,\n      'align-self': true,\n    },\n  },\n});\n\n/**\n * Return a sanitized string that is going to be rendered in the browser to prevent XSS attacks.\n * Note that sanitized tags will be removed, such as \"<script>\".\n * We don't allow form or input elements.\n */\nexport function sanitize(unsanitizedString: string): string {\n  try {\n    DOMPurify.addHook('afterSanitizeAttributes', (node) => {\n      if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {\n        node.setAttribute('rel', 'noopener noreferrer');\n      }\n    });\n\n    return DOMPurify.sanitize(unsanitizedString, {\n      USE_PROFILES: { html: true },\n      FORBID_TAGS: ['form', 'input'],\n      ADD_ATTR: ['target'],\n    });\n  } catch (error) {\n    console.error('String could not be sanitized', unsanitizedString);\n    return escapeHtml(unsanitizedString);\n  } finally {\n    DOMPurify.removeHook('afterSanitizeAttributes');\n  }\n}\n\nexport function sanitizeTrustedTypesRSS(unsanitizedString: string): TrustedHTML {\n  return DOMPurify.sanitize(unsanitizedString, {\n    RETURN_TRUSTED_TYPE: true,\n    ADD_ATTR: ['xmlns:atom', 'version', 'property', 'content'],\n    ADD_TAGS: ['rss', 'meta', 'channel', 'title', 'link', 'description', 'atom:link', 'item', 'pubDate', 'guid'],\n    PARSER_MEDIA_TYPE: 'application/xhtml+xml',\n  });\n}\n\nexport function sanitizeTrustedTypes(unsanitizedString: string): TrustedHTML {\n  return DOMPurify.sanitize(unsanitizedString, { RETURN_TRUSTED_TYPE: true });\n}\n\n/**\n * Returns string safe from XSS attacks to be used in the Text panel plugin.\n *\n * Even though we allow the style-attribute, there's still default filtering applied to it\n * Info: https://github.com/leizongmin/js-xss#customize-css-filter\n * Whitelist: https://github.com/leizongmin/js-css-filter/blob/master/lib/default.js\n */\nexport function sanitizeTextPanelContent(unsanitizedString: string): string {\n  try {\n    return sanitizeTextPanelWhitelist.process(unsanitizedString);\n  } catch (error) {\n    console.error('String could not be sanitized', unsanitizedString);\n    return 'Text string could not be sanitized';\n  }\n}\n\n// Returns sanitized SVG, free from XSS attacks to be used when rendering SVG content.\nexport function sanitizeSVGContent(unsanitizedString: string): string {\n  return DOMPurify.sanitize(unsanitizedString, { USE_PROFILES: { svg: true, svgFilters: true } });\n}\n\n// Return a sanitized URL, free from XSS attacks, such as javascript:alert(1)\nexport function sanitizeUrl(url: string): string {\n  return braintreeSanitizeUrl(url);\n}\n\n// Returns true if the string contains ANSI color codes.\nexport function hasAnsiCodes(input: string): boolean {\n  return /\\u001b\\[\\d{1,2}m/.test(input);\n}\n\n// Returns a string with HTML entities escaped.\nexport function escapeHtml(str: string): string {\n  return String(str)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/'/g, '&#39;')\n    .replace(/\"/g, '&quot;');\n}\n\nexport class PathValidationError extends Error {\n  constructor(message = 'Invalid request path') {\n    super(message);\n    this.name = 'PathValidationError';\n    // Maintains proper stack trace for where error was thrown (only available on V8)\n    if (Error.captureStackTrace) {\n      Error.captureStackTrace(this, PathValidationError);\n    }\n  }\n}\n\n/**\n * Validates a path or URL, protecting against path traversal attacks.\n * Returns the original input if safe, or throw an error\n */\nexport function validatePath<OriginalPath extends string>(path: OriginalPath): OriginalPath {\n  try {\n    let decoded: string = path;\n    while (true) {\n      const nextDecode = decodeURIComponent(decoded);\n      if (nextDecode === decoded) {\n        break; // String is fully decoded.\n      }\n      decoded = nextDecode;\n    }\n\n    // Validate the entire decoded string for traversal attempts\n    // This prevents attacks that use query separators to hide traversal payloads\n    if (/\\.\\.|\\/\\\\|[\\t\\n\\r]/.test(decoded)) {\n      throw new PathValidationError();\n    }\n\n    // Return the original path (not the decoded version) to preserve the full URL\n    return path;\n  } catch (err) {\n    // Rethrow the original PathValidationError to preserve the stack trace\n    if (err instanceof PathValidationError) {\n      throw err;\n    }\n\n    // A decoding error can happen with malformed URIs (e.g., % not followed by hex).\n    // These are suspicious, so we treat them as traversal attempts.\n    throw new PathValidationError('Error validating request path');\n  }\n}\n\nexport const textUtil = {\n  escapeHtml,\n  hasAnsiCodes,\n  sanitize,\n  sanitizeTextPanelContent,\n  sanitizeUrl,\n  sanitizeSVGContent,\n  sanitizeTrustedTypes,\n  sanitizeTrustedTypesRSS,\n};\n"],"names":["xss","DOMPurify","braintreeSanitizeUrl"],"mappings":"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"}