Servicio: Helpers de escape por contexto (base.js)
Descripción
Conjunto de 4 funciones globales (definidas en static/js/base.js, cargadas antes que cualquier otro script del frontend legacy) que implementan escape de texto según el contexto en el que se interpole en HTML. Su propósito es neutralizar XSS y CSV injection en todos los renders de la interfaz.
API de funciones
window.escapeHtml(str) — Contexto: Texto / Contenido de elemento
Escapa <, >, & para que un string no pueda romper un elemento HTML o inyectar tags/entidades.
Uso: Siempre que interpoles un dato no confiable (nombre de dispositivo, hostname SNMP, nombre de asset/playlist, error.message, etc.) como contenido de texto dentro de un elemento:
// ANTES (vulnerable):
html += `<span>${device.name}</span>`;
// DESPUÉS (seguro):
html += `<span>${escapeHtml(device.name)}</span>`;
No escapa comillas — no es seguro dentro de un atributo entrecomillado (usa escapeAttr para eso).
window.escapeAttr(str) — Contexto: Valores dentro de atributos entrecomillados
Escapa <, >, &, ", ' para que un dato no pueda romper un atributo y ejecutar código.
Uso: Cuando interpoles un dato dentro de un atributo entrecomillado como title="...", data-*="...", value="...", style="...", alt="...":
// ANTES (vulnerable - puede romper con comilla):
html += `<div title="${device.hostname}">...</div>`;
// Si device.hostname = 'Malicious" onclick="alert(1)"'
// DESPUÉS (seguro):
html += `<div title="${escapeAttr(device.hostname)}">...</div>`;
Diferencia vs escapeHtml: Escapa comillas, así que no puede romperse el atributo.
window.safeUrl(url) — Contexto: href / src (URLs)
Devuelve # si la URL usa esquema peligroso (javascript:, data:, vbscript:). Permite http(s), mailto:, tel:, URLs relativas y anchors. Limpia espacios en blanco antes de validar (para evitar esquemas “disfrazados” como java\tscript:).
Uso: Cuando construyas una URL dinámicamente:
// ANTES (vulnerable):
html += `<a href="${device.ip}">Open</a>`;
// O peor:
html += `<a href="${userProvidedUrl}">Link</a>`;
// DESPUÉS (seguro):
html += `<a href="${safeUrl(`http://${device.ip}`)}">Open</a>`;
html += `<a href="${safeUrl(userProvidedUrl)}">Link</a>`;
Nota: Combinar con escapeAttr si la URL también necesita ir dentro de un atributo HTML:
html += `<a href="${escapeAttr(safeUrl(url))}">...</a>`;
window.sanitizeCsvCell(value) — Contexto: Exportes CSV
Antepone una comilla simple ' a un valor que comience con =, +, -, @, tab, o carriage return. Esto neutraliza la CSV/formula injection en Excel, LibreOffice, Google Sheets, etc., que interpretan esos caracteres como inicio de fórmula.
Uso: En cada celda de un CSV generado dinámicamente, especialmente nombres/datos de usuario:
// ANTES (vulnerable - una hoja de cálculo ejecutaría =1+1):
csv += `${device.name},${target.ip}\n`;
// Si device.name = "=1+1"
// DESPUÉS (seguro):
csv += `${sanitizeCsvCell(device.name)},${sanitizeCsvCell(target.ip)}\n`;
// Resultado: "'=1+1,..." la hoja lo trata como texto literal
Implementación en static/js/base.js
// En static/js/base.js (cargado primero)
function escapeHtml(str) {
if (str === null || str === undefined) return '';
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
}
window.escapeHtml = escapeHtml;
function escapeAttr(str) {
if (str === null || str === undefined) return '';
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
window.escapeAttr = escapeAttr;
function safeUrl(url) {
if (url === null || url === undefined) return '#';
const raw = String(url).trim();
const stripped = raw.replace(/\s+/g, "").toLowerCase();
if (/^(javascript|data|vbscript):/.test(stripped)) return '#';
return raw;
}
window.safeUrl = safeUrl;
function sanitizeCsvCell(value) {
const s = value === null || value === undefined ? '' : String(value);
return /^[=+\-@\t\r]/.test(s) ? "'" + s : s;
}
window.sanitizeCsvCell = sanitizeCsvCell;
Uso consistente: patrón recomendado
En ficheros del frontend legacy, se recomienda capturar los helpers en el scope local para comodidad (especialmente si el fichero ya tiene helpers locales como esc o _esc):
// Al inicio de una función que renderiza HTML
const esc = window.escapeHtml || (s => String(s));
const escA = window.escapeAttr || esc;
const safeUrl = window.safeUrl || (s => String(s));
// Luego usar:
html += `<span>${esc(device.name)}</span>`;
html += `<div title="${escA(device.hostname)}">...</div>`;
html += `<a href="${escA(safeUrl(url))}">...</a>`;
Fallback: Si un fichero se carga antes de base.js (raro, el diseño la pone primero), los fallbacks garantizan que no rompen.
Auditoría Suprema Etapa 3
Estos helpers fueron creados en commit #112 (2026-06-11, FE7 cimientos) y aplicados sistemáticamente en commit 6304eb5 (2026-06-12, Etapa 3 cierre FE2-FE7).
Verificación: node --check en 41 ficheros, revisión de diff sin doble-escape ni escapado de literales HTML de confianza.
Véase también
- [[feature—frontend—auditoria-suprema-etapa3-xss-csv]]
- [[concept—seguridad—xss]]
- [[concept—seguridad—csv-injection]]
- [[concept—producto—frontend-legacy]]
- [[entity—frontend—module—editor-fe2]]