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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 `
|
||||
<div class="gantt-popup">
|
||||
<h3 class="gantt-popup-title">${task.name}</h3>
|
||||
<h3 class="gantt-popup-title">${taskName}</h3>
|
||||
<div class="gantt-popup-info">
|
||||
<div class="gantt-popup-row">
|
||||
<span class="gantt-popup-label">Assignee:</span>
|
||||
|
||||
@@ -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 => `
|
||||
<tr>
|
||||
<td>${formatDate(log.created_at)}</td>
|
||||
<td>${log.event_type}</td>
|
||||
<td>${log.resource_type} / ${log.resource_id?.substring(0, 8) || '-'}</td>
|
||||
<td>${log.user_name || 'System'}</td>
|
||||
<td><span style="background-color: ${getSensitivityColor(log.sensitivity_level)}; color: ${log.sensitivity_level === 'medium' ? '#000' : '#fff'}; padding: 2px 8px; border-radius: 4px; font-size: 12px;">${log.sensitivity_level}</span></td>
|
||||
<td>${escapeHtml(formatDate(log.created_at))}</td>
|
||||
<td>${escapeHtml(log.event_type)}</td>
|
||||
<td>${escapeHtml(log.resource_type)} / ${escapeHtml(log.resource_id?.substring(0, 8)) || '-'}</td>
|
||||
<td>${escapeHtml(log.user_name) || 'System'}</td>
|
||||
<td><span style="background-color: ${getSensitivityColor(log.sensitivity_level)}; color: ${log.sensitivity_level === 'medium' ? '#000' : '#fff'}; padding: 2px 8px; border-radius: 4px; font-size: 12px;">${escapeHtml(log.sensitivity_level)}</span></td>
|
||||
</tr>
|
||||
`).join('')
|
||||
|
||||
|
||||
24
frontend/src/utils/escapeHtml.ts
Normal file
24
frontend/src/utils/escapeHtml.ts
Normal file
@@ -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, '"')
|
||||
.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, '=')
|
||||
}
|
||||
Reference in New Issue
Block a user