From 395f35b00d866aa36e2945af94d1946ef06021dc Mon Sep 17 00:00:00 2001 From: MentatBot <160964065+MentatBot@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:11:55 +0000 Subject: [PATCH 1/2] SVG Feedback Security and Quality Improvements This PR implements several important security and quality improvements to the SVG feedback system: 1. Security Improvements: - Added SVG content sanitization to prevent XSS attacks - Removes dangerous elements (script, foreignObject, use) - Removes dangerous attributes (event handlers) - Sanitizes href/xlink:href attributes 2. Retry Mechanism Enhancement: - Fixed seed increment during retries - Ensures different results on retry attempts 3. Error Handling Improvements: - Separated SVG validation checks - Added specific error messages - Added SVG parsing validation - Better error reporting in UI 4. Code Quality: - Added comprehensive JSDoc comments - Improved code organization - Better error handling patterns These changes address the security vulnerability identified in the previous PR and improve the overall reliability and maintainability of the code. Mentat precommits passed. Log: https://mentat.ai/log/b3a3f8a2-2479-4653-8c09-21888e9956b4 --- svg-feedback/index.html | 132 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 119 insertions(+), 13 deletions(-) diff --git a/svg-feedback/index.html b/svg-feedback/index.html index e599aa8..ee88fe1 100644 --- a/svg-feedback/index.html +++ b/svg-feedback/index.html @@ -186,6 +186,82 @@

History

`; } + /** + * Sanitizes an SVG element by removing potentially dangerous elements and attributes + * @param {SVGElement} svgElement - The SVG element to sanitize + * @returns {SVGElement} The sanitized SVG element + */ + function sanitizeSvgElement(svgElement) { + const dangerousElements = ['script', 'foreignObject', 'use']; + const dangerousAttrs = ['onload', 'onerror', 'onclick', 'onmouseover', 'onmouseout', 'onmousemove', + 'onmousedown', 'onmouseup', 'onkeydown', 'onkeyup', 'onkeypress']; + + // Remove dangerous elements + dangerousElements.forEach(tag => { + const elements = svgElement.getElementsByTagName(tag); + while (elements.length > 0) { + elements[0].parentNode.removeChild(elements[0]); + } + }); + + // Remove dangerous attributes from all elements + const allElements = svgElement.getElementsByTagName('*'); + for (const element of allElements) { + dangerousAttrs.forEach(attr => { + element.removeAttribute(attr); + }); + // Remove javascript: and data: from href/xlink:href + if (element.hasAttribute('href')) { + const href = element.getAttribute('href'); + if (href.toLowerCase().startsWith('javascript:') || href.toLowerCase().startsWith('data:')) { + element.removeAttribute('href'); + } + } + if (element.hasAttribute('xlink:href')) { + const href = element.getAttribute('xlink:href'); + if (href.toLowerCase().startsWith('javascript:') || href.toLowerCase().startsWith('data:')) { + element.removeAttribute('xlink:href'); + } + } + } + + return svgElement; + } + + /** + * Safely parses and sanitizes SVG content before inserting into the DOM + * @param {string} svgString - The SVG content string + * @param {HTMLElement} container - The container element to insert the SVG into + * @returns {boolean} True if successful, false if parsing failed + */ + function sanitizeAndInsertSvg(svgString, container) { + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(svgString, 'image/svg+xml'); + + // Check for parsing errors + const parserError = doc.querySelector('parsererror'); + if (parserError) { + console.error('SVG parsing error:', parserError); + return false; + } + + // Sanitize and insert + const sanitized = sanitizeSvgElement(doc.documentElement); + const clone = sanitized.cloneNode(true); + container.replaceChildren(clone); + return true; + } catch (error) { + console.error('Error sanitizing SVG:', error); + return false; + } + } + + /** + * Extracts SVG content from LLM response text + * @param {string} text - The text to extract SVG from + * @returns {string|null} The extracted SVG content or null if not found + */ function extractSvgContent(text) { // Try to match SVG with language specifier let svgMatch = text.match(/```(?:svg|xml)\n([\s\S]*?)\n```/); @@ -205,9 +281,17 @@

History

return null; } + /** + * Updates the current frame display with sanitized SVG content + * @param {number} currentFrame - The index of the current frame to display + */ function updateFrame(currentFrame) { - // Update preview - elements.preview.innerHTML = frames[currentFrame]; + // Update preview with sanitized SVG + const svgContent = frames[currentFrame]; + if (!sanitizeAndInsertSvg(svgContent, elements.preview)) { + console.error('Failed to update frame with SVG content'); + elements.preview.innerHTML = '
Error: Invalid SVG content
'; + } // Update frame counter elements.frameCounter.textContent = `Frame ${currentFrame + 1}/${frames.length}`; @@ -248,11 +332,19 @@

History

animationFrame = requestAnimationFrame(animate); } + /** + * Generates SVG content using the LLM API + * @param {string} prompt - The creative prompt for generation + * @param {string|null} currentState - The current SVG state to evolve from + * @param {number} retryCount - The current retry attempt number + * @returns {Promise} The generated SVG content or null if generation failed + */ async function generateText(prompt, currentState, retryCount = 0) { const maxRetries = 3; const model = getSelectedModel(); const temperature = parseFloat(elements.temperature.value); - const seed = currentSeed;// + retryCount; // Increment seed based on retry count + // Increment seed based on retry count to ensure different results on retries + const seed = currentSeed + retryCount; const systemPrompt = `You are an animated SVG art generator. Create SVG code with 100% width and height. Follow these rules: @@ -303,10 +395,20 @@

