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 }