diff --git a/src/__tests__/i18n.test.js b/src/__tests__/i18n.test.js
index bfe299507..b5854f4ae 100644
--- a/src/__tests__/i18n.test.js
+++ b/src/__tests__/i18n.test.js
@@ -6,6 +6,7 @@ import esDictionary from '../i18n/es';
import * as sync from '../sync';
import * as l from '../core/index';
+import { initSanitizer } from '../sanitizer';
describe('i18n', () => {
let syncSpy;
@@ -61,5 +62,22 @@ describe('i18n', () => {
expect(html.props.dangerouslySetInnerHTML.__html).not.toMatch(/javascript:alert/);
expect(html.props.dangerouslySetInnerHTML.__html).toEqual('');
});
+
+ it('should allow target=_blank with noopener noreferrer attributes', () => {
+ initSanitizer();
+
+ const i18n = require('../i18n');
+
+ const strings = {
+ test: 'link'
+ };
+
+ const m = Immutable.fromJS({ i18n: { strings } });
+ const html = i18n.html(m, 'test');
+
+ expect(html.props.dangerouslySetInnerHTML.__html).toEqual(
+ 'link'
+ );
+ });
});
});
diff --git a/src/core.js b/src/core.js
index 86d8bfb6d..4265694fd 100644
--- a/src/core.js
+++ b/src/core.js
@@ -2,6 +2,8 @@ import { EventEmitter } from 'events';
import { getEntity, observe, read } from './store/index';
import { remove, render } from './ui/box';
import webAPI from './core/web_api';
+import { initSanitizer } from './sanitizer';
+
import {
closeLock,
resumeAuth,
@@ -62,10 +64,13 @@ export default class Base extends EventEmitter {
this.id = idu.incremental();
this.engine = engine;
+
const hookRunner = ::this.runHook;
const emitEventFn = this.emit.bind(this);
const handleEventFn = this.on.bind(this);
+
go(this.id);
+ initSanitizer();
let m = setupLock(this.id, clientID, domain, options, hookRunner, emitEventFn, handleEventFn);
diff --git a/src/sanitizer.js b/src/sanitizer.js
new file mode 100644
index 000000000..efb087254
--- /dev/null
+++ b/src/sanitizer.js
@@ -0,0 +1,22 @@
+import { addHook } from 'dompurify';
+
+export function initSanitizer() {
+ // Extracted from the example at
+ // https://github.com/cure53/DOMPurify/blob/main/demos/hooks-target-blank-demo.html
+ addHook('afterSanitizeAttributes', function(node) {
+ // set all elements owning target to target=_blank
+ if ('target' in node) {
+ node.setAttribute('target', '_blank');
+ // prevent https://www.owasp.org/index.php/Reverse_Tabnabbing
+ node.setAttribute('rel', 'noopener noreferrer');
+ }
+
+ // set non-HTML/MathML links to xlink:show=new
+ if (
+ !node.hasAttribute('target') &&
+ (node.hasAttribute('xlink:href') || node.hasAttribute('href'))
+ ) {
+ node.setAttribute('xlink:show', 'new');
+ }
+ });
+}