go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/tools/sanitize_html/sanitize_html.ts (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import createDomPurify from 'dompurify';
    16  import { trustedTypes } from 'trusted-types';
    17  
    18  const domPurify = createDomPurify(window);
    19  
    20  // Mitigate target="_blank" vulnerability.
    21  domPurify.addHook('afterSanitizeAttributes', (node) => {
    22    if (!['A', 'FORM', 'AREA'].includes(node.tagName)) {
    23      return;
    24    }
    25  
    26    // Note: rel="noopener" is added when the target is not set because <base> can
    27    // set the default target to _blank.
    28    if (
    29      ['_self', '_top', '_parent'].includes(node.getAttribute('target') || '')
    30    ) {
    31      return;
    32    }
    33  
    34    const existingRef = node.getAttribute('rel') || '';
    35    if (!/\bnoopener\b/i.test(existingRef)) {
    36      node.setAttribute('rel', (existingRef + ' noopener').trim());
    37    }
    38  });
    39  
    40  /**
    41   * Sanitizes the input HTML string.
    42   */
    43  export function sanitizeHTML(html: string): string;
    44  export function sanitizeHTML(
    45    html: string,
    46    opts: { RETURN_TRUSTED_TYPE: true },
    47  ): TrustedHTML;
    48  export function sanitizeHTML(
    49    html: string,
    50    opts?: { RETURN_TRUSTED_TYPE: true },
    51  ): string | TrustedHTML {
    52    return domPurify.sanitize(html, {
    53      ADD_ATTR: ['target', 'artifact-id', 'inv-level'],
    54      ADD_TAGS: ['text-artifact'],
    55      RETURN_TRUSTED_TYPE: opts?.RETURN_TRUSTED_TYPE,
    56    });
    57  }
    58  
    59  /**
    60   * Initialize a default trusted types policy.
    61   *
    62   * IMPORTANT:
    63   * The default policy may fail to sanitize the HTML when the correct CSP (
    64   * content security policy) is not set via a HTTP header or HTML meta tag. Given
    65   * that the code for setting the CSP is often located far away from where the
    66   * unsanitized HTMLs are used, it's hard to notice when the default policy
    67   * failed to be applied automatically. To avoid this issue, HTML should still be
    68   * sanitized before being injected to DOM (e.g. via React's
    69   * `dangerouslySetInnerHTML`, or setting `ele.innerHTML`) even when the
    70   * default trusted types policy is used.
    71   */
    72  export function initDefaultTrustedTypesPolicy() {
    73    if (!window.trustedTypes || !window.trustedTypes.createPolicy) {
    74      window.trustedTypes = trustedTypes;
    75    }
    76  
    77    window.trustedTypes!.createPolicy('default', {
    78      createHTML: sanitizeHTML,
    79    });
    80  }