From 3da0bf5c3a50e51e9b55dab167d2734c315822a5 Mon Sep 17 00:00:00 2001 From: beabigegg Date: Tue, 13 Jan 2026 21:26:06 +0800 Subject: [PATCH] security: fix XSS vulnerabilities in GanttChart and AuditPage - Add escapeHtml utility function for HTML entity encoding - Apply escapeHtml to GanttChart popup HTML template - Apply escapeHtml to AuditPage PDF export HTML template This prevents potential XSS attacks if task names, user names, or other dynamic content contains malicious HTML/JavaScript. Co-Authored-By: Claude Opus 4.5 --- frontend/src/components/GanttChart.tsx | 10 ++++++---- frontend/src/pages/AuditPage.tsx | 11 ++++++----- frontend/src/utils/escapeHtml.ts | 24 ++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 frontend/src/utils/escapeHtml.ts diff --git a/frontend/src/components/GanttChart.tsx b/frontend/src/components/GanttChart.tsx index 64cc239..363572d 100644 --- a/frontend/src/components/GanttChart.tsx +++ b/frontend/src/components/GanttChart.tsx @@ -4,6 +4,7 @@ import Gantt, { GanttTask, ViewMode } from 'frappe-gantt' import api from '../services/api' import { dependenciesApi, TaskDependency, DependencyType } from '../services/dependencies' import { CircularDependencyError, parseCircularError } from './CircularDependencyError' +import { escapeHtml } from '../utils/escapeHtml' interface CycleDetails { cycle: string[] @@ -198,13 +199,14 @@ export function GanttChart({ const originalTask = taskMap.current.get(task.id) if (!originalTask) return '' - const assignee = originalTask.assignee_name || 'Unassigned' - const statusName = originalTask.status_name || 'No Status' - const priority = originalTask.priority.charAt(0).toUpperCase() + originalTask.priority.slice(1) + const assignee = escapeHtml(originalTask.assignee_name) || 'Unassigned' + const statusName = escapeHtml(originalTask.status_name) || 'No Status' + const priority = escapeHtml(originalTask.priority.charAt(0).toUpperCase() + originalTask.priority.slice(1)) + const taskName = escapeHtml(task.name) return `
-

${task.name}

+

${taskName}

Assignee: diff --git a/frontend/src/pages/AuditPage.tsx b/frontend/src/pages/AuditPage.tsx index c34dfb1..472519c 100644 --- a/frontend/src/pages/AuditPage.tsx +++ b/frontend/src/pages/AuditPage.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next' import { useAuth } from '../contexts/AuthContext' import { SkeletonTable } from '../components/Skeleton' import { auditService, AuditLog, AuditLogFilters, IntegrityCheckResponse } from '../services/audit' +import { escapeHtml } from '../utils/escapeHtml' interface AuditLogDetailProps { log: AuditLog @@ -368,11 +369,11 @@ export default function AuditPage() { const tableRows = logs.map(log => ` - ${formatDate(log.created_at)} - ${log.event_type} - ${log.resource_type} / ${log.resource_id?.substring(0, 8) || '-'} - ${log.user_name || 'System'} - ${log.sensitivity_level} + ${escapeHtml(formatDate(log.created_at))} + ${escapeHtml(log.event_type)} + ${escapeHtml(log.resource_type)} / ${escapeHtml(log.resource_id?.substring(0, 8)) || '-'} + ${escapeHtml(log.user_name) || 'System'} + ${escapeHtml(log.sensitivity_level)} `).join('') diff --git a/frontend/src/utils/escapeHtml.ts b/frontend/src/utils/escapeHtml.ts new file mode 100644 index 0000000..ae6595b --- /dev/null +++ b/frontend/src/utils/escapeHtml.ts @@ -0,0 +1,24 @@ +/** + * Escapes HTML special characters to prevent XSS attacks. + * Use this when inserting dynamic content into HTML strings. + */ +export function escapeHtml(unsafe: string | null | undefined): string { + if (unsafe == null) return '' + return String(unsafe) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + +/** + * Escapes HTML for use in HTML attributes. + * More restrictive than escapeHtml - also escapes backticks and equals. + */ +export function escapeAttr(unsafe: string | null | undefined): string { + if (unsafe == null) return '' + return escapeHtml(unsafe) + .replace(/`/g, '`') + .replace(/=/g, '=') +}