ui: start template safety cleanup

This commit is contained in:
a.tolmachev
2026-05-02 13:31:21 +00:00
parent a6e896ed89
commit 2f84a38b69
3 changed files with 43 additions and 10 deletions
+4 -4
View File
@@ -2,14 +2,14 @@
## Current
### `feat/frontend-xss-hardening`
### `feat/frontend-template-safety-cleanup`
Status: in_progress
DoD:
- API-provided values are not rendered into `innerHTML` on user-facing admin pages
- error strings from API responses are rendered through text nodes
- streaming admin pages use DOM construction instead of HTML string concatenation for dynamic cards
- local UI helpers do not use `innerHTML` for static SVG or icon insertion
- wizard shell and workspace shell use DOM-based icon and fragment construction
- remaining HTML-bearing i18n/template paths are explicitly isolated and tracked
## Next
+17 -1
View File
@@ -18,6 +18,16 @@ function tfKey(key, vars) {
return window.tf ? tf(key, vars) : tKey(key);
}
function buildIconSvg(href, width, height) {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', String(width));
svg.setAttribute('height', String(height));
var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttribute('href', href);
svg.appendChild(use);
return svg;
}
function mapKeyRecord(record) {
var apiKey = record.api_key || {};
return {
@@ -310,7 +320,13 @@ document.getElementById('copy-key-btn').addEventListener('click', function() {
if (navigator.clipboard) {
navigator.clipboard.writeText(value).catch(function() {});
}
this.innerHTML = '<svg width="13" height="13"><use href="' + (window.APP_BASE || '') + 'icons/general/check.svg#icon"/></svg>';
this.replaceChildren(
buildIconSvg(
(window.APP_BASE || '') + 'icons/general/check.svg#icon',
13,
13
)
);
if (window.CrankUi) {
window.CrankUi.info(
tKey('apikeys.toast.copy_message'),
+22 -5
View File
@@ -12,6 +12,16 @@ function tfKey(key, vars) {
return typeof tf === 'function' ? tf(key, vars) : key;
}
function buildIconSvg(href, width, height) {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', String(width));
svg.setAttribute('height', String(height));
var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttribute('href', href);
svg.appendChild(use);
return svg;
}
function workspaceColor(index) {
return ['#0d9488', '#7c3aed', '#0891b2', '#f59e0b', '#2563eb'][index % 5];
}
@@ -139,10 +149,11 @@ function renderWorkspaceList() {
item.appendChild(info);
if (active) {
var check = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
check.setAttribute('width', '12');
check.setAttribute('height', '12');
check.innerHTML = '<use href="' + (window.APP_BASE || '') + 'icons/general/check.svg#icon"></use>';
var check = buildIconSvg(
(window.APP_BASE || '') + 'icons/general/check.svg#icon',
12,
12
);
item.appendChild(check);
}
@@ -160,7 +171,13 @@ function renderWorkspaceList() {
var dd = document.getElementById('ws-dropdown');
if (dd) dd.style.display = 'none';
});
manageLink.innerHTML = '<svg width="14" height="14"><use href="' + (window.APP_BASE || '') + 'icons/general/settings.svg#icon"/></svg>';
manageLink.appendChild(
buildIconSvg(
(window.APP_BASE || '') + 'icons/general/settings.svg#icon',
14,
14
)
);
manageLink.appendChild(document.createTextNode(tKey('settings.ws.title')));
list.appendChild(manageLink);
}