@@ -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 {
@@ -416,7 +413,6 @@ class ReflectionMatrix {
416413 */
417414 async generateAnalysis ( ) {
418415 try {
419- console . log ( "Summary stored" , this . summary ) ;
420416 const response = await fetch ( `${ this . PORT } /analysis` , {
421417 method : "POST" ,
422418 headers : { "Content-Type" : "application/json" } ,
@@ -575,8 +571,11 @@ class ReflectionMatrix {
575571 */
576572 saveReport ( data ) {
577573 const key = "musicblocks_analysis" ;
578- localStorage . setItem ( key , data . response ) ;
579- console . log ( "Conversation saved in localStorage." ) ;
574+ try {
575+ localStorage . setItem ( key , data . response ) ;
576+ } catch ( e ) {
577+ console . warn ( "Could not save analysis report to localStorage:" , e ) ;
578+ }
580579 }
581580
582581 /** Reads the analysis report from localStorage.
@@ -615,13 +614,76 @@ class ReflectionMatrix {
615614 URL . revokeObjectURL ( url ) ;
616615 }
617616
617+ /**
618+ * Escapes HTML special characters to prevent XSS attacks.
619+ * @param {string } text - The text to escape.
620+ * @returns {string } - The escaped text.
621+ */
622+ escapeHTML ( text ) {
623+ const escapeMap = {
624+ "&" : "&" ,
625+ "<" : "<" ,
626+ ">" : ">" ,
627+ '"' : """ ,
628+ "'" : "'"
629+ } ;
630+ return text . replace ( / [ & < > " ' ] / g, char => escapeMap [ char ] ) ;
631+ }
632+
633+ /**
634+ * Sanitizes HTML content using DOMParser to prevent XSS.
635+ * Removes unsafe attributes and ensures links are safe.
636+ * @param {string } htmlString - The HTML string to sanitize.
637+ * @returns {string } - The sanitized HTML string.
638+ */
639+ sanitizeHTML ( htmlString ) {
640+ const parser = new DOMParser ( ) ;
641+ const doc = parser . parseFromString ( htmlString , "text/html" ) ;
642+
643+ // Sanitize links
644+ const links = doc . getElementsByTagName ( "a" ) ;
645+ for ( let i = 0 ; i < links . length ; i ++ ) {
646+ const link = links [ i ] ;
647+ const href = link . getAttribute ( "href" ) ;
648+
649+ // If no href, or it's unsafe, remove the attribute
650+ if ( ! href || this . isUnsafeUrl ( href ) ) {
651+ link . removeAttribute ( "href" ) ;
652+ } else {
653+ // Enforce security attributes for external links
654+ link . setAttribute ( "target" , "_blank" ) ;
655+ link . setAttribute ( "rel" , "noopener noreferrer" ) ;
656+ }
657+ }
658+
659+ return doc . body . innerHTML ;
660+ }
661+
662+ /**
663+ * Checks if a URL is unsafe (javascript:, data:, vbscript:).
664+ * @param {string } url - The URL to check.
665+ * @returns {boolean } - True if unsafe, false otherwise.
666+ */
667+ isUnsafeUrl ( url ) {
668+ const trimmed = url . trim ( ) . toLowerCase ( ) ;
669+ const unsafeSchemes = [ "javascript:" , "data:" , "vbscript:" ] ;
670+ // Check if it starts with any unsafe scheme
671+ // Note: DOMParser handles HTML entity decoding, so we check the raw attribute safely here
672+ // But for extra safety against control characters, we rely on the fact that
673+ // we are operating on the parsed DOM attribute.
674+ return unsafeSchemes . some ( scheme => trimmed . replace ( / \s + / g, "" ) . startsWith ( scheme ) ) ;
675+ }
676+
618677 /**
619678 * Converts Markdown text to HTML.
620679 * @param {string } md - The Markdown text.
621680 * @returns {string } - The converted HTML text.
622681 */
623682 mdToHTML ( md ) {
624- let html = md ;
683+ // Step 1: Escape HTML first to prevent XSS attacks from raw tags
684+ let html = this . escapeHTML ( md ) ;
685+
686+ // Step 2: Convert Markdown syntax to HTML
625687
626688 // Headings
627689 html = html . replace ( / ^ # # # # # # ( .* $ ) / gim, "<h6>$1</h6>" ) ;
@@ -635,12 +697,13 @@ class ReflectionMatrix {
635697 html = html . replace ( / \* \* ( .* ?) \* \* / gim, "<b>$1</b>" ) ;
636698 html = html . replace ( / \* ( .* ?) \* / gim, "<i>$1</i>" ) ;
637699
638- // Links
639- html = html . replace ( / \[ ( .* ?) \] \( ( .* ?) \) / gim, "<a href='$2' target='_blank' >$1</a>" ) ;
700+ // Links - Create raw anchor tags, sanitization happens in Step 3
701+ html = html . replace ( / \[ ( .* ?) \] \( ( .* ?) \) / gim, "<a href='$2'>$1</a>" ) ;
640702
641703 // Line breaks
642704 html = html . replace ( / \n / gim, "<br>" ) ;
643705
644- return html . trim ( ) ;
706+ // Step 3: Sanitize the generated HTML using DOMParser
707+ return this . sanitizeHTML ( html ) ;
645708 }
646709}
0 commit comments