Skip to content

Commit 46bfac5

Browse files
authored
fix: integrate DOMPurify to sanitize innerHTML assignments in dashboard (#252)
* fix: integrate DOMPurify for XSS protection in dashboard
1 parent a43306c commit 46bfac5

2 files changed

Lines changed: 125 additions & 96 deletions

File tree

src/AzureEventGridSimulator/Dashboard/app.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,9 @@
279279

280280
elements.emptyState.classList.add('hidden');
281281

282-
elements.eventsList.innerHTML = filteredEvents
282+
elements.eventsList.innerHTML = DOMPurify.sanitize(filteredEvents
283283
.map(event => renderEventItem(event))
284-
.join('');
284+
.join(''));
285285

286286
// Attach click handlers
287287
elements.eventsList.querySelectorAll('.event-item').forEach(item => {
@@ -303,9 +303,9 @@
303303

304304
elements.emptyRejectionsState.classList.add('hidden');
305305

306-
elements.rejectionsList.innerHTML = filteredRejections
306+
elements.rejectionsList.innerHTML = DOMPurify.sanitize(filteredRejections
307307
.map(rejection => renderRejectionItem(rejection))
308-
.join('');
308+
.join(''));
309309

310310
// Attach click handlers
311311
elements.rejectionsList.querySelectorAll('.rejection-item').forEach(item => {
@@ -386,7 +386,7 @@
386386
selectedEventId = null;
387387
selectedRejectionId = null;
388388
elements.detailPanel.classList.remove('open');
389-
elements.detailContent.innerHTML = '<p class="detail-placeholder">Select an event to view details</p>';
389+
elements.detailContent.innerHTML = DOMPurify.sanitize('<p class="detail-placeholder">Select an event to view details</p>');
390390
renderEventsList();
391391
renderRejectionsList();
392392
}
@@ -447,7 +447,7 @@
447447
${renderDeliverySection(event.deliveries || [])}
448448
`;
449449

450-
elements.detailContent.innerHTML = html;
450+
elements.detailContent.innerHTML = DOMPurify.sanitize(html);
451451

452452
// Attach attempt toggle handlers
453453
elements.detailContent.querySelectorAll('.attempt-toggle').forEach(toggle => {
@@ -505,7 +505,7 @@
505505
` : ''}
506506
`;
507507

508-
elements.detailContent.innerHTML = html;
508+
elements.detailContent.innerHTML = DOMPurify.sanitize(html);
509509
}
510510

511511
function renderDeliverySection(deliveries) {
Lines changed: 118 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,127 @@
11
<!DOCTYPE html>
22
<html lang="en">
3-
<head>
4-
<meta charset="UTF-8">
5-
<meta content="width=device-width, initial-scale=1.0" name="viewport">
6-
<title>Azure Event Grid Simulator - Dashboard</title>
7-
<link href="/dashboard/styles.css" rel="stylesheet">
8-
</head>
9-
<body>
10-
<header class="header">
11-
<h1>Azure Event Grid Simulator</h1>
12-
<div class="header-controls">
13-
<button class="clear-btn" id="clearBtn">Clear</button>
14-
<label class="auto-refresh-toggle">
15-
<input checked id="autoRefresh" type="checkbox">
16-
<span>Auto-refresh</span>
17-
</label>
18-
<span class="refresh-indicator" id="refreshIndicator"></span>
19-
</div>
20-
</header>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
6+
<title>Azure Event Grid Simulator - Dashboard</title>
7+
<link href="/dashboard/styles.css" rel="stylesheet" />
8+
</head>
9+
<body>
10+
<header class="header">
11+
<h1>Azure Event Grid Simulator</h1>
12+
<div class="header-controls">
13+
<button class="clear-btn" id="clearBtn">Clear</button>
14+
<label class="auto-refresh-toggle">
15+
<input checked id="autoRefresh" type="checkbox" />
16+
<span>Auto-refresh</span>
17+
</label>
18+
<span class="refresh-indicator" id="refreshIndicator"></span>
19+
</div>
20+
</header>
2121

22-
<main class="main-content">
23-
<section class="stats-section" id="statsSection">
24-
<div class="stat-card">
25-
<span class="stat-value" id="statTotalReceived">0</span>
26-
<span class="stat-label">Total Received</span>
27-
</div>
28-
<div class="stat-card">
29-
<span class="stat-value" id="statInHistory">0</span>
30-
<span class="stat-label">In History</span>
31-
</div>
32-
<div class="stat-card">
33-
<span class="stat-value" id="statDelivered">0</span>
34-
<span class="stat-label">Delivered</span>
35-
</div>
36-
<div class="stat-card">
37-
<span class="stat-value" id="statFailed">0</span>
38-
<span class="stat-label">Failed</span>
39-
</div>
40-
<div class="stat-card">
41-
<span class="stat-value" id="statPending">0</span>
42-
<span class="stat-label">Pending</span>
43-
</div>
44-
<div class="stat-card stat-card-rejected">
45-
<span class="stat-value" id="statRejected">0</span>
46-
<span class="stat-label">Rejected</span>
47-
</div>
48-
<div class="stat-card">
49-
<span class="stat-value" id="statTopics">0</span>
50-
<span class="stat-label">Active Topics</span>
51-
</div>
52-
</section>
22+
<main class="main-content">
23+
<section class="stats-section" id="statsSection">
24+
<div class="stat-card">
25+
<span class="stat-value" id="statTotalReceived">0</span>
26+
<span class="stat-label">Total Received</span>
27+
</div>
28+
<div class="stat-card">
29+
<span class="stat-value" id="statInHistory">0</span>
30+
<span class="stat-label">In History</span>
31+
</div>
32+
<div class="stat-card">
33+
<span class="stat-value" id="statDelivered">0</span>
34+
<span class="stat-label">Delivered</span>
35+
</div>
36+
<div class="stat-card">
37+
<span class="stat-value" id="statFailed">0</span>
38+
<span class="stat-label">Failed</span>
39+
</div>
40+
<div class="stat-card">
41+
<span class="stat-value" id="statPending">0</span>
42+
<span class="stat-label">Pending</span>
43+
</div>
44+
<div class="stat-card stat-card-rejected">
45+
<span class="stat-value" id="statRejected">0</span>
46+
<span class="stat-label">Rejected</span>
47+
</div>
48+
<div class="stat-card">
49+
<span class="stat-value" id="statTopics">0</span>
50+
<span class="stat-label">Active Topics</span>
51+
</div>
52+
</section>
5353

54-
<section class="events-section">
55-
<div class="events-header">
56-
<div class="tab-container">
57-
<button class="tab-btn active" data-tab="events" id="tabEvents">Events</button>
58-
<button class="tab-btn" data-tab="rejections" id="tabRejections">Rejections <span
59-
class="rejection-count" id="rejectionCount"></span></button>
60-
</div>
61-
<select class="topic-filter" id="topicFilter">
62-
<option value="">All Topics</option>
63-
</select>
64-
</div>
54+
<section class="events-section">
55+
<div class="events-header">
56+
<div class="tab-container">
57+
<button
58+
class="tab-btn active"
59+
data-tab="events"
60+
id="tabEvents"
61+
>
62+
Events
63+
</button>
64+
<button
65+
class="tab-btn"
66+
data-tab="rejections"
67+
id="tabRejections"
68+
>
69+
Rejections
70+
<span
71+
class="rejection-count"
72+
id="rejectionCount"
73+
></span>
74+
</button>
75+
</div>
76+
<select class="topic-filter" id="topicFilter">
77+
<option value="">All Topics</option>
78+
</select>
79+
</div>
6580

66-
<div class="tab-content" id="eventsTab">
67-
<div class="empty-state" id="emptyState">
68-
<div class="empty-state-icon">&#128235;</div>
69-
<h3>No events received yet</h3>
70-
<p>Events will appear here when they are published to the simulator.</p>
71-
</div>
72-
<div class="events-list" id="eventsList"></div>
73-
</div>
81+
<div class="tab-content" id="eventsTab">
82+
<div class="empty-state" id="emptyState">
83+
<div class="empty-state-icon">&#128235;</div>
84+
<h3>No events received yet</h3>
85+
<p>
86+
Events will appear here when they are published to
87+
the simulator.
88+
</p>
89+
</div>
90+
<div class="events-list" id="eventsList"></div>
91+
</div>
7492

75-
<div class="tab-content hidden" id="rejectionsTab">
76-
<div class="empty-state" id="emptyRejectionsState">
77-
<div class="empty-state-icon">&#10003;</div>
78-
<h3>No rejected events</h3>
79-
<p>Validation errors will appear here when invalid events are sent to the simulator.</p>
80-
</div>
81-
<div class="events-list" id="rejectionsList"></div>
82-
</div>
83-
</section>
93+
<div class="tab-content hidden" id="rejectionsTab">
94+
<div class="empty-state" id="emptyRejectionsState">
95+
<div class="empty-state-icon">&#10003;</div>
96+
<h3>No rejected events</h3>
97+
<p>
98+
Validation errors will appear here when invalid
99+
events are sent to the simulator.
100+
</p>
101+
</div>
102+
<div class="events-list" id="rejectionsList"></div>
103+
</div>
104+
</section>
84105

85-
<aside class="detail-panel" id="detailPanel">
86-
<div class="detail-header">
87-
<h3>Event Details</h3>
88-
<button class="close-btn" id="closeDetail">&times;</button>
89-
</div>
90-
<div class="detail-content" id="detailContent">
91-
<p class="detail-placeholder">Select an event to view details</p>
92-
</div>
93-
</aside>
94-
</main>
106+
<aside class="detail-panel" id="detailPanel">
107+
<div class="detail-header">
108+
<h3>Event Details</h3>
109+
<button class="close-btn" id="closeDetail">&times;</button>
110+
</div>
111+
<div class="detail-content" id="detailContent">
112+
<p class="detail-placeholder">
113+
Select an event to view details
114+
</p>
115+
</div>
116+
</aside>
117+
</main>
95118

96-
<script src="/dashboard/app.js"></script>
97-
</body>
119+
<script
120+
src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.7/purify.min.js"
121+
integrity="sha512-78KH17QLT5e55GJqP76vutp1D2iAoy06WcYBXB6iBCsmO6wWzx0Qdg8EDpm8mKXv68BcvHOyeeP4wxAL0twJGQ=="
122+
crossorigin="anonymous"
123+
referrerpolicy="no-referrer"
124+
></script>
125+
<script src="/dashboard/app.js"></script>
126+
</body>
98127
</html>

0 commit comments

Comments
 (0)