CreaRack-SL

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, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
}
window.escapeHtml = escapeHtml;

function escapeAttr(str) {
    if (str === null || str === undefined) return '';
    return String(str)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}
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]]