fix(code-editor): escape HTML in toolbar labels for security

This commit is contained in:
Haileyesus
2026-02-24 13:47:01 +03:00
parent a04eec409c
commit cc0dba435e

View File

@@ -39,6 +39,15 @@ const getExpandIcon = (isExpanded: boolean) => {
return '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />'; return '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />';
}; };
const escapeHtml = (value: string): string => (
value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
);
export const createEditorToolbarPanelExtension = ({ export const createEditorToolbarPanelExtension = ({
file, file,
showDiff, showDiff,
@@ -67,19 +76,31 @@ export const createEditorToolbarPanelExtension = ({
const chunkCount = chunks.length; const chunkCount = chunks.length;
const maxChunkIndex = Math.max(0, chunkCount - 1); const maxChunkIndex = Math.max(0, chunkCount - 1);
currentIndex = Math.max(0, Math.min(currentIndex, maxChunkIndex)); currentIndex = Math.max(0, Math.min(currentIndex, maxChunkIndex));
const escapedLabels = {
changes: escapeHtml(labels.changes),
previousChange: escapeHtml(labels.previousChange),
nextChange: escapeHtml(labels.nextChange),
hideDiff: escapeHtml(labels.hideDiff),
showDiff: escapeHtml(labels.showDiff),
collapse: escapeHtml(labels.collapse),
expand: escapeHtml(labels.expand),
};
// Icons are static SVG path fragments controlled by this module.
const diffVisibilityIcon = getDiffVisibilityIcon(showDiff);
const expandIcon = getExpandIcon(isExpanded);
let toolbarHtml = '<div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">'; let toolbarHtml = '<div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">';
toolbarHtml += '<div style="display: flex; align-items: center; gap: 8px;">'; toolbarHtml += '<div style="display: flex; align-items: center; gap: 8px;">';
if (hasDiff) { if (hasDiff) {
toolbarHtml += ` toolbarHtml += `
<span style="font-weight: 500;">${chunkCount > 0 ? `${currentIndex + 1}/${chunkCount}` : '0'} ${labels.changes}</span> <span style="font-weight: 500;">${chunkCount > 0 ? `${currentIndex + 1}/${chunkCount}` : '0'} ${escapedLabels.changes}</span>
<button class="cm-diff-nav-btn cm-diff-nav-prev" title="${labels.previousChange}" ${chunkCount === 0 ? 'disabled' : ''}> <button class="cm-diff-nav-btn cm-diff-nav-prev" title="${escapedLabels.previousChange}" ${chunkCount === 0 ? 'disabled' : ''}>
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
</svg> </svg>
</button> </button>
<button class="cm-diff-nav-btn cm-diff-nav-next" title="${labels.nextChange}" ${chunkCount === 0 ? 'disabled' : ''}> <button class="cm-diff-nav-btn cm-diff-nav-next" title="${escapedLabels.nextChange}" ${chunkCount === 0 ? 'disabled' : ''}>
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg> </svg>
@@ -92,9 +113,9 @@ export const createEditorToolbarPanelExtension = ({
if (file.diffInfo) { if (file.diffInfo) {
toolbarHtml += ` toolbarHtml += `
<button class="cm-toolbar-btn cm-toggle-diff-btn" title="${showDiff ? labels.hideDiff : labels.showDiff}"> <button class="cm-toolbar-btn cm-toggle-diff-btn" title="${showDiff ? escapedLabels.hideDiff : escapedLabels.showDiff}">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
${getDiffVisibilityIcon(showDiff)} ${diffVisibilityIcon}
</svg> </svg>
</button> </button>
`; `;
@@ -112,9 +133,9 @@ export const createEditorToolbarPanelExtension = ({
if (isSidebar && onToggleExpand) { if (isSidebar && onToggleExpand) {
toolbarHtml += ` toolbarHtml += `
<button class="cm-toolbar-btn cm-expand-btn" title="${isExpanded ? labels.collapse : labels.expand}"> <button class="cm-toolbar-btn cm-expand-btn" title="${isExpanded ? escapedLabels.collapse : escapedLabels.expand}">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
${getExpandIcon(isExpanded)} ${expandIcon}
</svg> </svg>
</button> </button>
`; `;