44// Initialize PolliLibJS API
55const polliAPI = new PollinationsAPI ( ) ;
66
7+ // Sanitize HTML to prevent XSS attacks
8+ function sanitizeHTML ( html ) {
9+ if ( typeof DOMPurify !== 'undefined' ) {
10+ return DOMPurify . sanitize ( html , {
11+ ALLOWED_TAGS : [ 'p' , 'br' , 'strong' , 'em' , 'b' , 'i' , 'u' , 'a' , 'code' , 'pre' , 'ul' , 'ol' , 'li' , 'blockquote' , 'img' , 'span' , 'div' , 'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' ] ,
12+ ALLOWED_ATTR : [ 'href' , 'src' , 'alt' , 'class' , 'id' , 'target' , 'rel' , 'crossorigin' , 'loading' ]
13+ } ) ;
14+ }
15+ return html ;
16+ }
17+
718// Settings Toggle
819const settingsToggle = document . getElementById ( 'settingsToggle' ) ;
920const settingsPanel = document . getElementById ( 'settingsPanel' ) ;
@@ -353,7 +364,7 @@ textModel.addEventListener('change', function() {
353364 role : 'system' ,
354365 content : modelType === 'midijourney' ? midijourneySystemPrompt : systemMessage . content
355366 } ] ;
356- chatOutput . innerHTML += `<p><em>Switched to ${ textModel . value } model. ${ modelType === 'midijourney' ? 'Starting Midijourney context...' : 'Starting new conversation with system context.' } </em></p>` ;
367+ chatOutput . innerHTML += sanitizeHTML ( `<p><em>Switched to ${ textModel . value } model. ${ modelType === 'midijourney' ? 'Starting Midijourney context...' : 'Starting new conversation with system context.' } </em></p>` ) ;
357368 scrollToBottom ( ) ;
358369} ) ;
359370
@@ -378,7 +389,7 @@ async function generateImageFromPrompt(prompt, appendToChat = true) {
378389 const imageBlob = await response . blob ( ) ;
379390 const imageObjectURL = URL . createObjectURL ( imageBlob ) ;
380391 if ( appendToChat ) {
381- chatOutput . innerHTML += `<img src="${ imageObjectURL } " alt="Generated Image" class="inline ${ cssClass } ">` ;
392+ chatOutput . innerHTML += sanitizeHTML ( `<img src="${ imageObjectURL } " alt="Generated Image" class="inline ${ cssClass } ">` ) ;
382393 scrollToBottom ( ) ;
383394 }
384395 return imageObjectURL ;
@@ -387,7 +398,7 @@ async function generateImageFromPrompt(prompt, appendToChat = true) {
387398 }
388399 } catch ( error ) {
389400 console . error ( "Error generating image:" , error ) ;
390- chatOutput . innerHTML += `<p><strong>Error:</strong> Unable to generate image. Please try again.</p>` ;
401+ chatOutput . innerHTML += sanitizeHTML ( `<p><strong>Error:</strong> Unable to generate image. Please try again.</p>` ) ;
391402 scrollToBottom ( ) ;
392403 }
393404}
@@ -400,7 +411,7 @@ chatForm.onsubmit = async function(event) {
400411 const selectedModel = textModel . value ;
401412 const isEvil = selectedModel === 'evil' ;
402413 const modelType = getModelType ( selectedModel ) ;
403- chatOutput . innerHTML += `<p><strong>${ isEvil ? 'Evil User' : 'User' } :</strong> ${ prompt } </p>` ;
414+ chatOutput . innerHTML += sanitizeHTML ( `<p><strong>${ isEvil ? 'Evil User' : 'User' } :</strong> ${ prompt } </p>` ) ;
404415 userInput . value = '' ;
405416 scrollToBottom ( ) ;
406417 if ( modelType === 'chat' || isEvil ) {
@@ -416,7 +427,7 @@ chatForm.onsubmit = async function(event) {
416427 messages : getModelMessages ( modelType , prompt ) ,
417428 model : selectedModel
418429 } ;
419- chatOutput . innerHTML += `<p id="ai-thinking"><em>${ isEvil ? 'Evil AI plotting...' : 'AI is thinking...' } </em></p>` ;
430+ chatOutput . innerHTML += sanitizeHTML ( `<p id="ai-thinking"><em>${ isEvil ? 'Evil AI plotting...' : 'AI is thinking...' } </em></p>` ) ;
420431 scrollToBottom ( ) ;
421432 try {
422433 // Use direct fetch like demo page
@@ -447,16 +458,16 @@ chatForm.onsubmit = async function(event) {
447458 . trim ( ) ;
448459 aiResponse = aiResponse . replace ( / \n + / g, '\n' ) . trim ( ) ;
449460 if ( aiResponse ) {
450- chatOutput . innerHTML += `<p><strong>${ isEvil ? 'Evil AI' : 'AI' } :</strong> ${ aiResponse } </p>` ;
461+ chatOutput . innerHTML += sanitizeHTML ( `<p><strong>${ isEvil ? 'Evil AI' : 'AI' } :</strong> ${ aiResponse } </p>` ) ;
451462 scrollToBottom ( ) ;
452463 }
453- chatOutput . innerHTML += `<p>${ isEvil ? 'Summoning evil image...' : 'Generating image...' } </p>` ;
464+ chatOutput . innerHTML += sanitizeHTML ( `<p>${ isEvil ? 'Summoning evil image...' : 'Generating image...' } </p>` ) ;
454465 scrollToBottom ( ) ;
455466 await generateImageFromPrompt ( lastImagePrompt ) ;
456467 if ( midiNotation && modelType === 'midijourney' ) {
457468 console . log ( "\n=== PLAYING MIDI SEQUENCE ===" ) ;
458469 console . log ( "MIDI Notation:" , midiNotation ) ;
459- chatOutput . innerHTML += `<p>Playing musical sequence...</p>` ;
470+ chatOutput . innerHTML += sanitizeHTML ( `<p>Playing musical sequence...</p>` ) ;
460471 scrollToBottom ( ) ;
461472 downloadMidiBtn . disabled = false ;
462473 synth . playMidiSequence ( midiNotation ) ;
@@ -467,12 +478,12 @@ chatForm.onsubmit = async function(event) {
467478 }
468479 } else if ( isEvil ) {
469480 aiResponse = aiResponse . replace ( / \n + / g, '\n' ) . trim ( ) ;
470- chatOutput . innerHTML += `<p><strong>Evil AI:</strong> ${ aiResponse } </p>` ;
471- chatOutput . innerHTML += `<p><em>Evil AI failed to use proper image format. Next response should include ![MRKDWN]()</em></p>` ;
481+ chatOutput . innerHTML += sanitizeHTML ( `<p><strong>Evil AI:</strong> ${ aiResponse } </p>` ) ;
482+ chatOutput . innerHTML += sanitizeHTML ( `<p><em>Evil AI failed to use proper image format. Next response should include ![MRKDWN]()</em></p>` ) ;
472483 scrollToBottom ( ) ;
473484 } else {
474485 aiResponse = aiResponse . replace ( / \n + / g, '\n' ) . trim ( ) ;
475- chatOutput . innerHTML += `<p><strong>AI:</strong> ${ aiResponse } </p>` ;
486+ chatOutput . innerHTML += sanitizeHTML ( `<p><strong>AI:</strong> ${ aiResponse } </p>` ) ;
476487 scrollToBottom ( ) ;
477488 }
478489 if ( modelType === 'chat' || isEvil ) {
@@ -487,7 +498,7 @@ chatForm.onsubmit = async function(event) {
487498 } catch ( error ) {
488499 console . error ( "Error:" , error ) ;
489500 const errorMessage = isEvil ? 'The darkness is temporarily unavailable. Please try again.' : 'Unable to contact AI. Please try again.' ;
490- chatOutput . innerHTML += `<p><strong>Error:</strong> ${ errorMessage } </p>` ;
501+ chatOutput . innerHTML += sanitizeHTML ( `<p><strong>Error:</strong> ${ errorMessage } </p>` ) ;
491502 scrollToBottom ( ) ;
492503 const thinkingMessage = document . getElementById ( "ai-thinking" ) ;
493504 if ( thinkingMessage ) {
@@ -502,7 +513,7 @@ directImageButton.onclick = async function() {
502513 if ( ! prompt && ! lastImagePrompt ) return ;
503514 const rawPrompt = prompt || lastImagePrompt ;
504515 lastImagePrompt = rawPrompt ;
505- chatOutput . innerHTML += `<p>${ textModel . value === 'evil' ? 'Summoning evil direct image...' : 'Generating direct image...' } </p>` ;
516+ chatOutput . innerHTML += sanitizeHTML ( `<p>${ textModel . value === 'evil' ? 'Summoning evil direct image...' : 'Generating direct image...' } </p>` ) ;
506517 scrollToBottom ( ) ;
507518 await generateImageFromPrompt ( rawPrompt ) ;
508519 userInput . value = '' ;
0 commit comments