// Adversarial checks for MarkdownContent's allowHtml mode: the discover README
// renders HuggingFace model cards, which are third-party content, so every
// payload below must come out neutralized - both right after sanitize and
// after an innerHTML re-parse, which is where mutation XSS would surface.
import MarkdownContent from '$lib/components/app/content/MarkdownContent/MarkdownContent.svelte';
import { SAFE_HTML_CONFIG } from '$lib/components/app/content/MarkdownContent/safe-html-config';
import DOMPurify from 'dompurify';
import { mount, unmount } from 'svelte';
import { describe, expect, it } from 'vitest';
const HANDLER_ATTR = /\son[a-z]+\s*=/i;
const JS_URL = /javascript\s*:/i;
/** Payload execution canary: set by any payload that runs. */
function xssFired(): boolean {
return (window as { __mdXss?: number }).__mdXss !== undefined;
}
function roundTrip(html: string): string {
const el = document.createElement('div');
el.innerHTML = html;
document.body.appendChild(el);
const again = el.innerHTML;
el.remove();
return again;
}
const PAYLOADS: Record = {
'details ontoggle': 'x',
'dom clobbering': '',
'form and input': '',
'iframe javascript src': '',
'iframe srcdoc': '',
'img onerror': '',
'link case and entity href': 'x',
'link data:text/html href':
'x',
'link javascript href': 'x',
'link vbscript href': 'x',
'math href': '',
'mathml annotation-xml integration point':
'',
'mathml mtext style mXSS':
'',
noscript: '
',
SAFE_HTML_CONFIG
) as string;
expect(clean).toContain('href="https://example.com"');
expect(clean).toContain(' {
it('renders an allowHtml payload README without executing it', async () => {
const evil = [
'# Evil card',
'',
'',
'x',
'',
'',
'',
'',
'normal **markdown** continues'
].join('\n');
const target = document.createElement('div');
document.body.appendChild(target);
mount(MarkdownContent, { props: { allowHtml: true, content: evil }, target });
for (let i = 0; i < 100 && !target.textContent?.includes('normal'); i++) {
await new Promise((r) => setTimeout(r, 50));
}
await new Promise((r) => setTimeout(r, 200));
expect(target.textContent).toContain('normal');
expect(xssFired()).toBe(false);
expect(target.querySelector('script')).toBeNull();
expect(target.querySelector('iframe')).toBeNull();
target.remove();
});
it('escapes raw HTML to literal text in the default mode', async () => {
const target = document.createElement('div');
document.body.appendChild(target);
const component = mount(MarkdownContent, {
props: {
content: ''
},
target
});
for (let i = 0; i < 100 && !target.textContent?.includes('onerror'); i++) {
await new Promise((r) => setTimeout(r, 50));
}
await new Promise((r) => setTimeout(r, 200));
expect(xssFired()).toBe(false);
expect(target.querySelector('img[src="x"]')).toBeNull();
expect(target.textContent).toContain('onerror');
target.remove();
if (component) unmount(component);
});
});