@@ -22,6 +22,19 @@ const SLASH_COMMANDS = [
2222 { cmd : "/help" , desc : "Show help" } ,
2323] ;
2424
25+ // Security: HTML escape utility to prevent XSS
26+ function escapeHtml ( unsafe ) {
27+ if ( typeof unsafe !== "string" ) {
28+ return "" ;
29+ }
30+ return unsafe
31+ . replace ( / & / g, "&" )
32+ . replace ( / < / g, "<" )
33+ . replace ( / > / g, ">" )
34+ . replace ( / " / g, """ )
35+ . replace ( / ' / g, "'" ) ;
36+ }
37+
2538// Connect to WebSocket
2639function connect ( ) {
2740 const protocol = window . location . protocol === "https:" ? "wss:" : "ws:" ;
@@ -71,10 +84,16 @@ function updateStatus(text, className = "") {
7184function showUpdateBanner ( currentVersion , latestVersion ) {
7285 const banner = document . createElement ( "div" ) ;
7386 banner . className = "update-banner" ;
74- banner . innerHTML = `
75- <span>📦 Update available: ${ currentVersion } → ${ latestVersion } </span>
76- <button onclick="this.parentElement.remove()">✕</button>
77- ` ;
87+
88+ const span = document . createElement ( "span" ) ;
89+ span . textContent = `📦 Update available: ${ currentVersion } → ${ latestVersion } ` ;
90+
91+ const button = document . createElement ( "button" ) ;
92+ button . textContent = "✕" ;
93+ button . onclick = ( ) => banner . remove ( ) ;
94+
95+ banner . appendChild ( span ) ;
96+ banner . appendChild ( button ) ;
7897 document . body . insertBefore ( banner , document . body . firstChild ) ;
7998}
8099
@@ -161,18 +180,21 @@ function renderFileTree(tree) {
161180
162181 // Show directories first
163182 directories . slice ( 0 , 50 ) . forEach ( dir => {
183+ const safeName = escapeHtml ( dir . name ) ;
164184 html += `
165185 <div class="file-item directory">
166- 📁 ${ dir . name }
186+ 📁 ${ safeName }
167187 </div>
168188 ` ;
169189 } ) ;
170190
171191 // Then files
172192 files . slice ( 0 , 100 ) . forEach ( file => {
193+ const safeName = escapeHtml ( file . name ) ;
194+ const safePath = escapeHtml ( file . path ) ;
173195 html += `
174- <div class="file-item" onclick="viewFile('${ file . path } ')">
175- 📄 ${ file . name }
196+ <div class="file-item" onclick="viewFile('${ safePath } ')">
197+ 📄 ${ safeName }
176198 </div>
177199 ` ;
178200 } ) ;
@@ -190,10 +212,15 @@ function renderSessions(sessions) {
190212 let html = "" ;
191213 sessions . forEach ( session => {
192214 const date = new Date ( session . lastAccessed ) . toLocaleDateString ( ) ;
215+ const safeName = escapeHtml ( session . name ) ;
216+ const safeId = escapeHtml ( session . id ) ;
217+ const safeCount = escapeHtml ( String ( session . messageCount || 0 ) ) ;
218+ const safeDate = escapeHtml ( date ) ;
219+
193220 html += `
194- <div class="session-item" onclick="switchSession('${ session . id } ')">
195- <div class="session-name">${ session . name } </div>
196- <div class="session-meta">${ session . messageCount } messages · ${ date } </div>
221+ <div class="session-item" onclick="switchSession('${ safeId } ')">
222+ <div class="session-name">${ safeName } </div>
223+ <div class="session-meta">${ safeCount } messages · ${ safeDate } </div>
197224 </div>
198225 ` ;
199226 } ) ;
@@ -249,9 +276,11 @@ function handleAutocomplete(input) {
249276
250277 let html = "" ;
251278 matches . forEach ( ( cmd , index ) => {
279+ const safeCmd = escapeHtml ( cmd . cmd ) ;
280+ const safeDesc = escapeHtml ( cmd . desc ) ;
252281 html += `
253- <div class="autocomplete-item" data-cmd="${ cmd . cmd } " data-index="${ index } ">
254- <strong>${ cmd . cmd } </strong> - ${ cmd . desc }
282+ <div class="autocomplete-item" data-cmd="${ safeCmd } " data-index="${ index } ">
283+ <strong>${ safeCmd } </strong> - ${ safeDesc }
255284 </div>
256285 ` ;
257286 } ) ;
0 commit comments