Skip to content

Commit 1d5dc7e

Browse files
committed
Fix incomplete sanitization in inline event handlers
Add proper escapeForAttr function that escapes both backslashes and single quotes to prevent XSS vulnerabilities in onclick handlers. Fixes 4 CodeQL security warnings.
1 parent 1372dee commit 1d5dc7e

File tree

1 file changed

+11
-4
lines changed

1 file changed

+11
-4
lines changed

static/js/app.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ let demoMode = true; // Demo mode on by default
55
let previousSectionModal = null; // Track which section modal was open
66
let assignmentNameMap = {}; // Map assignment IDs to their display names for demo mode
77

8+
// Escape function for inline event handlers
9+
function escapeForAttr(str) {
10+
return String(str)
11+
.replace(/\\/g, '\\\\')
12+
.replace(/'/g, "\\'");
13+
}
14+
815
// Demo mode functions
916
function toggleDemoMode() {
1017
const classListCheckbox = document.getElementById("demo-mode");
@@ -296,7 +303,7 @@ async function loadAssignmentDifficulty() {
296303
return `
297304
<tr style="cursor:pointer;" onclick="openAssignmentModal('${
298305
item.assignment_id
299-
}', '${displayName.replace(/'/g, "\\'")}')">
306+
}', '${escapeForAttr(displayName)}')">
300307
<td>${displaySection}</td>
301308
<td title="${displayName}">${
302309
displayName.length > 30 ? displayName.substring(0, 30) + "..." : displayName
@@ -1336,7 +1343,7 @@ async function loadSectionProgress() {
13361343
const displaySection = maskSectionName(section.section, index);
13371344

13381345
return `
1339-
<tr style="cursor:pointer;" onclick="openSectionModal('${section.section.replace(/'/g, "\\'")}')">
1346+
<tr style="cursor:pointer;" onclick="openSectionModal('${escapeForAttr(section.section)}')">
13401347
<td>${displaySection}</td>
13411348
<td data-value="${section.students_started}">
13421349
<div style="display:flex; align-items:center; gap:10px;">
@@ -2013,7 +2020,7 @@ async function openSectionModal(sectionName) {
20132020
} else {
20142021
notStartedBody.innerHTML = notStarted
20152022
.map((sp, index) => `
2016-
<tr style="cursor:pointer;" onclick="previousSectionModal = '${sectionName.replace(/'/g, "\\'")}'; closeSectionModal(); openStudentModal('${sp.student.id}')">
2023+
<tr style="cursor:pointer;" onclick="previousSectionModal = '${escapeForAttr(sectionName)}'; closeSectionModal(); openStudentModal('${sp.student.id}')">
20172024
<td>${maskName(sp.student.first_name, sp.student.last_name, index)}</td>
20182025
<td>${sp.student.night || '-'}</td>
20192026
</tr>
@@ -2030,7 +2037,7 @@ async function openSectionModal(sectionName) {
20302037
.map((sp, index) => {
20312038
const progressPct = (sp.completed / sp.total) * 100;
20322039
return `
2033-
<tr style="cursor:pointer;" onclick="previousSectionModal = '${sectionName.replace(/'/g, "\\'")}'; closeSectionModal(); openStudentModal('${sp.student.id}')">
2040+
<tr style="cursor:pointer;" onclick="previousSectionModal = '${escapeForAttr(sectionName)}'; closeSectionModal(); openStudentModal('${sp.student.id}')">
20342041
<td>${maskName(sp.student.first_name, sp.student.last_name, index)}</td>
20352042
<td>${sp.student.night || '-'}</td>
20362043
<td>

0 commit comments

Comments
 (0)