Skip to content

Commit 392121a

Browse files
committed
Fix potential XSS in Markdown rendering via Event Delegation
1 parent fa2ccf4 commit 392121a

1 file changed

Lines changed: 26 additions & 6 deletions

File tree

ui/frontend/main.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,7 +2029,7 @@ function renderCodeBlock(code, lang) {
20292029
const blockId = 'code-' + Math.random().toString(36).substr(2, 9);
20302030

20312031
return `<div class="code-block-wrapper">
2032-
<button class="code-block-copy" data-code-id="${blockId}" onclick="copyCodeBlock(this)" title="Copy code">
2032+
<button class="code-block-copy" data-code-id="${blockId}" title="Copy code">
20332033
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
20342034
<span class="copy-text">Copy</span>
20352035
</button>
@@ -2509,7 +2509,7 @@ function renderMarkdown(text, { allowCodeBlock = true, unwrapLanguages = [] } =
25092509
const rawHtml = window.marked.parse(protectedText, { breaks: true, gfm: true, renderer: renderer });
25102510
let sanitized = window.DOMPurify ? DOMPurify.sanitize(rawHtml, {
25112511
ADD_TAGS: ['button', 'svg', 'path', 'rect', 'line', 'polyline', 'circle', 'polygon'],
2512-
ADD_ATTR: ['onclick', 'data-code-id', 'viewBox', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'd', 'x', 'y', 'width', 'height', 'rx', 'ry', 'cx', 'cy', 'r', 'x1', 'y1', 'x2', 'y2', 'points']
2512+
ADD_ATTR: ['data-code-id', 'data-ref-id', 'data-msg-idx', 'viewBox', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'd', 'x', 'y', 'width', 'height', 'rx', 'ry', 'cx', 'cy', 'r', 'x1', 'y1', 'x2', 'y2', 'points']
25132513
}) : rawHtml;
25142514

25152515
// Restore original LaTeX (with $$ delimiters intact)
@@ -3482,12 +3482,9 @@ function renumberCitations(text, sources) {
34823482

34833483
function formatCitationHtml(html, messageIdx = null) {
34843484
if (!html) return "";
3485-
// Add onclick="scrollToReference(1, messageIdx)"
3486-
// Note: scrollToReference function must be mounted to window or defined in global scope
3487-
// messageIdx is used to locate specific message bubble, avoiding confusion with multiple message citations
34883485
return html.replace(
34893486
/\[(\d+)\]/g,
3490-
(match, p1) => `<span class="citation-link" onclick="scrollToReference(${p1}, ${messageIdx})">[${p1}]</span>`
3487+
(match, p1) => `<span class="citation-link" data-ref-id="${p1}" data-msg-idx="${messageIdx || ''}">[${p1}]</span>`
34913488
);
34923489
}
34933490

@@ -10128,3 +10125,26 @@ async function applyParameterModification(action) {
1012810125

1012910126
return true;
1013010127
}
10128+
10129+
// --- Global Event Delegation ---
10130+
document.addEventListener('click', function(e) {
10131+
// 1. Handle Copy Code Button
10132+
const copyBtn = e.target.closest('.code-block-copy');
10133+
if (copyBtn) {
10134+
if (typeof copyCodeBlock === 'function') {
10135+
copyCodeBlock(copyBtn);
10136+
}
10137+
return;
10138+
}
10139+
10140+
// 2. Handle Citation Link
10141+
const citationLink = e.target.closest('.citation-link');
10142+
if (citationLink) {
10143+
const refId = citationLink.dataset.refId;
10144+
const msgIdx = citationLink.dataset.msgIdx;
10145+
if (refId && typeof window.scrollToReference === 'function') {
10146+
window.scrollToReference(parseInt(refId), msgIdx ? (msgIdx === '' ? null : parseInt(msgIdx)) : null);
10147+
}
10148+
return;
10149+
}
10150+
});

0 commit comments

Comments
 (0)