diff --git a/packages/grafana-data/src/text/sanitize.test.ts b/packages/grafana-data/src/text/sanitize.test.ts index c3ce3d3eb90..a0c69838976 100644 --- a/packages/grafana-data/src/text/sanitize.test.ts +++ b/packages/grafana-data/src/text/sanitize.test.ts @@ -55,6 +55,26 @@ describe('sanitize', () => { const str = sanitize(html); expect(str).toBe(''); }); + + describe('should sanitize anchors with target="_blank"', () => { + it('should add rel="noopener noreferrer" to target="_blank" links', () => { + const html = 'Link'; + const str = sanitize(html); + expect(str).toBe('Link'); + }); + + it('should preserve existing rel attributes and add noopener noreferrer, if not already added', () => { + const html = 'Link'; + const str = sanitize(html); + expect(str).toBe('Link'); + }); + + it('should not modify links without target="_blank"', () => { + const html = 'Link'; + const str = sanitize(html); + expect(str).toBe('Link'); + }); + }); }); describe('validatePath', () => { diff --git a/packages/grafana-data/src/text/sanitize.ts b/packages/grafana-data/src/text/sanitize.ts index 0c3dadd5ebf..4d22046cb31 100644 --- a/packages/grafana-data/src/text/sanitize.ts +++ b/packages/grafana-data/src/text/sanitize.ts @@ -56,13 +56,22 @@ const sanitizeTextPanelWhitelist = new xss.FilterXSS({ */ export function sanitize(unsanitizedString: string): string { try { + DOMPurify.addHook('afterSanitizeAttributes', (node) => { + if (node.tagName === 'A' && node.getAttribute('target') === '_blank') { + node.setAttribute('rel', 'noopener noreferrer'); + } + }); + return DOMPurify.sanitize(unsanitizedString, { USE_PROFILES: { html: true }, FORBID_TAGS: ['form', 'input'], + ADD_ATTR: ['target'], }); } catch (error) { console.error('String could not be sanitized', unsanitizedString); return escapeHtml(unsanitizedString); + } finally { + DOMPurify.removeHook('afterSanitizeAttributes'); } }