mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-11 05:13:01 +00:00
fix: Add HTML escaping to prevent XSS in fallback code and checkbox-group
Add proper HTML escaping for schema-derived values to prevent XSS vulnerabilities in fallback rendering code and checkbox-group widget. Problem: - Fallback code in generateFieldHtml (line 3094) doesn't escape propLabel when building HTML strings, while main renderArrayObjectItem uses escapeHtml() - Checkbox-group widget (lines 3012-3025) doesn't escape option or label values - While risk is limited (values come from plugin schemas), malicious plugin schemas or untrusted schema sources could inject XSS - Inconsistent with main renderArrayObjectItem which properly escapes Solution: - Added escapeHtml() calls for propLabel in fallback array-of-objects rendering (both locations: generateFieldHtml and addArrayObjectItem fallback) - Added escapeHtml() calls for option values in checkbox-group widget: - checkboxId (contains option) - data-option-value attribute - value attribute - label text in span - Ensures consistent XSS protection across all rendering paths This prevents potential XSS if plugin schemas contain malicious HTML/script content in enum values or property titles.
This commit is contained in:
@@ -3012,18 +3012,18 @@ function generateFieldHtml(key, prop, value, prefix = '') {
|
|||||||
enumItems.forEach((option) => {
|
enumItems.forEach((option) => {
|
||||||
const isChecked = arrayValue.includes(option);
|
const isChecked = arrayValue.includes(option);
|
||||||
const label = labels[option] || option.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
const label = labels[option] || option.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||||
const checkboxId = `${fieldId}_${option}`;
|
const checkboxId = `${fieldId}_${escapeHtml(option)}`;
|
||||||
html += `
|
html += `
|
||||||
<label class="flex items-center">
|
<label class="flex items-center">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
id="${checkboxId}"
|
id="${checkboxId}"
|
||||||
data-checkbox-group="${fieldId}"
|
data-checkbox-group="${fieldId}"
|
||||||
data-option-value="${option}"
|
data-option-value="${escapeHtml(option)}"
|
||||||
value="${option}"
|
value="${escapeHtml(option)}"
|
||||||
${isChecked ? 'checked' : ''}
|
${isChecked ? 'checked' : ''}
|
||||||
onchange="updateCheckboxGroupData('${fieldId}')"
|
onchange="updateCheckboxGroupData('${fieldId}')"
|
||||||
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||||
<span class="ml-2 text-sm text-gray-700">${label}</span>
|
<span class="ml-2 text-sm text-gray-700">${escapeHtml(label)}</span>
|
||||||
</label>
|
</label>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
@@ -3092,7 +3092,7 @@ function generateFieldHtml(key, prop, value, prefix = '') {
|
|||||||
const propSchema = itemProperties[propKey];
|
const propSchema = itemProperties[propKey];
|
||||||
const propValue = item[propKey] !== undefined ? item[propKey] : propSchema.default;
|
const propValue = item[propKey] !== undefined ? item[propKey] : propSchema.default;
|
||||||
const propLabel = propSchema.title || propKey.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
const propLabel = propSchema.title || propKey.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||||
html += `<div class="mb-3"><label class="block text-sm font-medium text-gray-700 mb-1">${propLabel}</label>`;
|
html += `<div class="mb-3"><label class="block text-sm font-medium text-gray-700 mb-1">${escapeHtml(propLabel)}</label>`;
|
||||||
if (propSchema.type === 'boolean') {
|
if (propSchema.type === 'boolean') {
|
||||||
const checked = propValue ? 'checked' : '';
|
const checked = propValue ? 'checked' : '';
|
||||||
html += `<input type="checkbox" data-prop-key="${propKey}" ${checked} class="h-4 w-4 text-blue-600" onchange="window.updateArrayObjectData('${fieldId}')">`;
|
html += `<input type="checkbox" data-prop-key="${propKey}" ${checked} class="h-4 w-4 text-blue-600" onchange="window.updateArrayObjectData('${fieldId}')">`;
|
||||||
@@ -6497,7 +6497,7 @@ if (typeof window !== 'undefined') {
|
|||||||
const propSchema = itemsSchema.properties[propKey];
|
const propSchema = itemsSchema.properties[propKey];
|
||||||
const propValue = newItem[propKey] !== undefined ? newItem[propKey] : propSchema.default;
|
const propValue = newItem[propKey] !== undefined ? newItem[propKey] : propSchema.default;
|
||||||
const propLabel = propSchema.title || propKey.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
const propLabel = propSchema.title || propKey.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||||
itemHtml += `<div class="mb-3"><label class="block text-sm font-medium text-gray-700 mb-1">${propLabel}</label>`;
|
itemHtml += `<div class="mb-3"><label class="block text-sm font-medium text-gray-700 mb-1">${escapeHtml(propLabel)}</label>`;
|
||||||
if (propSchema.type === 'boolean') {
|
if (propSchema.type === 'boolean') {
|
||||||
const checked = propValue ? 'checked' : '';
|
const checked = propValue ? 'checked' : '';
|
||||||
// No name attribute - rely solely on _data field to prevent key leakage
|
// No name attribute - rely solely on _data field to prevent key leakage
|
||||||
|
|||||||
Reference in New Issue
Block a user