Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dompurify.js « lib « javascripts « assets « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 6f24590f9e78ae880ebe5f37d5265c912c9075f1 (plain)
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
import DOMPurify from 'dompurify';
import { getNormalizedURL, getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';

const { sanitize: dompurifySanitize, addHook, isValidAttribute } = DOMPurify;

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'],
  FORBID_TAGS: ['style', 'mstyle'],
  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 };