fix: escape remaining server values rendered through innerHTML

Maintenance and diagnostics results, review-tag errors, video info and
metadata error messages, consistency issues, recent image filenames,
output directory names, the prompt rating attribute, gallery media types
and duplicate group headers were interpolated unescaped. The copy
fallback modal also inlined the prompt text inside <textarea>, which a
closing tag could break out of; it now sets textarea.value.
This commit is contained in:
Vito Sansevero
2026-10-08 18:01:34 -07:00
parent e709a158e6
commit 0f9569cdd8
2 changed files with 28 additions and 27 deletions
+21 -20
View File
@@ -621,7 +621,7 @@
<span>${escapeHtml(runLabel)}</span>
</div>` : ""}
<div class="flex items-center space-x-1">
<div class="rating flex space-x-1" data-id="${prompt.id}" data-rating="${rating}">
<div class="rating flex space-x-1" data-id="${escapeHtml(prompt.id)}" data-rating="${escapeHtml(rating)}">
${this.renderStars(rating, prompt.id)}
</div>
</div>
@@ -1636,7 +1636,7 @@
resultsContainer.innerHTML = `
<div class="bg-pm-error-tint border border-pm-error rounded-pm-sm p-4">
<h4 class="text-pm-error font-medium mb-2">❌ Maintenance Failed</h4>
<p class="text-pm-secondary">${error.message}</p>
<p class="text-pm-secondary">${escapeHtml(error.message)}</p>
</div>
`;
this.showNotification('❌ Maintenance failed', 'error');
@@ -1658,7 +1658,7 @@
html += `
<div class="flex justify-between items-center p-2 bg-pm-input rounded">
<span class="text-pm-secondary">Operations Completed</span>
<span class="text-pm-success font-mono text-sm">${data.operations_completed}</span>
<span class="text-pm-success font-mono text-sm">${escapeHtml(data.operations_completed)}</span>
</div>
`;
html += `
@@ -1680,29 +1680,29 @@
html += `<div class="${bgColor} border rounded-pm-sm p-4">`;
html += `<h4 class="text-pm font-medium mb-2 flex items-center space-x-2">`;
html += `<span>${statusIcon}</span>`;
html += `<span class="capitalize">${operation.replace(/_/g, ' ')}</span>`;
html += `<span class="capitalize">${escapeHtml(operation.replace(/_/g, ' '))}</span>`;
html += `<span class="text-sm font-mono ${result.success ? 'text-pm-success' : 'text-pm-error'}">${statusText}</span>`;
html += `</h4>`;
if (result.message) {
html += `<p class="text-pm-secondary mb-2">${result.message}</p>`;
html += `<p class="text-pm-secondary mb-2">${escapeHtml(result.message)}</p>`;
}
// Show specific details
if (result.removed_count !== undefined) {
html += `<div class="text-sm text-pm-secondary">Items removed: ${result.removed_count}</div>`;
html += `<div class="text-sm text-pm-secondary">Items removed: ${escapeHtml(result.removed_count)}</div>`;
}
if (result.duplicate_hashes !== undefined) {
html += `<div class="text-sm text-pm-secondary">Duplicate hash groups found: ${result.duplicate_hashes}</div>`;
html += `<div class="text-sm text-pm-secondary">Duplicate hash groups found: ${escapeHtml(result.duplicate_hashes)}</div>`;
}
if (result.issues_found !== undefined) {
html += `<div class="text-sm text-pm-secondary">Issues found: ${result.issues_found}</div>`;
html += `<div class="text-sm text-pm-secondary">Issues found: ${escapeHtml(result.issues_found)}</div>`;
if (result.issues && result.issues.length > 0) {
html += `<ul class="ml-4 list-disc text-xs text-pm-muted mt-1">`;
result.issues.forEach(issue => {
html += `<li>${issue}</li>`;
html += `<li>${escapeHtml(issue)}</li>`;
});
html += `</ul>`;
}
@@ -1710,14 +1710,14 @@
if (result.info) {
html += `<div class="text-sm text-pm-secondary mt-2">`;
html += `<p>Total prompts: ${result.info.total_prompts || 'N/A'}</p>`;
html += `<p>Database size: ${result.info.database_size_bytes ? this.formatFileSize(result.info.database_size_bytes) : 'N/A'}</p>`;
html += `<p>Total prompts: ${escapeHtml(result.info.total_prompts || 'N/A')}</p>`;
html += `<p>Database size: ${escapeHtml(result.info.database_size_bytes ? this.formatFileSize(result.info.database_size_bytes) : 'N/A')}</p>`;
html += `</div>`;
}
if (result.error) {
html += `<div class="text-sm text-pm-error mt-2 font-mono bg-pm-error-tint p-2 rounded">`;
html += `Error: ${result.error}`;
html += `Error: ${escapeHtml(result.error)}`;
html += `</div>`;
}
@@ -1754,7 +1754,7 @@
content.innerHTML = `
<div class="bg-pm-error-tint border border-pm-error rounded-pm-sm p-4">
<h4 class="text-pm-error font-medium mb-2">❌ Diagnostics Failed</h4>
<p class="text-pm-secondary">${error.message}</p>
<p class="text-pm-secondary">${escapeHtml(error.message)}</p>
</div>
`;
}
@@ -1793,25 +1793,25 @@
html += `<h4 class="text-pm font-medium mb-2 capitalize">${escapeHtml(category)}</h4>`;
if (result.message) {
html += `<p class="text-pm-secondary mb-2">${result.message}</p>`;
html += `<p class="text-pm-secondary mb-2">${escapeHtml(result.message)}</p>`;
}
// Show specific details based on category
if (category === 'database' && result.status === 'ok') {
html += `<div class="text-sm text-pm-secondary">`;
html += `<p>Prompts: ${result.prompt_count || 0}</p>`;
html += `<p>Prompts: ${escapeHtml(result.prompt_count || 0)}</p>`;
html += `<p>Images table: ${result.has_images_table ? 'Yes' : 'No'}</p>`;
html += `</div>`;
}
if (category === 'images_table' && result.status === 'ok') {
html += `<div class="text-sm text-pm-secondary">`;
html += `<p>Images: ${result.image_count || 0}</p>`;
html += `<p>Images: ${escapeHtml(result.image_count || 0)}</p>`;
if (result.recent_images && result.recent_images.length > 0) {
html += `<p>Recent images:</p>`;
html += `<ul class="ml-4 list-disc">`;
result.recent_images.slice(0, 3).forEach(img => {
html += `<li>${img.filename} → Prompt ${img.prompt_id}</li>`;
html += `<li>${escapeHtml(img.filename)} → Prompt ${escapeHtml(img.prompt_id)}</li>`;
});
html += `</ul>`;
}
@@ -1823,7 +1823,7 @@
html += `<p>Output directories found:</p>`;
html += `<ul class="ml-4 list-disc">`;
result.output_dirs.forEach(dir => {
html += `<li>${dir}</li>`;
html += `<li>${escapeHtml(dir)}</li>`;
});
html += `</ul>`;
html += `</div>`;
@@ -2044,7 +2044,7 @@
<div class="bg-pm-surface rounded-pm-md p-4 max-w-2xl w-full mx-4 border border-pm">
<h3 class="text-sm font-semibold text-pm mb-3">Copy Prompt Text</h3>
<p class="text-pm-secondary mb-4">Please manually copy the text below:</p>
<textarea readonly class="w-full h-32 px-4 py-3 bg-pm-surface border border-pm rounded-pm-sm text-pm resize-none" style="font-family: monospace;">${text}</textarea>
<textarea readonly class="w-full h-32 px-4 py-3 bg-pm-surface border border-pm rounded-pm-sm text-pm resize-none" style="font-family: monospace;"></textarea>
<div class="flex justify-end mt-4">
<button class="px-4 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm font-medium rounded-pm-sm transition-colors" onclick="this.closest('[class*=fixed]').remove()">
Close
@@ -2057,6 +2057,7 @@
// Auto-select the text in the textarea
const textarea = modal.querySelector('textarea');
textarea.value = text;
textarea.focus();
textarea.select();
@@ -3696,7 +3697,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
this.renderReviewTags();
} else {
document.getElementById('reviewTagsVisible').innerHTML =
`<div class="text-pm-error">Error: ${data.error}</div>`;
`<div class="text-pm-error">Error: ${escapeHtml(data.error)}</div>`;
}
} catch (error) {
console.error('Error generating tags:', error);
+7 -7
View File
@@ -429,7 +429,7 @@
data-original="${escapeHtml(image.url)}"
data-thumbnail="${escapeHtml(image.thumbnail_url || '')}"
data-caption="${escapeHtml(this.formatImageCaption(image))}"
data-media-type="${mediaType}"
data-media-type="${escapeHtml(mediaType)}"
data-is-video="${isVideo}"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex'">
<!-- Fallback for failed thumbnails -->
@@ -488,7 +488,7 @@
data-original="${escapeHtml(image.url)}"
data-thumbnail="${escapeHtml(image.thumbnail_url || '')}"
data-caption="${escapeHtml(this.formatImageCaption(image))}"
data-media-type="${mediaType}"
data-media-type="${escapeHtml(mediaType)}"
data-is-video="${isVideo}"
onerror="this.style.display='none'">
${hasThumb ? '<div class="absolute top-1 right-1 w-2 h-2 bg-pm-success rounded-full" title="Thumbnail available"></div>' : ''}
@@ -1018,7 +1018,7 @@
${escapeHtml(fileName)}
</div>
<div class="text-xs text-pm-muted">
Type: ${fileExt} Video
Type: ${escapeHtml(fileExt)} Video
</div>
</div>
@@ -1094,7 +1094,7 @@
container.innerHTML = `
<div class="bg-pm-error-tint border border-pm-error rounded-pm-md p-4">
<h4 class="text-pm-error font-medium mb-2">❌ Video Info Error</h4>
<p class="text-pm-secondary text-sm">${error.message}</p>
<p class="text-pm-secondary text-sm">${escapeHtml(error.message)}</p>
</div>
`;
}
@@ -1188,7 +1188,7 @@
container.innerHTML = `
<div class="bg-pm-error-tint border border-pm-error rounded-pm-md p-4">
<h4 class="text-pm-error font-medium mb-2">❌ Metadata Error</h4>
<p class="text-pm-secondary text-sm">${error.message}</p>
<p class="text-pm-secondary text-sm">${escapeHtml(error.message)}</p>
</div>
`;
}
@@ -2152,10 +2152,10 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
groupEl.innerHTML = `
<div class="mb-3 text-sm font-medium text-pm-secondary">
Duplicate Group ${groupIndex + 1} (${group.images.length} identical ${group.images[0].media_type}s)
Duplicate Group ${groupIndex + 1} (${group.images.length} identical ${escapeHtml(group.images[0].media_type)}s)
</div>
<div class="mb-3 p-3 bg-pm-primary rounded text-sm text-pm-secondary">
<strong>Content Hash:</strong> ${group.hash.substring(0, 16)}...
<strong>Content Hash:</strong> ${escapeHtml(String(group.hash || '').substring(0, 16))}...
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
${group.images.map((image, imageIndex) => `