All files / app/assets/javascripts/lib dompurify.js

100% Statements 39/39
100% Branches 21/21
100% Functions 12/12
100% Lines 36/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109      1521x   1521x                                       1521x 89x   112x   1521x 108x   1521x   1521x 230x 119x     111x 81x                     1521x 70x   70x 70x   70x                                 1521x 115x 115x     1521x 11231x 115x       1521x   1521x 20460x 71x       1521x 11231x 71x 71x 71x 70x 70x         5865x      
import DOMPurify from 'dompurify';
import { getNormalizedURL, getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
 
const { sanitize: dompurifySanitize, addHook, isValidAttribute } = DOMPurify;
 
export const defaultConfig = {
  // Safely allow SVG <use> tags
  ADD_TAGS: ['use', 'gl-emoji', 'copy-code'],
  // Prevent possible XSS attacks with data-* attributes used by @rails/ujs
  // See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1421
  FORBID_ATTR: [
    'data-remote',
    'data-url',
    'data-type',
    'data-method',
    'data-disable-with',
    'data-disabled',
    'data-disable',
    'data-turbo',
  ],
  FORBID_TAGS: ['style', 'mstyle', 'form'],
  ALLOW_UNKNOWN_PROTOCOLS: true,
};
 
// Only icons urls from `gon` are allowed
const getAllowedIconUrls = (gon = window.gon) =>
  [gon.sprite_file_icons, gon.sprite_icons]
    .filter(Boolean)
    .map((path) => relativePathToAbsolute(path, getBaseURL()));
 
const isUrlAllowed = (url) =>
  getAllowedIconUrls().some((allowedUrl) => getNormalizedURL(url).startsWith(allowedUrl));
 
const isHrefSafe = (url) => url.match(/^#/) || isUrlAllowed(url);
 
const removeUnsafeHref = (node, attr) => {
  if (!node.hasAttribute(attr)) {
    return;
  }
 
  if (!isHrefSafe(node.getAttribute(attr))) {
    node.removeAttribute(attr);
  }
};
 
/**
 * Appends 'noopener' & 'noreferrer' to rel
 * attr values to prevent reverse tabnabbing.
 *
 * @param {String} rel
 * @returns {String}
 */
const appendSecureRelValue = (rel) => {
  const attributes = new Set(rel ? rel.toLowerCase().split(' ') : []);
 
  attributes.add('noopener');
  attributes.add('noreferrer');
 
  return Array.from(attributes).join(' ');
};
 
/**
 * Sanitize icons' <use> tag attributes, to safely include
 * svgs such as in:
 *
 * <svg viewBox="0 0 100 100">
 *   <use href="/assets/icons-xxx.svg#icon_name"></use>
 * </svg>
 *
 * It validates both href & xlink:href attributes.
 * Note that `xlink:href` is deprecated, but still in use
 * https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
 *
 * @param {Object} node - Node to sanitize
 */
const sanitizeSvgIcon = (node) => {
  removeUnsafeHref(node, 'href');
  removeUnsafeHref(node, 'xlink:href');
};
 
addHook('afterSanitizeAttributes', (node) => {
  if (node.tagName.toLowerCase() === 'use') {
    sanitizeSvgIcon(node);
  }
});
 
const TEMPORARY_ATTRIBUTE = 'data-temp-href-target';
 
addHook('beforeSanitizeAttributes', (node) => {
  if (node.tagName === 'A' && node.hasAttribute('target')) {
    node.setAttribute(TEMPORARY_ATTRIBUTE, node.getAttribute('target'));
  }
});
 
addHook('afterSanitizeAttributes', (node) => {
  if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
    node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE));
    node.removeAttribute(TEMPORARY_ATTRIBUTE);
    if (node.getAttribute('target') === '_blank') {
      const rel = node.getAttribute('rel');
      node.setAttribute('rel', appendSecureRelValue(rel));
    }
  }
});
 
export const sanitize = (val, config) => dompurifySanitize(val, { ...defaultConfig, ...config });
 
export { isValidAttribute };