Cross-site scripting ocurre cuando datos que la aplicación quería mostrar se convierten en contenido ejecutable dentro del browser. El valor peligroso puede venir de un formulario, una URL, una base, una API o una dependencia comprometida. Output encoding es una defensa fuerte, pero solo funciona si corresponde al contexto exacto: texto HTML, atributo, URL, JavaScript y CSS no comparten las mismas reglas.

El browser parsea varios lenguajes

Un documento HTML puede contener markup, scripts, estilos y destinos de navegación. Un valor seguro entre etiquetas puede ser peligroso dentro de una string JavaScript. Un valor que permanece dentro de un atributo puede apuntar a javascript:.

La seguridad depende de dónde interpretará el browser los bytes. Frameworks con auto-escaping ayudan, pero raw HTML, innerHTML e inline handlers saltan esa protección.

Escapa en el boundary final

Guardar input HTML-encoded mezcla presentación con datos. Después el mismo valor puede usarse en JSON, email o CSV, donde las reglas HTML no aplican. Es mejor conservar valor semántico y codificar al renderizar.

El template conoce el contexto y puede elegir encoder. Raw rendering debe ser excepcional y revisado.

Evita contextos peligrosos

Insertar datos no confiables en script blocks, style blocks, event-handler attributes o fragmentos de markup es difícil de asegurar. Prefiere JSON, data attributes seguros y APIs DOM estructuradas. Para texto plano, usa textContent, no innerHTML.

La mejor defensa es impedir que datos se transformen en código. Encoding queda como operación clara de frontera, no como truco para neutralizar cualquier payload.

El HTML permitido necesita sanitizer

Comentarios enriquecidos y editores visuales pueden necesitar tags limitados. Un sanitizer confiable debe parsear HTML y aplicar allowlist de elementos, atributos y URL schemes. También debe lidiar con markup mal formado y diferencias de browsers.

No construyas sanitizers con regex. HTML es tolerante a errores y más complejo que una lista de reemplazos.

URLs requieren validación semántica

Escapar comillas mantiene un valor dentro de href, pero no vuelve seguro el destino. Debes validar esquemas, hosts o rutas permitidas. Un open redirect puede estar perfectamente encoded y seguir siendo peligroso.

Primero valida significado de navegación; después aplica attribute encoding. Son controles distintos.

CSP limita daño

Content Security Policy puede bloquear scripts inesperados, exigir nonces y generar reportes. Es defensa en profundidad. Legacy exceptions o políticas demasiado permisivas reducen su valor.

CSP no reemplaza escaping ni sanitization. Ayuda cuando algo falla, pero no debe ser la única barrera.

El cliente también puede introducir XSS

Una página renderizada de forma segura puede volverse vulnerable cuando JavaScript lee datos y los coloca en innerHTML. Audita DOM sinks, widgets externos y helpers personalizados. Trusted Types puede reforzar aplicaciones grandes al restringir sinks peligrosos.

Las pruebas deben observar el DOM final, no solo la respuesta del servidor. Código cliente puede cambiar por completo el contexto.

Stored XSS puede esperar mucho tiempo

Un payload guardado en perfil, ticket o import puede ejecutarse meses después en un panel administrativo. Cada output boundary debe tratar datos almacenados como no confiables, incluso si vienen de usuarios internos.

Las migraciones de framework merecen auditoría

Cambiar template engine o framework cliente puede modificar defaults de escaping. Un helper que antes recibía texto puede empezar a esperar HTML, o al revés. Antes de migrar, inventaria raw outputs, sanitizers personalizados y DOM sinks peligrosos.

Los tests de XSS deben ejecutarse contra el DOM final después de scripts cliente. Así se detectan regresiones que no aparecen mirando solo el HTML inicial.

La observabilidad no debe renderizar payloads

Herramientas internas de seguridad y logs deben mostrar valores sospechosos como texto escapado. Un dashboard de incidentes no debe ejecutar el payload que intenta investigar. CSP reports y métricas de sanitizer ayudan, pero también necesitan presentación segura.

Los permisos administrativos no reducen el riesgo

Muchas vulnerabilidades XSS aparecen primero en paneles internos porque se revisan menos. Sin embargo, esos paneles los usan personas con privilegios altos. Un payload guardado por un usuario común puede ejecutarse frente a un operador y cambiar datos, robar tokens o iniciar acciones administrativas.

Por eso las interfaces internas merecen el mismo contextual encoding que la parte pública. La confianza en el usuario actual no cambia la confianza en los datos almacenados.

La respuesta a incidentes debe buscar todos los sinks

Eliminar el registro que disparó una alerta no arregla la vulnerabilidad. Hay que identificar el sink, revisar componentes similares, invalidar sesiones potencialmente expuestas y confirmar si el payload se ejecutó para usuarios privilegiados. XSS es un fallo de frontera, no solo un valor malo en una tabla.

Después del arreglo, un test con el payload original debe quedar en la suite. Así el incidente se transforma en protección permanente, no en memoria oral del equipo responsable.

Prevenir XSS es preservar una frontera: datos siguen siendo datos y código sigue siendo código. Contextual encoding, sanitization controlada y APIs DOM seguras hacen esa frontera mantenible.