fix(security): eliminate XSS vectors and restore zoom accessibility

- Extract shared esc() utility (public/utils/html.js) replacing 8
  duplicate escHtml() functions across all page modules
- Apply HTML escaping to all user-controlled data in innerHTML
  templates: titles, names, locations, descriptions, colors, notes
  content, weather data, autocomplete suggestions
- Remove user-scalable=no and maximum-scale=1 from viewport meta
  tag, restoring pinch-to-zoom for WCAG 1.4.4 compliance
- Bump version to 0.7.1
This commit is contained in:
Ulas
2026-04-04 06:25:28 +02:00
parent 87186c03c0
commit 6bc4c46f03
13 changed files with 145 additions and 170 deletions
+5 -10
View File
@@ -9,6 +9,7 @@ import { api } from '/api.js';
import { openModal as openSharedModal, closeModal } from '/components/modal.js';
import { stagger, vibrate } from '/utils/ux.js';
import { t, formatDate, getLocale } from '/i18n.js';
import { esc } from '/utils/html.js';
// --------------------------------------------------------
// Konstanten
@@ -253,7 +254,7 @@ function renderCategoryBars(byCategory) {
return `
<div class="budget-bar-row">
<div class="budget-bar-row__label" title="${escHtml(c.category)}">${escHtml(c.category)}</div>
<div class="budget-bar-row__label" title="${esc(c.category)}">${esc(c.category)}</div>
<div class="budget-bar-row__track">
<div class="budget-bar-row__fill ${cls}" style="width:${pct}%;"></div>
</div>
@@ -289,8 +290,8 @@ function renderEntries() {
<div class="budget-entry" data-id="${e.id}">
<div class="budget-entry__indicator ${indClass}"></div>
<div class="budget-entry__body">
<div class="budget-entry__title">${escHtml(e.title)}</div>
<div class="budget-entry__meta">${date} · ${escHtml(e.category)}${recurTag}</div>
<div class="budget-entry__title">${esc(e.title)}</div>
<div class="budget-entry__meta">${date} · ${esc(e.category)}${recurTag}</div>
</div>
<div class="budget-entry__amount ${amtClass}">${sign}${formatAmount(e.amount)}</div>
<button class="budget-entry__delete" data-action="delete" data-id="${e.id}" aria-label="${t('budget.deleteLabel')}">
@@ -354,7 +355,7 @@ function openBudgetModal({ mode, entry = null }) {
<div class="form-group">
<label class="form-label" for="bm-title">${t('budget.titleLabel')}</label>
<input type="text" class="form-input" id="bm-title"
placeholder="${t('budget.titlePlaceholder')}" value="${escHtml(isEdit ? entry.title : '')}">
placeholder="${t('budget.titlePlaceholder')}" value="${esc(isEdit ? entry.title : '')}">
</div>
<div class="form-group">
@@ -484,9 +485,3 @@ async function deleteEntry(id) {
// Hilfsfunktion
// --------------------------------------------------------
function escHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&amp;').replace(/</g, '&lt;')
.replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}