fix(ui): sanitize markdown before chat DOM insertion

This commit is contained in:
William Valentin
2026-02-15 21:44:32 -08:00
parent 157e99ccb5
commit 22959ea3aa
5 changed files with 183 additions and 16 deletions
+51 -2
View File
@@ -42,6 +42,55 @@
let pendingAttachments = [];
function sanitizeHtml(html) {
const root = document.createElement('div');
root.innerHTML = html;
const blockedTags = new Set([
'script', 'style', 'iframe', 'object', 'embed', 'link', 'meta', 'base',
'form', 'input', 'button', 'textarea', 'select',
]);
const nodes = root.querySelectorAll('*');
for (const el of nodes) {
const tag = el.tagName.toLowerCase();
if (blockedTags.has(tag)) {
el.remove();
continue;
}
for (const attr of Array.from(el.attributes)) {
const name = attr.name.toLowerCase();
const value = attr.value.trim();
if (name.startsWith('on') || name === 'style') {
el.removeAttribute(attr.name);
continue;
}
if (name === 'href' || name === 'src' || name === 'xlink:href') {
const normalized = value.replace(/[\u0000-\u001F\u007F\s]+/g, '').toLowerCase();
if (
normalized.startsWith('javascript:')
|| normalized.startsWith('vbscript:')
|| normalized.startsWith('data:text/html')
) {
el.removeAttribute(attr.name);
}
}
}
if (tag === 'a' && el.getAttribute('target') === '_blank') {
el.setAttribute('rel', 'noopener noreferrer');
}
}
return root.innerHTML;
}
function renderSafeMarkdown(text) {
const html = marked.parse(String(text ?? ''));
return sanitizeHtml(html);
}
function isSupportedImageMime(mimeType) {
return mimeType === 'image/jpeg'
|| mimeType === 'image/png'
@@ -280,7 +329,7 @@
// Render final response as markdown inside the assistant area
const responseDiv = document.createElement('div');
responseDiv.className = 'message assistant';
responseDiv.innerHTML = marked.parse(data.content || '');
responseDiv.innerHTML = renderSafeMarkdown(data.content || '');
area.appendChild(responseDiv);
}
@@ -326,7 +375,7 @@
function appendAssistantMessage(content) {
const div = document.createElement('div');
div.className = 'message assistant';
div.innerHTML = marked.parse(content);
div.innerHTML = renderSafeMarkdown(content);
messagesEl.appendChild(div);
scrollToBottom();
}