Fix 7 frontend security vulnerabilities (4 critical, 3 high)
- Escape all innerHTML assignments with user/external data across 12 JS files - Upgrade credential encryption: per-value IV, key moved to sessionStorage - Fix open redirect in TOTP auth via proper URL hostname validation - Remove sensitive DNS topology data from localStorage cache - Add security regression test suite (51 tests) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -74,13 +74,13 @@
|
||||
html += '<tr style="border-bottom: 1px solid var(--border); color: var(--muted);"><th style="padding: 8px; text-align: left;">Container</th><th style="padding: 8px; text-align: left;">Image</th><th style="padding: 8px; text-align: left;">Current</th><th style="padding: 8px; text-align: left;">Latest</th><th style="padding: 8px; text-align: right;">Actions</th></tr>';
|
||||
for (const u of updates) {
|
||||
html += `<tr style="border-bottom: 1px solid var(--border);">`;
|
||||
html += `<td style="padding: 8px; font-weight: 500;">${u.containerName}</td>`;
|
||||
html += `<td style="padding: 8px; color: var(--muted);">${u.imageName}</td>`;
|
||||
html += `<td style="padding: 8px;"><code style="font-size: 0.78rem; background: var(--bg); padding: 2px 6px; border-radius: 4px;">${u.currentDigest}</code></td>`;
|
||||
html += `<td style="padding: 8px;"><code style="font-size: 0.78rem; background: var(--ok-bg); color: var(--ok-fg); padding: 2px 6px; border-radius: 4px;">${u.latestDigest}</code></td>`;
|
||||
html += `<td style="padding: 8px; font-weight: 500;">${escapeHtml(u.containerName)}</td>`;
|
||||
html += `<td style="padding: 8px; color: var(--muted);">${escapeHtml(u.imageName)}</td>`;
|
||||
html += `<td style="padding: 8px;"><code style="font-size: 0.78rem; background: var(--bg); padding: 2px 6px; border-radius: 4px;">${escapeHtml(u.currentDigest)}</code></td>`;
|
||||
html += `<td style="padding: 8px;"><code style="font-size: 0.78rem; background: var(--ok-bg); color: var(--ok-fg); padding: 2px 6px; border-radius: 4px;">${escapeHtml(u.latestDigest)}</code></td>`;
|
||||
html += `<td style="padding: 8px; text-align: right;">`;
|
||||
html += `<button class="update-now-btn" data-id="${u.containerId}" data-name="${u.containerName}" style="padding: 4px 10px; font-size: 0.78rem; background: var(--accent); color: var(--bg); border-color: var(--accent); margin-right: 4px;">Update</button>`;
|
||||
html += `<button class="rollback-btn" data-id="${u.containerId}" data-name="${u.containerName}" style="padding: 4px 10px; font-size: 0.78rem;">Rollback</button>`;
|
||||
html += `<button class="update-now-btn" data-id="${escapeHtml(u.containerId)}" data-name="${escapeHtml(u.containerName)}" style="padding: 4px 10px; font-size: 0.78rem; background: var(--accent); color: var(--bg); border-color: var(--accent); margin-right: 4px;">Update</button>`;
|
||||
html += `<button class="rollback-btn" data-id="${escapeHtml(u.containerId)}" data-name="${escapeHtml(u.containerName)}" style="padding: 4px 10px; font-size: 0.78rem;">Rollback</button>`;
|
||||
html += '</td></tr>';
|
||||
}
|
||||
html += '</table>';
|
||||
@@ -143,7 +143,7 @@
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
availableContainer.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${e.message}</div>`;
|
||||
availableContainer.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${escapeHtml(e.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,19 +180,19 @@
|
||||
const dur = h.duration ? (h.duration < 1000 ? h.duration + 'ms' : Math.round(h.duration / 1000) + 's') : '-';
|
||||
html += `<tr style="border-bottom: 1px solid var(--border);">`;
|
||||
html += `<td style="padding: 6px; color: var(--muted);">${timeAgo(h.timestamp)}</td>`;
|
||||
html += `<td style="padding: 6px; font-weight: 500;">${h.containerName}</td>`;
|
||||
html += `<td style="padding: 6px; color: var(--muted);">${h.imageName}</td>`;
|
||||
html += `<td style="padding: 6px; font-weight: 500;">${escapeHtml(h.containerName)}</td>`;
|
||||
html += `<td style="padding: 6px; color: var(--muted);">${escapeHtml(h.imageName)}</td>`;
|
||||
html += `<td style="padding: 6px;">${dur}</td>`;
|
||||
html += `<td style="padding: 6px;"><span style="color: ${ok ? 'var(--ok-fg)' : 'var(--bad-fg)'};">${ok ? '✓ success' : '✗ failed'}</span></td>`;
|
||||
html += '</tr>';
|
||||
if (!ok && h.error) {
|
||||
html += `<tr><td colspan="5" style="padding: 4px 6px 8px; font-size: 0.78rem; color: var(--bad-fg);">${h.error}</td></tr>`;
|
||||
html += `<tr><td colspan="5" style="padding: 4px 6px 8px; font-size: 0.78rem; color: var(--bad-fg);">${escapeHtml(h.error)}</td></tr>`;
|
||||
}
|
||||
}
|
||||
html += '</table>';
|
||||
historyContainer.innerHTML = html;
|
||||
} catch (e) {
|
||||
historyContainer.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${e.message}</div>`;
|
||||
historyContainer.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${escapeHtml(e.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,17 +212,17 @@
|
||||
for (const c of containers) {
|
||||
const name = c.name || c.Names?.[0]?.replace(/^\//, '') || c.Id?.substring(0, 12);
|
||||
const cid = c.containerId || c.Id;
|
||||
html += `<tr style="border-bottom: 1px solid var(--border);" data-container-id="${cid}">`;
|
||||
html += `<td style="padding: 8px; font-weight: 500;">${name}</td>`;
|
||||
html += `<tr style="border-bottom: 1px solid var(--border);" data-container-id="${escapeHtml(cid)}">`;
|
||||
html += `<td style="padding: 8px; font-weight: 500;">${escapeHtml(name)}</td>`;
|
||||
html += `<td style="padding: 8px;">
|
||||
<select class="auto-schedule" data-id="${cid}" style="padding: 4px 8px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.82rem;">
|
||||
<select class="auto-schedule" data-id="${escapeHtml(cid)}" style="padding: 4px 8px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.82rem;">
|
||||
<option value="">Disabled</option>
|
||||
<option value="daily">Daily</option>
|
||||
<option value="weekly">Weekly</option>
|
||||
<option value="monthly">Monthly</option>
|
||||
</select></td>`;
|
||||
html += `<td style="padding: 8px;"><input type="checkbox" class="auto-rollback" data-id="${cid}" checked /></td>`;
|
||||
html += `<td style="padding: 8px; text-align: right;"><button class="save-auto-btn" data-id="${cid}" data-name="${name}" style="padding: 4px 10px; font-size: 0.78rem;">Save</button></td>`;
|
||||
html += `<td style="padding: 8px;"><input type="checkbox" class="auto-rollback" data-id="${escapeHtml(cid)}" checked /></td>`;
|
||||
html += `<td style="padding: 8px; text-align: right;"><button class="save-auto-btn" data-id="${escapeHtml(cid)}" data-name="${escapeHtml(name)}" style="padding: 4px 10px; font-size: 0.78rem;">Save</button></td>`;
|
||||
html += '</tr>';
|
||||
}
|
||||
html += '</table>';
|
||||
@@ -257,7 +257,7 @@
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
autoContainer.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${e.message}</div>`;
|
||||
autoContainer.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${escapeHtml(e.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user