From 430bc1c25637ce932059758097c9570be4d17949 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 00:31:09 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20Fix=20XSS=20in=20file=20list=20r?= =?UTF-8?q?endering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Improved escapeHtml to include quote escaping. - Used escapeHtml for all filename and fileId interpolations in public/index.html. - Refactored event handlers to use data- attributes and this.dataset for safe parameter passing. - Used CSS.escape for safe attribute selection in querySelector. - Added safety checks for DOM element existence. - Fixed a duplicate saveComment function. Co-authored-by: praxstack <73683289+praxstack@users.noreply.github.com> --- public/index.html | 110 ++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/public/index.html b/public/index.html index 44fbbc0..5c5ce88 100644 --- a/public/index.html +++ b/public/index.html @@ -978,55 +978,25 @@

💡 Quick Templates:

textarea.setSelectionRange(cursorPos + template.length, cursorPos + template.length); } - function saveComment() { - const textarea = document.getElementById('commentTextarea'); - const comment = textarea.value.trim(); - - if (comment) { - fileComments[currentCommentFile] = comment; - - // Update comment button to show it has a comment - const commentBtn = document.querySelector(`[onclick*="'${currentCommentFile}'"]`); - if (commentBtn) { - commentBtn.classList.add('has-comment'); - const indicator = document.getElementById(`comment-indicator-${currentCommentFileId}`); - if (indicator) { - indicator.style.display = 'inline'; - } - } - - // Add comment display to the file - showCommentInFile(currentCommentFile, currentCommentFileId, comment); - } else { - // Remove comment if empty - delete fileComments[currentCommentFile]; - const commentBtn = document.querySelector(`[onclick*="'${currentCommentFile}'"]`); - if (commentBtn) { - commentBtn.classList.remove('has-comment'); - const indicator = document.getElementById(`comment-indicator-${currentCommentFileId}`); - if (indicator) { - indicator.style.display = 'none'; - } - } - removeCommentFromFile(currentCommentFileId); - } - - closeCommentModal(); - } function showCommentInFile(file, fileId, comment) { // Remove existing comment display removeCommentFromFile(fileId); // Add comment display after file header - const fileItem = document.getElementById(`diff-${fileId}`).parentElement; + const diffElem = document.getElementById(`diff-${fileId}`); + if (!diffElem) return; + + const fileItem = diffElem.parentElement; const commentDiv = document.createElement('div'); commentDiv.id = `comment-display-${fileId}`; commentDiv.className = 'comment-display'; commentDiv.innerHTML = `💭 Your Comment:
${escapeHtml(comment)}`; const fileHeader = fileItem.querySelector('.file-header'); - fileHeader.parentNode.insertBefore(commentDiv, fileHeader.nextSibling); + if (fileHeader) { + fileHeader.parentNode.insertBefore(commentDiv, fileHeader.nextSibling); + } } function removeCommentFromFile(fileId) { @@ -1207,20 +1177,24 @@

No Staged Changes

const fileDiv = document.createElement('div'); fileDiv.className = 'file-item'; fileDiv.innerHTML = ` -
+
- ▶️ + ▶️ ${fileIcon} - ${file} + ${escapeHtml(file)}
-
+
Click to load diff
${badgeText}
-
+
Loading diff for ${fileName}... @@ -1263,6 +1239,8 @@

No Staged Changes

const diffDiv = document.getElementById(`diff-${fileId}`); const icon = document.getElementById(`icon-${fileId}`); + if (!diffDiv || !icon) return; + if (diffDiv.classList.contains('active')) { diffDiv.classList.remove('active'); icon.classList.remove('rotated'); @@ -1346,7 +1324,8 @@

No Staged Changes

fileComments[currentCommentFile] = comment; console.log(`💬 File comment added: ${currentCommentFile}: "${comment.substring(0, 50)}${comment.length > 50 ? '...' : ''}"`); - const commentBtn = document.querySelector(`[onclick*="'${currentCommentFile}'"]`); + // Use data attribute instead of string matching + const commentBtn = document.querySelector(`.comment-btn[data-file="${CSS.escape(currentCommentFile)}"]`); if (commentBtn) { commentBtn.classList.add('has-comment'); const indicator = document.getElementById(`comment-indicator-${currentCommentFileId}`); @@ -1356,7 +1335,7 @@

No Staged Changes

} else { delete fileComments[currentCommentFile]; console.log(`❌ File comment removed: ${currentCommentFile}`); - const commentBtn = document.querySelector(`[onclick*="'${currentCommentFile}'"]`); + const commentBtn = document.querySelector(`.comment-btn[data-file="${CSS.escape(currentCommentFile)}"]`); if (commentBtn) { commentBtn.classList.remove('has-comment'); const indicator = document.getElementById(`comment-indicator-${currentCommentFileId}`); @@ -1426,7 +1405,13 @@

No Staged Changes

${oldNum || ''} ${newNum || ''} - +
${escapeHtml(line.content)} @@ -1446,15 +1431,18 @@

No Staged Changes

const removed = lines.filter(line => line.startsWith('-') && !line.startsWith('---')).length; const statsElement = document.getElementById(`stats-${fileId}`); - statsElement.innerHTML = ` - +${added} - -${removed} - `; + if (statsElement) { + statsElement.innerHTML = ` + +${added} + -${removed} + `; + } } // Toggle file selection for export function toggleFileSelection(file, fileId) { const checkbox = document.getElementById(`select-${fileId}`); + if (!checkbox) return; const fileItem = checkbox.closest('.file-item'); if (checkbox.checked) { @@ -1640,9 +1628,15 @@

No Staged Changes

} function escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; + if (text === null || text === undefined) return ''; + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; }); } // Keyboard shortcuts