Skip to content

Avoid re-sanitizing markdown during rendering #259655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 21 additions & 14 deletions src/vs/base/browser/markdownRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,21 +147,28 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
renderedMarkdown = elements.map(e => typeof e === 'string' ? e : e.outerHTML).join('');
}

const htmlParser = new DOMParser();
const markdownHtmlDoc = htmlParser.parseFromString(sanitizeRenderedMarkdown(renderedMarkdown, markdown.isTrusted ?? false, options.sanitizerConfig) as unknown as string, 'text/html');
const renderedContent = document.createElement('div');
const sanitizerConfig = getDomSanitizerConfig(markdown.isTrusted ?? false, options.sanitizerConfig ?? {});
domSanitize.safeSetInnerHtml(renderedContent, renderedMarkdown, sanitizerConfig);

rewriteRenderedLinks(markdown, options, markdownHtmlDoc.body);
// Rewrite links and images before potentially inserting them into the real dom
rewriteRenderedLinks(markdown, options, renderedContent);

const element = target ?? document.createElement('div');
element.innerHTML = sanitizeRenderedMarkdown(markdownHtmlDoc.body.innerHTML, markdown.isTrusted ?? false, options.sanitizerConfig) as unknown as string;
let outElement: HTMLElement;
if (target) {
outElement = target;
DOM.reset(target, ...renderedContent.children);
} else {
outElement = renderedContent;
}

