diff --git a/.changeset/fix-remove-rel-target-outgoing-links.md b/.changeset/fix-remove-rel-target-outgoing-links.md
new file mode 100644
index 000000000..c76cef9ba
--- /dev/null
+++ b/.changeset/fix-remove-rel-target-outgoing-links.md
@@ -0,0 +1,5 @@
+---
+default: patch
+---
+
+Remove target and rel attributes from outgoing html links.
diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts
index 0a06a2330..d8be87e30 100644
--- a/src/app/components/editor/output.ts
+++ b/src/app/components/editor/output.ts
@@ -82,7 +82,7 @@ const elementToCustomHtml = (
case BlockType.Link:
return testMatrixTo(node.href)
? sanitizeText(node.href)
- : `${children}`;
+ : `${children}`;
case BlockType.Command:
return `/${sanitizeText(node.command)}`;
default:
diff --git a/src/app/plugins/markdown/markdownToHtml.test.ts b/src/app/plugins/markdown/markdownToHtml.test.ts
index f6599622a..6ed37ae0f 100644
--- a/src/app/plugins/markdown/markdownToHtml.test.ts
+++ b/src/app/plugins/markdown/markdownToHtml.test.ts
@@ -35,6 +35,8 @@ describe('markdownToHtml', () => {
it('converts links', () => {
const result = markdownToHtml('[link](https://example.com)');
expect(result).toContain(' {
diff --git a/src/app/plugins/markdown/markdownToHtml.ts b/src/app/plugins/markdown/markdownToHtml.ts
index c7761859e..89e09e0bd 100644
--- a/src/app/plugins/markdown/markdownToHtml.ts
+++ b/src/app/plugins/markdown/markdownToHtml.ts
@@ -154,12 +154,8 @@ export function markdownToHtml(markdown: string, options?: MarkdownToHtmlOptions
const allowlistedHtml = escapeNonAllowlistedHtmlTags(unescapedInline);
- // Force all links to open in a new tab, validate .
+ // Validate after sanitization.
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
- if (node.tagName === 'A' && node.getAttribute('href')) {
- node.setAttribute('target', '_blank');
- node.setAttribute('rel', 'noreferrer noopener');
- }
if (node.tagName === 'OL') {
const start = node.getAttribute('start');
if (start !== null && !ORDERED_LIST_START_REGEX.test(start)) {
@@ -177,8 +173,6 @@ export function markdownToHtml(markdown: string, options?: MarkdownToHtmlOptions
'title',
'height',
'width',
- 'target',
- 'rel',
'data-mx-emoticon',
'data-mx-spoiler',
'data-mx-maths',
@@ -194,9 +188,8 @@ export function markdownToHtml(markdown: string, options?: MarkdownToHtmlOptions
// Ensure these safe attrs survive sanitization even when the input HTML
// originates from markdown-embedded tags (e.g. custom emoji
).
// `start` must be URI-safe or DOMPurify drops it when ALLOWED_URI_REGEXP is set.
- ADD_ATTR: ['target', 'rel', 'height', 'width'],
+ ADD_ATTR: ['height', 'width'],
ADD_URI_SAFE_ATTR: ['start'],
- // Force all links to have safe rel attribute
FORCE_BODY: false,
ALLOWED_URI_REGEXP: /^(?:https?|ftp|mailto|magnet|mxc):/i,
});
diff --git a/src/app/utils/sanitize.test.ts b/src/app/utils/sanitize.test.ts
index b4839f926..6df18dbb1 100644
--- a/src/app/utils/sanitize.test.ts
+++ b/src/app/utils/sanitize.test.ts
@@ -53,8 +53,8 @@ describe('sanitizeCustomHtml', () => {
expect(result).toContain('data-mx-maths="x"');
expect(result).toContain('data-md="**"');
expect(result).toContain('href="https://example.com"');
- expect(result).toContain('target="_blank"');
expect(result).toContain('data-md="[]()"');
+ expect(result).not.toContain('target=');
expect(result).not.toContain('rel=');
expect(result).toContain('');
expect(result).not.toContain('type=');
diff --git a/src/app/utils/sanitize.ts b/src/app/utils/sanitize.ts
index 0e56d550c..3b21ca39b 100644
--- a/src/app/utils/sanitize.ts
+++ b/src/app/utils/sanitize.ts
@@ -46,7 +46,7 @@ const permittedHtmlTags = [
const permittedTagToAttributes = {
span: ['data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'data-mx-maths', 'data-md'],
- a: ['target', 'href', 'data-md'],
+ a: ['href', 'data-md'],
img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'], // data-mx-emoticon is for MSC2545
ol: ['start', 'data-md'],
ul: ['data-md'],