Trusted Types ya es Baseline: el freno que tu frontend necesita antes de usar innerHTML
webdevelopment 21 de agosto de 2026 · Mintec

Trusted Types ya es Baseline: el freno que tu frontend necesita antes de usar innerHTML

Trusted Types ya es Baseline newly available. Así convertimos innerHTML, previews de CMS y widgets de terceros en una frontera revisable para proyectos Astro y Next.js.

Trusted Types ya es Baseline: el freno que tu frontend necesita antes de usar innerHTML

Trusted Types ya está marcado como Baseline newly available. Para un sitio Astro o Next.js, no es una excusa para repartir innerHTML con más confianza: es la oportunidad de convertir cada inserción de HTML, URL de script o JavaScript dinámico en una frontera pequeña, nombrada y revisable. La implementación correcta no empieza activando CSP en producción. Empieza identificando qué contenido necesita HTML de verdad, sanitizándolo en el límite correcto y dejando que el navegador rompa las rutas que nadie debería estar usando.[1]

El problema no suele estar en el componente que el equipo acaba de escribir. Está en las excepciones acumuladas: un preview de CMS, un bloque de texto enriquecido, una integración de chat, un fragmento de personalización, un embed que llegó con una campaña. Casi todas terminan en la misma familia de APIs. Un string alcanza innerHTML, outerHTML, insertAdjacentHTML, un srcdoc o una URL de script; el navegador interpreta algo que antes era solo texto.

En una arquitectura compuesta, esa familia de atajos se multiplica. El frontend recibe contenido del CMS, módulos de proveedores y datos de rutas distintas. Separar esas capas, como planteamos en nuestra arquitectura web composable, hace más flexible al sitio. También obliga a decidir dónde se puede convertir texto en DOM.

Qué cambia cuando el navegador exige un tipo, no un string

Trusted Types no trae un sanitizador mágico. MDN lo explica de forma precisa: el equipo crea una política con una función de transformación y el navegador exige que los datos pasen por ella antes de llegar a un sink de inyección. Para HTML, esa función normalmente llama a un sanitizador; para scripts y URLs de scripts, puede limitar las entradas a una lista muy pequeña o directamente apagarlas.[1]

La pieza que lo vuelve una barrera real es CSP. Con require-trusted-types-for 'script', asignar un string ordinario a sinks de DOM XSS como innerHTML genera un TypeError en lugar de renderizarlo. La directiva trusted-types permite además limitar los nombres de políticas que una app puede crear.[2]

Eso cambia la conversación de seguridad. Antes preguntábamos: "¿este HTML viene limpio?". Con enforcement preguntamos: "¿qué módulo tiene permiso de fabricar TrustedHTML y por qué?". La segunda pregunta deja una huella en código, en CSP y en revisión de pull request.

Ruta de contenidoDecisión correctaLo que Trusted Types aporta
Texto, títulos y datos de UIRenderizar como texto o props normalesNo hace falta una política; se conserva el escape natural del framework
Rich text del CMSSanitizar en la frontera de contenido, conservar una política de HTML para mutaciones clienteEvita que otro script reinserte un string sin pasar por el mismo control
Preview editorial o editor WYSIWYGAislarlo, inventariar sus sinks y probarlo en report-onlyExpone dependencias que escriben HTML por debajo
Widget de terceroExigir compatibilidad o aislar en iframe cuando tenga sentidoHace visible que el proveedor necesita acceso a un sink sensible
Scripts dinámicos o JSONP heredadoEliminar o permitir una URL explícita y revisadaReduce una superficie que casi nunca justifica ser genérica

La tabla es más útil que una regla tipo "nunca uses innerHTML". Un bloque editorial puede necesitar markup. Un selector de producto no. Aplicar la misma solución a ambos es cómo terminan apareciendo sanitizadores dispersos, configuraciones distintas y excepciones imposibles de auditar.

Astro y Next.js ya te cubren parte del camino, pero no la excepción

Astro escapa el contenido normal que renderiza. El riesgo aparece cuando usamos set:html, cuando un componente de isla muta el DOM tras cargar, o cuando un editor manda HTML para previsualización. React hace algo parecido: sus children se escapan por defecto; dangerouslySetInnerHTML es el carril que deliberadamente se sale de esa protección.

Nuestra regla de arquitectura es simple: un render de servidor debe recibir HTML ya sanitizado desde la capa de contenido. El cliente no debe repetir esa lógica en diez componentes. Si una isla necesita reemplazar un fragmento, llama a una función de infraestructura, no a element.innerHTML desde cualquier feature.

// src/lib/cms-html-policy.ts
import DOMPurify from "dompurify";

const cmsHtml = window.trustedTypes?.createPolicy("cms-html", {
  createHTML(input) {
    // La política devuelve un string; el navegador lo envuelve como TrustedHTML.
    return DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false });
  },
});

export function replaceCmsFragment(target: Element, rawHtml: string) {
  const clean = DOMPurify.sanitize(rawHtml, { RETURN_TRUSTED_TYPE: false });

  if (!cmsHtml) {
    target.innerHTML = clean; // Fallback para navegadores sin enforcement.
    return;
  }

  target.innerHTML = cmsHtml.createHTML(clean);
}

