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');
}
}