From 113d663fce0ad96f0dd586bf009f613d479a1254 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Sat, 7 Feb 2026 13:04:52 -0800 Subject: [PATCH] fix: resolve 15 GitHub code scanning security alerts - Fix XSS in showFullPrompt() across admin.js, gallery.js, metadata.html by replacing HTML string interpolation with DOM manipulation (createElement + textContent), eliminating unsafe user content injection - Fix stack trace exposure in py/api error responses by replacing str(e) with generic messages and adding logger.exception() for server-side traceability --- py/api/__init__.py | 12 +++++--- py/api/admin.py | 5 ++-- py/api/autotag_routes.py | 8 +++--- py/api/images.py | 5 ++-- web/js/admin.js | 59 ++++++++++++++++++++++++++-------------- web/js/gallery.js | 28 ++++++++++++------- web/metadata.html | 34 ++++++++++++++++------- 7 files changed, 97 insertions(+), 54 deletions(-) diff --git a/py/api/__init__.py b/py/api/__init__.py index 2495df7..457565d 100644 --- a/py/api/__init__.py +++ b/py/api/__init__.py @@ -199,8 +199,9 @@ class PromptManagerAPI( ) except Exception as e: + self.logger.exception("Failed to load web UI") return web.Response( - text=f"

Error

Failed to load web UI: {str(e)}

", + text="

Error

Failed to load web UI. Check server logs for details.

", content_type="text/html", status=500, ) @@ -232,8 +233,9 @@ class PromptManagerAPI( ) except Exception as e: + self.logger.exception("Failed to load gallery") return web.Response( - text=f"

Error

Failed to load gallery: {str(e)}

", + text="

Error

Failed to load gallery. Check server logs for details.

", content_type="text/html", status=500, ) @@ -265,8 +267,9 @@ class PromptManagerAPI( ) except Exception as e: + self.logger.exception("Failed to load admin UI") return web.Response( - text=f"

Error

Failed to load admin UI: {str(e)}

", + text="

Error

Failed to load admin UI. Check server logs for details.

", content_type="text/html", status=500, ) @@ -298,8 +301,9 @@ class PromptManagerAPI( ) except Exception as e: + self.logger.exception("Failed to load gallery") return web.Response( - text=f"

Error

Failed to load gallery: {str(e)}

", + text="

Error

Failed to load gallery. Check server logs for details.

", content_type="text/html", status=500, ) diff --git a/py/api/admin.py b/py/api/admin.py index 09de150..f96d9c3 100644 --- a/py/api/admin.py +++ b/py/api/admin.py @@ -1116,9 +1116,8 @@ class AdminRoutesMixin: yield f"data: {json.dumps({'type': 'complete', 'processed': processed_count, 'found': found_count, 'added': added_count, 'linked': linked_count})}\n\n" except Exception as e: - self.logger.error(f"Scan error: {e}") - self.logger.error(f"Scan error traceback: {traceback.format_exc()}") - yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n" + self.logger.exception("Scan error") + yield f"data: {json.dumps({'type': 'error', 'message': 'An internal error occurred. Check server logs for details.'})}\n\n" response = web.StreamResponse( status=200, diff --git a/py/api/autotag_routes.py b/py/api/autotag_routes.py index 2864570..5b95679 100644 --- a/py/api/autotag_routes.py +++ b/py/api/autotag_routes.py @@ -93,8 +93,8 @@ class AutotagRoutesMixin: yield f"data: {json.dumps({'type': 'error', 'message': 'Download failed'})}\n\n" except Exception as e: - self.logger.error(f"Download model error: {e}") - yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n" + self.logger.exception("Download model error") + yield f"data: {json.dumps({'type': 'error', 'message': 'An internal error occurred. Check server logs for details.'})}\n\n" response = web.StreamResponse( status=200, @@ -274,8 +274,8 @@ class AutotagRoutesMixin: self.logger.error(f"AutoTag error: {e}") import traceback - self.logger.error(f"AutoTag traceback: {traceback.format_exc()}") - yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n" + self.logger.exception("AutoTag error") + yield f"data: {json.dumps({'type': 'error', 'message': 'An internal error occurred. Check server logs for details.'})}\n\n" response = web.StreamResponse( status=200, diff --git a/py/api/images.py b/py/api/images.py index 599acaf..2c2da5b 100644 --- a/py/api/images.py +++ b/py/api/images.py @@ -817,11 +817,12 @@ class ImageRoutesMixin: ) except Exception as e: + self.logger.exception("Thumbnail generation failed") await send_progress( "error", { - "error": str(e), - "message": f"Thumbnail generation failed: {str(e)}", + "error": "An internal error occurred", + "message": "Thumbnail generation failed. Check server logs for details.", }, ) diff --git a/web/js/admin.js b/web/js/admin.js index 11f0b5e..60106e3 100644 --- a/web/js/admin.js +++ b/web/js/admin.js @@ -2539,19 +2539,28 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; showFullPrompt(type) { if (!this.currentMetadata) return; - + const prompt = type === 'positive' ? this.currentMetadata.positivePrompt : this.currentMetadata.negativePrompt; + const safePrompt = this.escapeHtml(prompt); + const safeType = this.escapeHtml(type.charAt(0).toUpperCase() + type.slice(1)); const newWindow = window.open('', '_blank'); - newWindow.document.write(` - - ${type.charAt(0).toUpperCase() + type.slice(1)} Prompt - -

${type.charAt(0).toUpperCase() + type.slice(1)} Prompt

-
${prompt}
- - - - `); + const doc = newWindow.document; + doc.open(); + doc.write('' + safeType + ' Prompt'); + doc.close(); + doc.body.style.cssText = 'background:#111;color:#fff;font-family:monospace;padding:20px;'; + const h2 = doc.createElement('h2'); + h2.textContent = type.charAt(0).toUpperCase() + type.slice(1) + ' Prompt'; + doc.body.appendChild(h2); + const pre = doc.createElement('pre'); + pre.style.cssText = 'background:#222;padding:15px;border-radius:5px;white-space:pre-wrap;line-height:1.5;'; + pre.textContent = prompt; + doc.body.appendChild(pre); + const btn = doc.createElement('button'); + btn.textContent = 'Copy to Clipboard'; + btn.style.cssText = 'margin-top:20px;padding:10px 20px;background:#444;color:#fff;border:none;border-radius:5px;cursor:pointer;'; + btn.addEventListener('click', () => { navigator.clipboard.writeText(pre.textContent).then(() => alert('Copied!')); }); + doc.body.appendChild(btn); } showWorkflowData() { @@ -3182,17 +3191,25 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; showFullPrompt(type) { if (this.currentMetadata) { const prompt = type === 'positive' ? this.currentMetadata.positivePrompt : this.currentMetadata.negativePrompt; + const safeType = this.escapeHtml(type.charAt(0).toUpperCase() + type.slice(1)); const newWindow = window.open('', '_blank'); - newWindow.document.write(` - - ${type.charAt(0).toUpperCase() + type.slice(1)} Prompt - -

${type.charAt(0).toUpperCase() + type.slice(1)} Prompt

-
${prompt}
- - - - `); + const doc = newWindow.document; + doc.open(); + doc.write('' + safeType + ' Prompt'); + doc.close(); + doc.body.style.cssText = 'background:#111;color:#fff;font-family:monospace;padding:20px;'; + const h2 = doc.createElement('h2'); + h2.textContent = type.charAt(0).toUpperCase() + type.slice(1) + ' Prompt'; + doc.body.appendChild(h2); + const pre = doc.createElement('pre'); + pre.style.cssText = 'background:#222;padding:15px;border-radius:5px;white-space:pre-wrap;line-height:1.5;'; + pre.textContent = prompt; + doc.body.appendChild(pre); + const btn = doc.createElement('button'); + btn.textContent = 'Copy to Clipboard'; + btn.style.cssText = 'margin-top:20px;padding:10px 20px;background:#444;color:#fff;border:none;border-radius:5px;cursor:pointer;'; + btn.addEventListener('click', () => { navigator.clipboard.writeText(pre.textContent).then(() => alert('Copied!')); }); + doc.body.appendChild(btn); } } diff --git a/web/js/gallery.js b/web/js/gallery.js index 0d4e54b..2f6d0cf 100644 --- a/web/js/gallery.js +++ b/web/js/gallery.js @@ -1197,17 +1197,25 @@ showFullPrompt(type) { if (this.currentMetadata) { const prompt = type === 'positive' ? this.currentMetadata.positivePrompt : this.currentMetadata.negativePrompt; + const safeType = this.escapeHtml(type.charAt(0).toUpperCase() + type.slice(1)); const newWindow = window.open('', '_blank'); - newWindow.document.write(` - - ${type.charAt(0).toUpperCase() + type.slice(1)} Prompt - -

${type.charAt(0).toUpperCase() + type.slice(1)} Prompt

-
${prompt}
- - - - `); + const doc = newWindow.document; + doc.open(); + doc.write('' + safeType + ' Prompt'); + doc.close(); + doc.body.style.cssText = 'background:#111;color:#fff;font-family:monospace;padding:20px;'; + const h2 = doc.createElement('h2'); + h2.textContent = type.charAt(0).toUpperCase() + type.slice(1) + ' Prompt'; + doc.body.appendChild(h2); + const pre = doc.createElement('pre'); + pre.style.cssText = 'background:#222;padding:15px;border-radius:5px;white-space:pre-wrap;line-height:1.5;'; + pre.textContent = prompt; + doc.body.appendChild(pre); + const btn = doc.createElement('button'); + btn.textContent = 'Copy to Clipboard'; + btn.style.cssText = 'margin-top:20px;padding:10px 20px;background:#444;color:#fff;border:none;border-radius:5px;cursor:pointer;'; + btn.addEventListener('click', () => { navigator.clipboard.writeText(pre.textContent).then(() => alert('Copied!')); }); + doc.body.appendChild(btn); } } diff --git a/web/metadata.html b/web/metadata.html index 294d578..930a65e 100644 --- a/web/metadata.html +++ b/web/metadata.html @@ -477,20 +477,34 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`; }; } + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + function showFullPrompt(type) { if (currentWorkflowData) { const prompt = type === 'positive' ? currentWorkflowData.positivePrompt : currentWorkflowData.negativePrompt; + const safeType = escapeHtml(type.charAt(0).toUpperCase() + type.slice(1)); const newWindow = window.open('', '_blank'); - newWindow.document.write(` - - ${type.charAt(0).toUpperCase() + type.slice(1)} Prompt - -

${type.charAt(0).toUpperCase() + type.slice(1)} Prompt

-
${prompt}
- - - - `); + const doc = newWindow.document; + doc.open(); + doc.write('' + safeType + ' Prompt'); + doc.close(); + doc.body.style.cssText = 'background:#111;color:#fff;font-family:monospace;padding:20px;'; + const h2 = doc.createElement('h2'); + h2.textContent = type.charAt(0).toUpperCase() + type.slice(1) + ' Prompt'; + doc.body.appendChild(h2); + const pre = doc.createElement('pre'); + pre.style.cssText = 'background:#222;padding:15px;border-radius:5px;white-space:pre-wrap;line-height:1.5;'; + pre.textContent = prompt; + doc.body.appendChild(pre); + const btn = doc.createElement('button'); + btn.textContent = 'Copy to Clipboard'; + btn.style.cssText = 'margin-top:20px;padding:10px 20px;background:#444;color:#fff;border:none;border-radius:5px;cursor:pointer;'; + btn.addEventListener('click', function() { navigator.clipboard.writeText(pre.textContent).then(function() { alert('Copied!'); }); }); + doc.body.appendChild(btn); } }