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
This commit is contained in:
Vito Sansevero
2026-02-07 13:04:52 -08:00
parent 85aff0179a
commit 113d663fce
7 changed files with 97 additions and 54 deletions
+8 -4
View File
@@ -199,8 +199,9 @@ class PromptManagerAPI(
)
except Exception as e:
self.logger.exception("Failed to load web UI")
return web.Response(
text=f"<h1>Error</h1><p>Failed to load web UI: {str(e)}</p>",
text="<h1>Error</h1><p>Failed to load web UI. Check server logs for details.</p>",
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"<h1>Error</h1><p>Failed to load gallery: {str(e)}</p>",
text="<h1>Error</h1><p>Failed to load gallery. Check server logs for details.</p>",
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"<h1>Error</h1><p>Failed to load admin UI: {str(e)}</p>",
text="<h1>Error</h1><p>Failed to load admin UI. Check server logs for details.</p>",
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"<h1>Error</h1><p>Failed to load gallery: {str(e)}</p>",
text="<h1>Error</h1><p>Failed to load gallery. Check server logs for details.</p>",
content_type="text/html",
status=500,
)
+2 -3
View File
@@ -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,
+4 -4
View File
@@ -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,
+3 -2
View File
@@ -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.",
},
)
+38 -21
View File
@@ -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(`
<html>
<head><title>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</title></head>
<body style="background: #111; color: #fff; font-family: monospace; padding: 20px;">
<h2>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</h2>
<div style="background: #222; padding: 15px; border-radius: 5px; white-space: pre-wrap; line-height: 1.5;">${prompt}</div>
<button onclick="navigator.clipboard.writeText(\`${prompt.replace(/`/g, '\\`')}\`).then(() => alert('Copied!'))" style="margin-top: 20px; padding: 10px 20px; background: #444; color: #fff; border: none; border-radius: 5px; cursor: pointer;">Copy to Clipboard</button>
</body>
</html>
`);
const doc = newWindow.document;
doc.open();
doc.write('<!DOCTYPE html><html><head><title>' + safeType + ' Prompt</title></head><body></body></html>');
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(`
<html>
<head><title>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</title></head>
<body style="background: #111; color: #fff; font-family: monospace; padding: 20px;">
<h2>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</h2>
<div style="background: #222; padding: 15px; border-radius: 5px; white-space: pre-wrap; line-height: 1.5;">${prompt}</div>
<button onclick="navigator.clipboard.writeText(\`${prompt.replace(/`/g, '\\`')}\`).then(() => alert('Copied!'))" style="margin-top: 20px; padding: 10px 20px; background: #444; color: #fff; border: none; border-radius: 5px; cursor: pointer;">Copy to Clipboard</button>
</body>
</html>
`);
const doc = newWindow.document;
doc.open();
doc.write('<!DOCTYPE html><html><head><title>' + safeType + ' Prompt</title></head><body></body></html>');
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);
}
}
+18 -10
View File
@@ -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(`
<html>
<head><title>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</title></head>
<body style="background: #111; color: #fff; font-family: monospace; padding: 20px;">
<h2>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</h2>
<div style="background: #222; padding: 15px; border-radius: 5px; white-space: pre-wrap; line-height: 1.5;">${prompt}</div>
<button onclick="navigator.clipboard.writeText(\`${prompt.replace(/`/g, '\\`')}\`).then(() => alert('Copied!'))" style="margin-top: 20px; padding: 10px 20px; background: #444; color: #fff; border: none; border-radius: 5px; cursor: pointer;">Copy to Clipboard</button>
</body>
</html>
`);
const doc = newWindow.document;
doc.open();
doc.write('<!DOCTYPE html><html><head><title>' + safeType + ' Prompt</title></head><body></body></html>');
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);
}
}
+24 -10
View File
@@ -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(`
<html>
<head><title>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</title></head>
<body style="background: #111; color: #fff; font-family: monospace; padding: 20px;">
<h2>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</h2>
<div style="background: #222; padding: 15px; border-radius: 5px; white-space: pre-wrap; line-height: 1.5;">${prompt}</div>
<button onclick="navigator.clipboard.writeText(\`${prompt.replace(/`/g, '\\`')}\`).then(() => alert('Copied!'))" style="margin-top: 20px; padding: 10px 20px; background: #444; color: #fff; border: none; border-radius: 5px; cursor: pointer;">Copy to Clipboard</button>
</body>
</html>
`);
const doc = newWindow.document;
doc.open();
doc.write('<!DOCTYPE html><html><head><title>' + safeType + ' Prompt</title></head><body></body></html>');
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);
}
}