@@ -296,7 +296,6 @@ class ReflectionMatrix {
296296 async updateProjectCode ( ) {
297297 const code = await this . activity . prepareExport ( ) ;
298298 if ( code === this . code ) {
299- console . log ( "No changes in code detected." ) ;
300299 return ; // No changes in code
301300 }
302301
@@ -308,8 +307,6 @@ class ReflectionMatrix {
308307 if ( data . algorithm !== "unchanged" ) {
309308 this . projectAlgorithm = data . algorithm ; // update algorithm
310309 this . code = code ;
311- } else {
312- console . log ( "No changes in algorithm detected." ) ;
313310 }
314311 this . botReplyDiv ( data , false , false ) ;
315312 } else {
@@ -414,7 +411,6 @@ class ReflectionMatrix {
414411 */
415412 async generateAnalysis ( ) {
416413 try {
417- console . log ( "Summary stored" , this . summary ) ;
418414 const response = await fetch ( `${ this . PORT } /analysis` , {
419415 method : "POST" ,
420416 headers : { "Content-Type" : "application/json" } ,
@@ -573,8 +569,11 @@ class ReflectionMatrix {
573569 */
574570 saveReport ( data ) {
575571 const key = "musicblocks_analysis" ;
576- localStorage . setItem ( key , data . response ) ;
577- console . log ( "Conversation saved in localStorage." ) ;
572+ try {
573+ localStorage . setItem ( key , data . response ) ;
574+ } catch ( e ) {
575+ console . warn ( "Could not save analysis report to localStorage:" , e ) ;
576+ }
578577 }
579578
580579 /** Reads the analysis report from localStorage.
@@ -613,13 +612,76 @@ class ReflectionMatrix {
613612 URL . revokeObjectURL ( url ) ;
614613 }
615614
615+ /**
616+ * Escapes HTML special characters to prevent XSS attacks.
617+ * @param {string } text - The text to escape.
618+ * @returns {string } - The escaped text.
619+ */
620+ escapeHTML ( text ) {
621+ const escapeMap = {
622+ "&" : "&" ,
623+ "<" : "<" ,
624+ ">" : ">" ,
625+ '"' : """ ,
626+ "'" : "'"
627+ } ;
628+ return text . replace ( / [ & < > " ' ] / g, char => escapeMap [ char ] ) ;
629+ }
630+
631+ /**
632+ * Sanitizes HTML content using DOMParser to prevent XSS.
633+ * Removes unsafe attributes and ensures links are safe.
634+ * @param {string } htmlString - The HTML string to sanitize.
635+ * @returns {string } - The sanitized HTML string.
636+ */
637+ sanitizeHTML ( htmlString ) {
638+ const parser = new DOMParser ( ) ;
639+ const doc = parser . parseFromString ( htmlString , "text/html" ) ;
640+
641+ // Sanitize links
642+ const links = doc . getElementsByTagName ( "a" ) ;
643+ for ( let i = 0 ; i < links . length ; i ++ ) {
644+ const link = links [ i ] ;
645+ const href = link . getAttribute ( "href" ) ;
646+
647+ // If no href, or it's unsafe, remove the attribute
648+ if ( ! href || this . isUnsafeUrl ( href ) ) {
649+ link . removeAttribute ( "href" ) ;
650+ } else {
651+ // Enforce security attributes for external links
652+ link . setAttribute ( "target" , "_blank" ) ;
653+ link . setAttribute ( "rel" , "noopener noreferrer" ) ;
654+ }
655+ }
656+
657+ return doc . body . innerHTML ;
658+ }
659+
660+ /**
661+ * Checks if a URL is unsafe (javascript:, data:, vbscript:).
662+ * @param {string } url - The URL to check.
663+ * @returns {boolean } - True if unsafe, false otherwise.
664+ */
665+ isUnsafeUrl ( url ) {
666+ const trimmed = url . trim ( ) . toLowerCase ( ) ;
667+ const unsafeSchemes = [ "javascript:" , "data:" , "vbscript:" ] ;
668+ // Check if it starts with any unsafe scheme
669+ // Note: DOMParser handles HTML entity decoding, so we check the raw attribute safely here
670+ // But for extra safety against control characters, we rely on the fact that
671+ // we are operating on the parsed DOM attribute.
672+ return unsafeSchemes . some ( scheme => trimmed . replace ( / \s + / g, "" ) . startsWith ( scheme ) ) ;
673+ }
674+
616675 /**
617676 * Converts Markdown text to HTML.
618677 * @param {string } md - The Markdown text.
619678 * @returns {string } - The converted HTML text.
620679 */
621680 mdToHTML ( md ) {
622- let html = md ;
681+ // Step 1: Escape HTML first to prevent XSS attacks from raw tags
682+ let html = this . escapeHTML ( md ) ;
683+
684+ // Step 2: Convert Markdown syntax to HTML
623685
624686 // Headings
625687 html = html . replace ( / ^ # # # # # # ( .* $ ) / gim, "<h6>$1</h6>" ) ;
@@ -633,12 +695,13 @@ class ReflectionMatrix {
633695 html = html . replace ( / \* \* ( .* ?) \* \* / gim, "<b>$1</b>" ) ;
634696 html = html . replace ( / \* ( .* ?) \* / gim, "<i>$1</i>" ) ;
635697
636- // Links
637- html = html . replace ( / \[ ( .* ?) \] \( ( .* ?) \) / gim, "<a href='$2' target='_blank' >$1</a>" ) ;
698+ // Links - Create raw anchor tags, sanitization happens in Step 3
699+ html = html . replace ( / \[ ( .* ?) \] \( ( .* ?) \) / gim, "<a href='$2'>$1</a>" ) ;
638700
639701 // Line breaks
640702 html = html . replace ( / \n / gim, "<br>" ) ;
641703
642- return html . trim ( ) ;
704+ // Step 3: Sanitize the generated HTML using DOMParser
705+ return this . sanitizeHTML ( html ) ;
643706 }
644707}
0 commit comments