El ejemplo no es una invitación a poner DOMPurify en todo el bundle. Si el HTML ya llegó sanitizado desde el servidor, no tiene sentido reprocesarlo sin una razón concreta. Aquí el valor es otro: cualquier mutación cliente que sí exista pasa por un único módulo. El nombre cms-html aparece tanto en código como en CSP, y esa coincidencia convierte una revisión difusa en una búsqueda de dos minutos.

Tampoco conviertas una política permisiva en una coartada. El estándar advierte que una política default laxa puede anular el beneficio entero. La política por defecto sirve para migrar código heredado y detectar dependencias; el estado final debe usar políticas pequeñas y explícitas, o mejor aún, dejar de requerir sinks de HTML donde no aportan nada.[3]

El rollout que usamos antes de bloquear nada

El error más caro es añadir enforcement en el mismo deploy que cambia el CMS, los widgets y el router. Si algo se rompe, nadie sabe si falló la política, la integración o el contenido. Preferimos separar la migración en cinco contratos comprobables:

  1. Inventario de sinks. Busca innerHTML, outerHTML, insertAdjacentHTML, srcdoc, eval, new Function y asignaciones a script.src. No preguntes solo por tu código: incluye tags de terceros, previews y administración.
  2. Frontera de contenido. Para cada ruta rica, define quién sanitiza, con qué configuración y qué formatos acepta. Si un bloque solo necesita texto, elimina la ruta HTML en vez de protegerla.
  3. Observación antes de bloqueo. Publica Content-Security-Policy-Report-Only con require-trusted-types-for 'script' y recoge violaciones por ruta, navegador y proveedor. El propio estándar propone esta transición: observar, refactorizar y luego activar enforcement sin interrumpir la aplicación.[3]
  4. Políticas con nombre y dueño. Crea una política por necesidad real, por ejemplo cms-html o video-embed-url. Declárala en trusted-types; cualquier nombre nuevo requiere justificar por qué no reutiliza una abstracción segura.
  5. Enforcement y pruebas de regresión. Cuando las violaciones propias estén resueltas, cambia el header a enforcement. El smoke test debe abrir previews, rutas con rich text, formularios, componentes de Server Islands y los widgets reales, no un home limpio.

El header final suele ser corto:

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types cms-html video-embed-url

El header no reemplaza autenticación, validación de backend ni una CSP completa. Tampoco vuelve confiable a un script que ya controla el origen. Su trabajo es más específico: impedir que una modificación futura convierta un string cualquiera en HTML o código ejecutable sin pasar por una frontera que el equipo puede revisar.

El criterio de Mintec: no cuentes políticas, reduce lugares peligrosos

En una revisión de arquitectura no premiamos un sitio por tener cinco políticas de Trusted Types. Preferimos llegar a una política pequeña para rich text y cero para el resto. Si un equipo necesita una política nueva por cada widget, probablemente está aceptando demasiada lógica de terceros dentro del documento principal.

Este criterio combina bien con el trabajo de componentes SSR que explicamos en Declarative Shadow DOM: enviar HTML estable desde el servidor, hidratar solo donde hay interacción y reservar las mutaciones de DOM para casos concretos. También es la respuesta práctica a la deuda que vemos al endurecer proyectos creados con asistentes de código: el riesgo no es que una IA conozca innerHTML; es que nadie pueda señalar quién controla cada ruta hasta él. Nuestra guía sobre seguridad de vibe coding en producción cubre la compuerta de revisión más amplia.

Trusted Types no sustituye el criterio técnico. Lo hace exigible. Para sitios con CMS headless, islas interactivas y media de varios proveedores, esa diferencia vale más que otro checklist de seguridad: hace que el siguiente atajo deje evidencia antes de llegar a producción.

Sources

[1] https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API — MDN: Trusted Types API [2] https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/require-trusted-types-for — MDN: CSP require-trusted-types-for [3] https://w3c.github.io/trusted-types/dist/spec — W3C: Trusted Types specification

Preguntas Frecuentes

¿Qué son Trusted Types?

Es una API web que obliga a pasar valores destinados a sinks de DOM sensibles, como innerHTML, por una política definida por el equipo. Esa política puede sanitizar HTML y el navegador rechaza strings directos cuando CSP activa la enforcement.

¿Astro o React eliminan la necesidad de Trusted Types?

No. Astro y React evitan muchos riesgos cuando renderizan contenido normal, pero un set:html, dangerouslySetInnerHTML, preview de CMS o widget que escribe en el DOM vuelve a abrir la frontera. Trusted Types controla esa frontera en el navegador.

¿Cómo se activa Trusted Types sin romper producción?

Primero inventaría los sinks y activa Content-Security-Policy-Report-Only. Después centraliza las políticas y corrige las violaciones de código propio y proveedores. Solo entonces cambia a require-trusted-types-for 'script' en enforcement.

Artículos Relacionados