fix(security): address critical and high findings from security audit
Fix stored XSS in tasks (titles/subtasks) and settings (member list) by applying escHtml(). Harden trust proxy to loopback default, add OAuth state parameter for Google Calendar CSRF protection, sanitize CSV export against formula injection, invalidate sessions on user deletion, restrict usernames to alphanumeric chars, and require admin role for calendar sync triggers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -488,15 +488,24 @@ function bindDeleteButtons(container, user) {
|
||||
// Helfer
|
||||
// --------------------------------------------------------
|
||||
|
||||
function escHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function memberHtml(u) {
|
||||
return `
|
||||
<li class="settings-member" data-id="${u.id}">
|
||||
<div class="settings-avatar settings-avatar--sm" style="background:${u.avatar_color}">${initials(u.display_name)}</div>
|
||||
<div class="settings-avatar settings-avatar--sm" style="background:${escHtml(u.avatar_color)}">${initials(u.display_name)}</div>
|
||||
<div class="settings-member__info">
|
||||
<span class="settings-member__name">${u.display_name}</span>
|
||||
<span class="settings-member__meta">@${u.username} · ${u.role === 'admin' ? t('settings.roleAdmin') : t('settings.roleMember')}</span>
|
||||
<span class="settings-member__name">${escHtml(u.display_name)}</span>
|
||||
<span class="settings-member__meta">@${escHtml(u.username)} · ${u.role === 'admin' ? t('settings.roleAdmin') : t('settings.roleMember')}</span>
|
||||
</div>
|
||||
<button class="btn btn--icon btn--danger-outline" data-delete-user="${u.id}" data-name="${u.display_name}" aria-label="${u.display_name} ${t('settings.deleteMemberLabel')}" title="${t('settings.deleteMemberLabel')}">
|
||||
<button class="btn btn--icon btn--danger-outline" data-delete-user="${u.id}" data-name="${escHtml(u.display_name)}" aria-label="${escHtml(u.display_name)} ${t('settings.deleteMemberLabel')}" title="${t('settings.deleteMemberLabel')}">
|
||||
<i data-lucide="trash-2" aria-hidden="true"></i>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
+12
-3
@@ -10,6 +10,15 @@ import { openModal as openSharedModal, closeModal, wireBlurValidation, btnSucces
|
||||
import { stagger, vibrate } from '/utils/ux.js';
|
||||
import { t, formatDate } from '/i18n.js';
|
||||
|
||||
function escHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Konstanten
|
||||
// --------------------------------------------------------
|
||||
@@ -155,7 +164,7 @@ function renderTaskCard(task, opts = {}) {
|
||||
data-status="${s.status}" aria-label="${t('tasks.subtaskMarkDone', { title: s.title })}">
|
||||
${s.status === 'done' ? '<i data-lucide="check" style="width:10px;height:10px;color:#fff" aria-hidden="true"></i>' : ''}
|
||||
</button>
|
||||
<span class="subtask-item__title">${s.title}</span>
|
||||
<span class="subtask-item__title">${escHtml(s.title)}</span>
|
||||
</div>`).join('')
|
||||
: '';
|
||||
|
||||
@@ -170,7 +179,7 @@ function renderTaskCard(task, opts = {}) {
|
||||
|
||||
<div class="task-card__body">
|
||||
<div class="task-card__title" data-action="open-task" data-id="${task.id}">
|
||||
${task.title}
|
||||
${escHtml(task.title)}
|
||||
</div>
|
||||
<div class="task-card__meta">
|
||||
${renderPriorityBadge(task.priority)}
|
||||
@@ -504,7 +513,7 @@ function renderKanbanCard(task) {
|
||||
return `
|
||||
<div class="kanban-card ${task.status === 'done' ? 'kanban-card--done' : ''}"
|
||||
data-task-id="${task.id}" draggable="true">
|
||||
<div class="kanban-card__title">${task.title}</div>
|
||||
<div class="kanban-card__title">${escHtml(task.title)}</div>
|
||||
<div class="kanban-card__meta">
|
||||
${renderPriorityBadge(task.priority)}
|
||||
${due ? `<span class="due-date ${due.cls}"><i data-lucide="clock" style="width:10px;height:10px" aria-hidden="true"></i> ${due.label}</span>` : ''}
|
||||
|
||||
Reference in New Issue
Block a user