if (codeBlocks.length > 0) {
Promise.all(codeBlocks).then((tuples) => {
if (isDisposed) {
return;
}
const renderedElements = new Map(tuples);
const placeholderElements = element.querySelectorAll<HTMLDivElement>(`div[data-code]`);
const placeholderElements = outElement.querySelectorAll<HTMLDivElement>(`div[data-code]`);
for (const placeholderElement of placeholderElements) {
const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? '');
if (renderedElement) {
Expand All @@ -172,7 +179,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
});
} else if (syncCodeBlocks.length > 0) {
const renderedElements = new Map(syncCodeBlocks);
const placeholderElements = element.querySelectorAll<HTMLDivElement>(`div[data-code]`);
const placeholderElements = outElement.querySelectorAll<HTMLDivElement>(`div[data-code]`);
for (const placeholderElement of placeholderElements) {
const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? '');
if (renderedElement) {
Expand All @@ -183,7 +190,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende

// Signal size changes for image tags
if (options.asyncRenderCallback) {
for (const img of element.getElementsByTagName('img')) {
for (const img of outElement.getElementsByTagName('img')) {
const listener = disposables.add(DOM.addDisposableListener(img, 'load', () => {
listener.dispose();
options.asyncRenderCallback!();
Expand All @@ -193,17 +200,17 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende

// Add event listeners for links
if (options.actionHandler) {
const onClick = options.actionHandler.disposables.add(new DomEmitter(element, 'click'));
const onAuxClick = options.actionHandler.disposables.add(new DomEmitter(element, 'auxclick'));
const onClick = options.actionHandler.disposables.add(new DomEmitter(outElement, 'click'));
const onAuxClick = options.actionHandler.disposables.add(new DomEmitter(outElement, 'auxclick'));
options.actionHandler.disposables.add(Event.any(onClick.event, onAuxClick.event)(e => {
const mouseEvent = new StandardMouseEvent(DOM.getWindow(element), e);
const mouseEvent = new StandardMouseEvent(DOM.getWindow(outElement), e);
if (!mouseEvent.leftButton && !mouseEvent.middleButton) {
return;
}
activateLink(markdown, options, mouseEvent);
}));

options.actionHandler.disposables.add(DOM.addDisposableListener(element, 'keydown', (e) => {
options.actionHandler.disposables.add(DOM.addDisposableListener(outElement, 'keydown', (e) => {
const keyboardEvent = new StandardKeyboardEvent(e);
if (!keyboardEvent.equals(KeyCode.Space) && !keyboardEvent.equals(KeyCode.Enter)) {
return;
Expand All @@ -213,7 +220,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
}

// Remove/disable inputs
for (const input of [...element.getElementsByTagName('input')]) {
for (const input of [...outElement.getElementsByTagName('input')]) {
if (input.attributes.getNamedItem('type')?.value === 'checkbox') {
input.setAttribute('disabled', '');
} else {
Expand All @@ -227,7 +234,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
}

return {
element,
element: outElement,
dispose: () => {
isDisposed = true;
disposables.dispose();
Expand Down
6 changes: 3 additions & 3 deletions src/vs/base/test/browser/markdownRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ suite('MarkdownRenderer', () => {
mds.appendMarkdown(`[$(zap)-link](#link)`);

const result: HTMLElement = store.add(renderMarkdown(mds)).element;
assert.strictEqual(result.innerHTML, `<p><a data-href="#link" href="" title="#link" draggable="false"><span class="codicon codicon-zap"></span>-link</a></p>`);
assert.strictEqual(result.innerHTML, `<p><a draggable="false" title="#link" href="" data-href="#link"><span class="codicon codicon-zap"></span>-link</a></p>`);
});

test('render icon in table', () => {
Expand All @@ -186,7 +186,7 @@ suite('MarkdownRenderer', () => {
</thead>
<tbody><tr>
<td><span class="codicon codicon-zap"></span></td>
<td><a data-href="#link" href="" title="#link" draggable="false"><span class="codicon codicon-zap"></span>-link</a></td>
<td><a draggable="false" title="#link" href="" data-href="#link"><span class="codicon codicon-zap"></span>-link</a></td>
</tr>
</tbody></table>
`);
Expand Down Expand Up @@ -253,7 +253,7 @@ suite('MarkdownRenderer', () => {
});

const result: HTMLElement = store.add(renderMarkdown(md)).element;
assert.strictEqual(result.innerHTML, `<p><a data-href="command:doFoo" href="" title="command:doFoo" draggable="false">command1</a> <a data-href="command:doFoo" href="">command2</a></p>`);
assert.strictEqual(result.innerHTML, `<p><a draggable="false" title="command:doFoo" href="" data-href="command:doFoo">command1</a> <a href="" data-href="command:doFoo">command2</a></p>`);
});

suite('PlaintextMarkdownRender', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<div class="rendered-markdown"><p>&lt;!--[CDATA[&lt;div--&gt;content]]&gt;</p></div>
<div class="rendered-markdown"><p>

&lt;!--[CDATA[&lt;div--&gt;content]]&gt;</p></div>
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<div class="rendered-markdown">&lt;!-- comment1 &lt;div&gt;&lt;/div&gt; --&gt;<div>content</div><p>&lt;!-- comment2 --&gt;</p></div>
<div class="rendered-markdown">

&lt;!-- comment1 &lt;div&gt;&lt;/div&gt; --&gt;<div>content</div><p>&lt;!-- comment2 --&gt;</p></div>
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<div class="rendered-markdown"><p>1&lt;canvas&gt;2&lt;/canvas&gt;</p>&lt;details&gt;3&lt;/details&gt;4<p></p></div>
<div class="rendered-markdown">

<p>1&lt;canvas&gt;2&lt;/canvas&gt;</p>&lt;details&gt;3&lt;/details&gt;4<p></p></div>
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<div class="rendered-markdown"><p>1</p>&lt;details id="id1" style="display: none"&gt;2&lt;details id="my id 2"&gt;3&lt;/details&gt;&lt;/details&gt;4<p></p></div>
<div class="rendered-markdown">

<p>1</p>&lt;details id="id1" style="display: none"&gt;2&lt;details id="my id 2"&gt;3&lt;/details&gt;&lt;/details&gt;4<p></p></div>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<div class="rendered-markdown"><h1>heading</h1>
<div class="rendered-markdown">


<h1>heading</h1>
&lt;details&gt;
<ul>
<li><span>&lt;details&gt;<i>1</i>&lt;/details&gt;</span></li>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<div class="rendered-markdown"><p></p><div>&lt;img src="http://disallowed.com/image.jpg"&gt;</div><p></p></div>
<div class="rendered-markdown">

<p><div>&lt;img src="http://disallowed.com/image.jpg"&gt;</div></p></div>
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<div class="rendered-markdown"><p>&lt;area&gt;</p><hr><br>&lt;input type="text" value="test"&gt;<p></p></div>
<div class="rendered-markdown">

<p>&lt;area&gt;</p><hr><br>&lt;input value="test" type="text"&gt;<p></p></div>
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<div class="rendered-markdown"><p>&lt;area&gt;</p><hr><br><input type="checkbox" disabled=""><p></p></div>
<div class="rendered-markdown">

<p>&lt;area&gt;</p><hr><br><input type="checkbox" disabled=""><p></p></div>
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<div class="rendered-markdown"><p><strong>hello</strong></p></div>
<div class="rendered-markdown">

<p><strong>hello</strong></p></div>
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<div class="rendered-markdown"><ol>
<li><a data-href="https://example.com" href="" title="" draggable="false"><em>hello</em></a> test <strong>text</strong></li>
<div class="rendered-markdown">

<ol>
<li><a draggable="false" title="" href="" data-href="https://example.com"><em>hello</em></a> test <strong>text</strong></li>
</ol>
</div>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<div class="rendered-markdown"><h1>heading</h1>
<div class="rendered-markdown">


<h1>heading</h1>
<ul>
<li>1</li>
<li><b>hi</b></li>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<p>Hello <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation>\frac{1}{2}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 1.1901em; vertical-align: -0.345em"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.8451em"><span style="top: -2.655em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top: -3.23em"><span class="pstrut" style="height: 3em"></span><span class="frac-line" style="border-bottom-width: 0.04em"></span></span><span style="top: -3.394em"><span class="pstrut" style="height: 3em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.345em"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> World!</p>
<p>Hello <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow><annotation>\frac{1}{2}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span style="height: 1.1901em; vertical-align: -0.345em" class="strut"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height: 0.8451em" class="vlist"><span style="top: -2.655em"><span style="height: 3em" class="pstrut"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top: -3.23em"><span style="height: 3em" class="pstrut"></span><span style="border-bottom-width: 0.04em" class="frac-line"></span></span><span style="top: -3.394em"><span style="height: 3em" class="pstrut"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span style="height: 0.345em" class="vlist"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> World!</p>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<p>Block example:</p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><msubsup><mo>∫</mo><mrow><mo>−</mo><mi>∞</mi></mrow><mi>∞</mi></msubsup><msup><mi>e</mi><mrow><mo>−</mo><msup><mi>x</mi><mn>2</mn></msup></mrow></msup><mi>d</mi><mi>x</mi><mo>=</mo><msqrt><mi>π</mi></msqrt></mrow><annotation>\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span class="strut" style="height: 2.3846em; vertical-align: -0.9703em"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right: 0.44445em; position: relative; top: -0.0011em">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 1.4143em"><span style="top: -1.7881em; margin-left: -0.4445em; margin-right: 0.05em"><span class="pstrut" style="height: 2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">∞</span></span></span></span><span style="top: -3.8129em; margin-right: 0.05em"><span class="pstrut" style="height: 2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∞</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.9703em"><span></span></span></span></span></span></span><span class="mspace" style="margin-right: 0.1667em"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 1.0369em"><span style="top: -3.113em; margin-right: 0.05em"><span class="pstrut" style="height: 2.7em"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8913em"><span style="top: -2.931em; margin-right: 0.0714em"><span class="pstrut" style="height: 2.5em"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="mord mathnormal">d</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right: 0.2778em"></span><span class="mrel">=</span><span class="mspace" style="margin-right: 0.2778em"></span></span><span class="base"><span class="strut" style="height: 1.04em; vertical-align: -0.1908em"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.8492em"><span class="svg-align" style="top: -3em"><span class="pstrut" style="height: 3em"></span><span class="mord" style="padding-left: 0.833em"><span class="mord mathnormal" style="margin-right: 0.03588em">π</span></span></span><span style="top: -2.8092em"><span class="pstrut" style="height: 3em"></span><span class="hide-tail" style="min-width: 0.853em; height: 1.08em"><svg width="400em" height="1.08em"><path></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.1908em"><span></span></span></span></span></span></span></span></span></span>
<p>Block example:</p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><msubsup><mo>∫</mo><mrow><mo>−</mo><mi>∞</mi></mrow><mi>∞</mi></msubsup><msup><mi>e</mi><mrow><mo>−</mo><msup><mi>x</mi><mn>2</mn></msup></mrow></msup><mi>d</mi><mi>x</mi><mo>=</mo><msqrt><mi>π</mi></msqrt></mrow><annotation>\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}</annotation></semantics></math></span><span class="katex-html"><span class="base"><span style="height: 2.3846em; vertical-align: -0.9703em" class="strut"></span><span class="mop"><span style="margin-right: 0.44445em; position: relative; top: -0.0011em" class="mop op-symbol large-op">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height: 1.4143em" class="vlist"><span style="top: -1.7881em; margin-left: -0.4445em; margin-right: 0.05em"><span style="height: 2.7em" class="pstrut"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">∞</span></span></span></span><span style="top: -3.8129em; margin-right: 0.05em"><span style="height: 2.7em" class="pstrut"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∞</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span style="height: 0.9703em" class="vlist"><span></span></span></span></span></span></span><span style="margin-right: 0.1667em" class="mspace"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span style="height: 1.0369em" class="vlist"><span style="top: -3.113em; margin-right: 0.05em"><span style="height: 2.7em" class="pstrut"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span style="height: 0.8913em" class="vlist"><span style="top: -2.931em; margin-right: 0.0714em"><span style="height: 2.5em" class="pstrut"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="mord mathnormal">d</span><span class="mord mathnormal">x</span><span style="margin-right: 0.2778em" class="mspace"></span><span class="mrel">=</span><span style="margin-right: 0.2778em" class="mspace"></span></span><span class="base"><span style="height: 1.04em; vertical-align: -0.1908em" class="strut"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height: 0.8492em" class="vlist"><span style="top: -3em" class="svg-align"><span style="height: 3em" class="pstrut"></span><span style="padding-left: 0.833em" class="mord"><span style="margin-right: 0.03588em" class="mord mathnormal">π</span></span></span><span style="top: -2.8092em"><span style="height: 3em" class="pstrut"></span><span style="min-width: 0.853em; height: 1.08em" class="hide-tail"><svg height="1.08em" width="400em"><path></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span style="height: 0.1908em" class="vlist"><span></span></span></span></span></span></span></span></span></span>
Loading
Loading