diff --git a/static/js/components/shared/ModelModal.js b/static/js/components/shared/ModelModal.js
index 5c7493ed..84e1dbb3 100644
--- a/static/js/components/shared/ModelModal.js
+++ b/static/js/components/shared/ModelModal.js
@@ -14,7 +14,7 @@ import {
} from './ModelMetadata.js';
import { setupTagEditMode } from './ModelTags.js';
import { getModelApiClient } from '../../api/modelApiFactory.js';
-import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js';
+import { renderCompactTags, setupTagTooltip, formatFileSize, escapeAttribute, escapeHtml } from './utils.js';
import { renderTriggerWords, setupTriggerWordsEditMode } from './TriggerWords.js';
import { parsePresets, renderPresetTags } from './PresetTags.js';
import { initVersionsTab } from './ModelVersionsTab.js';
@@ -65,12 +65,6 @@ function hasLicenseField(license, field) {
return Object.prototype.hasOwnProperty.call(license || {}, field);
}
-function escapeAttribute(value) {
- return String(value ?? '')
- .replace(/&/g, '&')
- .replace(/"/g, '"');
-}
-
function indentMarkup(markup, spaces) {
if (!markup) {
return '';
@@ -266,9 +260,11 @@ export async function showModelModal(model, modelType) {
...model,
civitai: completeCivitaiData
};
+ const escapedFilePathAttr = escapeAttribute(modelWithFullData.file_path || '');
+ const escapedFolderPath = escapeHtml((modelWithFullData.file_path || '').replace(/[^/]+$/, '') || 'N/A');
const licenseIcons = renderLicenseIcons(modelWithFullData);
const viewOnCivitaiAction = modelWithFullData.from_civitai ? `
-
+
${translate('modals.model.actions.viewOnCivitaiText', {}, 'View on Civitai')}
`.trim() : '';
const creatorInfoAction = modelWithFullData.civitai?.creator ? `
@@ -472,8 +468,8 @@ export async function showModelModal(model, modelType) {
- ${modelWithFullData.file_path.replace(/[^/]+$/, '') || 'N/A'}
+ data-filepath="${escapedFilePathAttr}">
+ ${escapedFolderPath}
@@ -506,7 +502,7 @@ export async function showModelModal(model, modelType) {
-
+
${tabsContent}
diff --git a/static/js/components/shared/TriggerWords.js b/static/js/components/shared/TriggerWords.js
index dd82b3d6..dfc1cb17 100644
--- a/static/js/components/shared/TriggerWords.js
+++ b/static/js/components/shared/TriggerWords.js
@@ -6,6 +6,7 @@
import { showToast, copyToClipboard } from '../../utils/uiHelpers.js';
import { translate } from '../../utils/i18nHelpers.js';
import { getModelApiClient } from '../../api/modelApiFactory.js';
+import { escapeAttribute } from './utils.js';
/**
* Fetch trained words for a model
@@ -180,11 +181,12 @@ function createSuggestionDropdown(trainedWords, classTokens, existingWords = [])
* @returns {string} HTML content
*/
export function renderTriggerWords(words, filePath) {
+ const safeFilePath = escapeAttribute(filePath || '');
if (!words.length) return `
@@ -207,7 +209,7 @@ export function renderTriggerWords(words, filePath) {
@@ -647,4 +649,4 @@ window.copyTriggerWord = async function(word) {
console.error('Copy failed:', err);
showToast('toast.triggerWords.copyFailed', {}, 'error');
}
-};
\ No newline at end of file
+};
diff --git a/static/js/components/shared/utils.js b/static/js/components/shared/utils.js
index 0e5a5c1f..a0226e05 100644
--- a/static/js/components/shared/utils.js
+++ b/static/js/components/shared/utils.js
@@ -3,6 +3,20 @@
* Helper functions for the Model Modal component - General version
*/
+export function escapeHtml(value = '') {
+ if (value === null || value === undefined) return '';
+ return String(value)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
+export function escapeAttribute(value = '') {
+ return escapeHtml(value);
+}
+
/**
* Format file size
* @param {number} bytes - Number of bytes
@@ -31,6 +45,7 @@ export function formatFileSize(bytes) {
export function renderCompactTags(tags, filePath = '') {
// Remove the early return and always render the container
const tagsList = tags || [];
+ const safeFilePath = escapeAttribute(filePath || '');
// Display up to 5 tags, with a tooltip indicator if there are more
const visibleTags = tagsList.slice(0, 5);
@@ -40,20 +55,20 @@ export function renderCompactTags(tags, filePath = '') {