Security: XSS-Fix, localhost-Binding, API-Validierung (v1.1.3)

- Flask bindet auf 127.0.0.1 statt 0.0.0.0 — Port 8099 nicht mehr
  direkt im LAN erreichbar (host_network: true umgeht sonst HA-Auth)
- XSS: esc() Funktion + HTML-Escaping für alle user-controlled Werte
  in innerHTML (inv.name, modbus_ip, mqtt_topic_prefix, s.name, s.unit)
- API: POST /api/inverters-config validiert inverter_model, Port (1-65535),
  Modbus-Adresse (1-247) vor dem Speichern
- _poll_loop: int()-Aufrufe in try/except — kein Thread-Crash bei
  ungültiger Config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
retr0
2026-04-26 12:34:21 +02:00
parent 37ef054bbe
commit 5b490c0aea
3 changed files with 36 additions and 13 deletions
+12 -8
View File
@@ -221,6 +221,10 @@
<div class="toast" id="toast"></div>
<script>
function esc(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}
const DC_COLORS = {
power: '#f0c040', voltage: '#58a6ff', current: '#ffa657',
energy: '#3fb950', temperature: '#f85149', battery: '#bc8cff',
@@ -314,14 +318,14 @@ function renderLive(inverters) {
const hist = (inv.history || {})[s.id] || [];
return `<div class="sensor-card ${dcClass}">
<div class="sensor-icon">${ICON_MAP[s.icon]||"📊"}</div>
<div class="sensor-name">${s.name}</div>
<div class="sensor-value">${display}<span class="sensor-unit">${s.unit}</span></div>
<div class="sensor-name">${esc(s.name)}</div>
<div class="sensor-value">${display}<span class="sensor-unit">${esc(s.unit)}</span></div>
${sparkline(hist, s.device_class)}
</div>`;
}).join("");
return `<div class="inv-section">
<div class="inv-header">
<div class="inv-title">${inv.name}</div>
<div class="inv-title">${esc(inv.name)}</div>
<div class="inv-badge ${inv.modbus_ok ? "ok" : "err"}">${inv.modbus_ok ? "online" : "offline"}</div>
<div class="info-chip" style="margin-left:auto;font-size:11px">⏱ ${ago} vor · ${inv.poll_count} Messungen</div>
</div>
@@ -350,7 +354,7 @@ async function loadModels() {
modelsList = await fetchJSON(api("api/inverter-models"));
const sel = document.getElementById("modal-model");
sel.innerHTML = Object.values(modelsList).map(m =>
`<option value="${m.id}">${m.name} (${m.sensor_count} Sensoren)</option>`
`<option value="${esc(m.id)}">${esc(m.name)} (${m.sensor_count} Sensoren)</option>`
).join("");
}
@@ -362,13 +366,13 @@ function renderInverterList() {
<div class="inv-card-header">
<div class="inv-card-icon">☀️</div>
<div class="inv-card-info">
<div class="inv-card-name">${inv.name || "Wechselrichter"}</div>
<div class="inv-card-model">${model.name || inv.inverter_model}</div>
<div class="inv-card-name">${esc(inv.name || "Wechselrichter")}</div>
<div class="inv-card-model">${esc(model.name || inv.inverter_model)}</div>
</div>
</div>
<div class="inv-card-meta">
📡 ${inv.modbus_ip}:${inv.modbus_port || 502} · Slave ${inv.modbus_address || 1}<br>
📨 ${inv.mqtt_topic_prefix}<br>
📡 ${esc(inv.modbus_ip)}:${inv.modbus_port || 502} · Slave ${inv.modbus_address || 1}<br>
📨 ${esc(inv.mqtt_topic_prefix)}<br>
⏱ alle ${inv.update_interval || 30}s
</div>
<div class="inv-card-actions">