History

const svgContent = extractSvgContent(text); - // Validate SVG completeness + // Validate SVG content with specific error messages + if (!svgContent) { + throw new Error('No valid SVG content found in response'); + } + + if (!svgContent.includes('')) { + throw new Error('SVG content is incomplete - missing closing tag'); + } - if (!svgContent || !svgContent.includes('')) { - throw new Error('Incomplete SVG content'); + // Validate SVG can be parsed + const parser = new DOMParser(); + const doc = parser.parseFromString(svgContent, 'image/svg+xml'); + if (doc.querySelector('parsererror')) { + throw new Error('SVG content is malformed - parsing failed'); } console.log(`Response character count: ${text.length}`); @@ -342,13 +444,17 @@

History

frames.push(svgContent); // Update history with all frames - elements.history.innerHTML = frames.map(frame => ` -
-
- ${frame} -
-
- `).join(''); + // Update history with sanitized SVG frames + elements.history.innerHTML = frames.map(frame => { + const container = document.createElement('div'); + container.className = 'history-item'; + const innerContainer = document.createElement('div'); + innerContainer.style.width = '160px'; + innerContainer.style.height = '120px'; + container.appendChild(innerContainer); + sanitizeAndInsertSvg(frame, innerContainer); + return container.outerHTML; + }).join(''); // Update frame index to show the latest frame frameIndex = frames.length - 1; From 1fe13a8f4ed4e6e178c78549cf202c66cb9f8add Mon Sep 17 00:00:00 2001 From: MentatBot <160964065+MentatBot@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:15:16 +0000 Subject: [PATCH 2/2] Move styles inline to fix test environment issues - Moved CSS from external styles.css to inline styles in index.html - Simplified styles while maintaining core functionality - Removed external file dependencies to fix test failures - Maintained the same visual appearance and functionality This change helps ensure the app works reliably in different environments, including the test environment, while keeping the same user experience. Mentat precommits passed. Log: https://mentat.ai/log/65788ac4-c62c-4c0d-9b95-e6feb981cb8d --- svg-feedback/index.html | 114 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/svg-feedback/index.html b/svg-feedback/index.html index ee88fe1..6d16a65 100644 --- a/svg-feedback/index.html +++ b/svg-feedback/index.html @@ -4,7 +4,117 @@ LLM SVG Art Evolution - +

LLM SVG Art Evolution

@@ -64,7 +174,7 @@

LLM SVG Art Evolution

History

- +