14 sept 2025·8 min de lectura

Vulnerabilidades XSS en Markdown: pasos seguros para sanitizar texto enriquecido

Las vulnerabilidades XSS en Markdown pueden ocultarse en comentarios y notas. Aprende a sanitizar HTML de forma segura, restringir embeds y probar payloads reales antes del lanzamiento.

Vulnerabilidades XSS en Markdown: pasos seguros para sanitizar texto enriquecido

Por qué Markdown y el texto enriquecido pueden convertirse en un fallo de seguridad

Markdown y los editores de texto enriquecido dan sensación de seguridad porque parecen escritura simple. Pero muchas apps hacen lo mismo internamente: toman lo que escribió un usuario, lo convierten a HTML y renderizan ese HTML en el navegador de otra persona.

Ese último paso es donde empieza el problema. Si un atacante logra colar HTML que el navegador interpreta como código, puede ejecutar JavaScript en la sesión de otro usuario. Eso es XSS (cross-site scripting) en términos sencillos: el script del atacante se ejecuta como si viniera de tu sitio, con el acceso del usuario víctima.

Los comentarios, las notas y las respuestas de soporte son rutas de ataque habituales porque son fáciles de publicar (a menudo sin revisión), se muestran a muchas personas (administradores, compañeros de equipo, clientes) y se almacenan y renderizan después. Una mala publicación puede seguir dañando durante meses.

La parte complicada es que los editores Markdown y de texto enriquecido suelen generar HTML que no esperabas. Un pegado “simple” desde Google Docs puede traer etiquetas y atributos extraños. Algunas configuraciones de Markdown permiten HTML crudo a propósito. Algunos editores generan atributos que nunca planeaste soportar. Si tu app renderiza esa salida directamente, puedes acabar con vulnerabilidades XSS en Markdown incluso cuando la interfaz parece inofensiva.

Un ejemplo realista: un fundador añade una función de notas donde los compañeros pegan fragmentos y formatean texto. Alguien pega contenido que incluye un manejador de eventos como onerror en una imagen. Si tu renderer lo conserva, cada vez que un admin abra esa nota, el navegador ejecutará el payload.

Renderizar contenido es una característica de seguridad, no solo una decisión de formato.

Los riesgos básicos de XSS en contenido generado por usuarios

El XSS ocurre cuando tu app muestra entrada de usuario como si fuera contenido confiable de la página. Con Markdown y texto enriquecido, el riesgo aumenta porque a menudo conviertes la entrada a HTML y luego lo renderizas en los navegadores de otras personas.

Para comentarios, notas y perfiles, el problema más grave suele ser el XSS almacenado. Alguien publica un “comentario” que contiene HTML o JavaScript camuflado. Tu servidor lo guarda y cada persona que vea ese hilo ejecuta el código del atacante.

El XSS reflejado es otro tipo común: el payload se devuelve inmediatamente (por ejemplo desde una URL o un campo de búsqueda). También importa, pero el XSS almacenado es el que sigue golpeando con el tiempo.

Las víctimas no son solo usuarios aleatorios. El personal suele estar en mayor riesgo porque ve más contenido: moderadores, agentes de soporte y administradores que abren dashboards donde se muestran publicaciones de usuarios.

Si cae un XSS almacenado, un atacante puede robar cookies de sesión o tokens de acceso, leer datos privados mostrados en la UI (mensajes, correos, facturación), ejecutar acciones como el usuario (publicar, borrar, cambiar ajustes) o engañar a usuarios con UI falsa (phishing dentro de tu propio sitio).

“Solo usuarios internos tienen acceso” sigue siendo arriesgado. Las cuentas internas suelen tener más privilegios, reutilizan contraseñas y son confiadas por otros sistemas. Un comentario malicioso puede escalar rápidamente desde un usuario de bajo privilegio a una cuenta de personal.

Un ejemplo sencillo: un usuario publica una nota que parece normal, pero incluye un manejador de eventos escondido en HTML permitido. Cuando un agente de soporte hace clic para expandir la nota, el payload se ejecuta y envía silenciosamente su sesión al atacante. Así es como las vulnerabilidades XSS en Markdown se convierten en compromisos reales de cuentas.

Dónde se cuela HTML inseguro

La mayoría de los equipos esperan problemas solo cuando permiten HTML crudo. La sorpresa es que puedes sufrir vulnerabilidades XSS en Markdown incluso cuando los usuarios solo escriben “Markdown normal”.

Las características de Markdown se compilan en HTML que el navegador interpretará felizmente si no lo limpias. Los enlaces e imágenes son los grandes culpables: un [texto](...) o ![](...) aparentemente inocuo se convierte en una etiqueta \u003ca\u003e o \u003cimg\u003e. La parte riesgosa suele ser no la etiqueta en sí, sino el esquema de la URL y los atributos que terminan dentro.

Algunos parseadores de Markdown también permiten bloques de HTML crudo por defecto. Eso significa que un usuario puede pegar \u003cimg onerror=...\u003e o \u003csvg\u003e directamente en un comentario y que pase sin cambios, para luego ejecutarse al renderizarse. Incluso si crees “escapamos HTML”, revisa la configuración del parser y cualquier plugin que lo re-habilite.

Los editores de texto enriquecido pueden ser peores porque generan HTML que a simple vista parece inofensivo. Una frase en negrita puede incluir atributos extra, estilos inline y etiquetas extrañas que tu sanitizador debe entender correctamente. Un fallo común es permitir etiquetas “seguras” pero olvidar atributos peligrosos, como manejadores de eventos (onload, onclick) o atributos con URL que pueden esconder payloads javascript:.

Copiar y pegar desde Google Docs o Notion es una fuente frecuente de marcado desordenado. Los usuarios pegan texto con formato y de repente tienes spans anidados, CSS inline y atributos de metadatos que nunca formaron parte de tu plan. Ese HTML extra aumenta la posibilidad de una evasión o de que tu sanitizador rompa el formato de forma impredecible.

Durante la revisión, céntrate en los puntos de entrada que causan problemas repetidamente: HTML crudo habilitado en el parser de Markdown; enlaces o imágenes donde la URL no esté restringida a esquemas seguros; listas de “atributos permitidos” demasiado amplias; plugins que añaden funciones HTML (tablas, menciones, embeds); y rutas de pegado que aceptan HTML completo en lugar de texto plano.

Elige un modelo de contenido seguro antes de elegir un sanitizador

La mayoría de los bugs de XSS almacenado empiezan con un desajuste: pensabas que guardabas “comentarios”, pero tu sistema en realidad está guardando mini páginas web. Antes de elegir cualquier librería, decide qué pueden expresar los usuarios.

Una forma práctica de pensar en las vulnerabilidades XSS en Markdown es elegir un modelo de contenido y ceñirse a él:

  • Texto plano: lo más seguro y fácil. Aun así puedes soportar básicos como saltos de línea y autolinks simples.
  • Markdown limitado: bueno para la mayoría de productos. Permite formato (negrita, cursiva, listas, código) pero mantenlo predecible.
  • Texto enriquecido completo (estilo HTML): mayor riesgo. Solo elige esto si realmente necesitas diseños complejos.

Una vez elegido, escribe tus reglas como una allowlist. Para Markdown limitado, un conjunto típico seguro de elementos es: p, strong, em, ul, ol, li, code, pre y a. Mantén la lista corta a propósito.

También sé explícito sobre lo que nunca se permite. Lo obvio: script, iframe, object, embed y style. Pero muchas etiquetas “normales” pueden ser peligrosas según tu configuración, especialmente cualquier cosa que pueda cargar contenido remoto o afectar la página.

Los atributos necesitan el mismo trato. Por ejemplo, los enlaces podrían permitir solo href, y rechazar todo lo que parezca un manejador de eventos (onclick, onerror, y similares).

Cómo sanitizar HTML de forma segura (sin romperlo todo)

La manera más segura de manejar las vulnerabilidades XSS en Markdown es asumir que el contenido de usuario y las dependencias cambiarán con el tiempo. Tu parser de Markdown se actualiza, los navegadores cambian y etiquetas que parecían “inofensivas” pueden adquirir nuevos comportamientos.

Por eso sanitizar solo al guardar una publicación es arriesgado. Sanitiza también al renderizar, de modo que el contenido antiguo siga siendo seguro tras una actualización de dependencia o un cambio de configuración.

Una regla práctica: prefiere una allowlist. Las blocklists tienden a perder casos límite (nuevas etiquetas, atributos raros, peculiaridades del navegador). Una allowlist responde a una pregunta clara: “¿Qué permitimos en los comentarios?” Normalmente eso es formato básico, enlaces simples y nada que pueda ejecutar código.

Antes de sanitizar, normaliza lo que vas a sanitizar. Los atacantes usan trucos como caracteres codificados y espacios inusuales para eludir filtros. Decodifica entidades, normaliza Unicode y parsea a un árbol HTML real (no con regex). Luego ejecuta el sanitizador sobre esa representación normalizada para que no lo engañen con ortografías alternativas.

Un flujo práctico que evita la mayoría de roturas:

  • Convierte Markdown a HTML usando una librería de confianza.
  • Normaliza y decodifica el HTML (entidades, Unicode, espacios en atributos).
  • Sanitiza con una allowlist (etiquetas + atributos + esquemas de URL).
  • Renderiza la salida sanitizada y aplica una Content Security Policy segura por separado.
  • Registra o marca contenido que sea fuertemente recortado (a menudo señal de sondeo).

Mantén las reglas del sanitizador en un solo lugar y trátalas como código. Versiona la configuración, añade un breve changelog y escribe pruebas que verifiquen qué se mantiene y qué se elimina. Ejemplo: si más adelante decides permitir \u003cimg\u003e, cambia la allowlist y actualiza las pruebas, luego re-sanitiza en tiempo de render para que comentarios antiguos no se vuelvan peligrosos.

Endurece enlaces, imágenes y estilos

Harden Links and Images
Lock down links, images, and URL schemes so “normal” Markdown can’t execute code.

Enlaces, imágenes y estilos son donde el Markdown “seguro” a menudo se convierte en XSS almacenado. Incluso si sanitizas etiquetas HTML, debes tratar cada URL y cada valor de estilo como entrada no confiable.

Empieza por los enlaces. Un a aparentemente normal puede convertirse en ataque si permites esquemas de URL peligrosos como javascript: o data:. La regla más segura es simple: permite solo https: (y quizá http: para herramientas internas o dev), y rechaza todo lo demás. Normaliza y decodifica antes de comprobar, porque los atacantes usan mayúsculas mezcladas y caracteres codificados.

Si abres enlaces de usuarios en una nueva pestaña con target="_blank", protégelo. Sin los valores rel correctos, la nueva página puede controlar la original (tabnabbing). Haz esto por defecto en tu renderer en lugar de confiar en los autores.

Las imágenes no son “solo imágenes”. Un src puede apuntar a píxeles de tracking, recursos de la red interna o esquemas raros. Si permites imágenes, restringe los esquemas de src y considera proxificar las imágenes para que el navegador no las solicite directamente a servidores controlados por atacantes.

El estilo es un peligro silencioso. Aunque el CSS no ejecute scripts, puede ocultar advertencias, mover botones o hacer que un cuadro de inicio de sesión falso parezca real. Para la seguridad de comentarios, prefiere una pequeña allowlist de formato simple (negrita, cursiva, listas) y evita permitir CSS arbitrario.

Conjunto práctico de reglas:

  • Permite solo https: (opcionalmente http:) en href y src; bloquea javascript:, data:, file: y blob:.
  • Si target="_blank" está permitido, fuerza rel="noopener noreferrer".
  • Elimina style inline y bloquea \u003cstyle\u003e por completo.
  • Mantén soporte de imágenes al mínimo, o proxifícalas y cachea en servidor.
  • Define límites claros: longitud máxima de URL, máximo de atributos, máximo de elementos.

Ejemplo: una app de notas renderiza Markdown y permite imágenes. Un atacante publica un “diagrama útil” que se carga desde su servidor, registra cada visualización y usa CSS para ocultar el verdadero botón “Eliminar nota” bajo un falso aviso de “Re-autenticar”. Arreglar las vulnerabilidades XSS en Markdown significa tratar estos problemas como riesgos centrales de producto, no como casos raros.

Embeds: la forma más rápida de permitir ejecución de scripts por accidente

Los embeds parecen inocuos porque se ven como “solo un vídeo” o “solo un tweet”. En la práctica, son una de las formas más rápidas de convertir contenido de usuario en XSS almacenado, sobre todo si Markdown o el editor permiten HTML crudo. Muchas vulnerabilidades XSS en Markdown empiezan por una excepción que alguien añadió para iframes.

Si soportas embeds, decide desde el principio qué proveedores están permitidos y qué significa “embed” en tu app. “Cualquier iframe” no es una característica, es un agujero de seguridad.

Un patrón más seguro: el usuario pega una URL normal, tu servidor la comprueba contra una allowlist y luego tu servidor genera el HTML final del embed. No aceptes etiquetas iframe proporcionadas por el usuario ni atributos arbitrarios como srcdoc, onload o allow.

Reglas que mantienen los embeds útiles sin dar superficie de scripting:

  • Permite solo proveedores específicos (por nombre de host y patrón de ruta) y bloquea todo lo demás.
  • Genera el HTML de embed en el servidor a partir de una plantilla limpia, no desde HTML del usuario.
  • Deshabilita iframes inline en comentarios/notas salvo que haya una razón fuerte.
  • Si debes permitir un iframe, establece límites de tamaño y un sandbox estricto.
  • Elimina todos los manejadores de eventos y atributos riesgosos; nunca permitas URLs javascript:.

Incluso con sandboxing, recuerda que los embeds cargan contenido de terceros. Trátalos como un límite separado.

Prueba payloads reales de XSS antes de lanzar

Rescue an AI Prototype
We diagnose messy AI-generated frontends and backends, then refactor to production-ready code.

Los sanitizadores suelen parecer correctos en una demo rápida y luego fallan con la entrada rara que crean los usuarios reales. Antes de lanzar comentarios o notas, ejecuta un pequeño conjunto de pruebas repetibles que intenten romper tu renderer y tus reglas de sanitización.

Empieza probando con tres roles y tres vistas. Usa un usuario normal que pueda publicar contenido, luego verifica lo que ve un moderador y lo que ve un administrador. El XSS almacenado suele activarse solo cuando otra persona carga la página, especialmente en dashboards, colas de moderación, vistas previas de correo o paneles de “actividad reciente”.

Utiliza una suite breve de payloads que cubra estilos de bypass comunes (no te fíes de uno o dos obvios). Por ejemplo, prueba caracteres codificados (entidades HTML, atributos con mayúsculas mezcladas), etiquetas malformadas o sin cerrar (para confundir al parser), etiquetas anidadas (etiqueta exterior segura, interior peligrosa), URLs peligrosas dentro de enlaces (javascript: o data:) y atributos manejadores de eventos (como onerror) en cualquier etiqueta que tu sanitizador permita.

Mantén las pruebas realistas: el contenido debería seguir renderizando si es inofensivo, pero nunca debe ejecutar código. Una buena comprobación es: “¿Este comentario aparece como texto o formato seguro, sin popups, redirecciones, llamadas de red ni cambios inesperados en la UI?”

También verifica el comportamiento en todos los lugares donde muestras el mismo contenido. Sanitizar en el editor pero no en el correo de notificación, o hacerlo en la página de comentarios pero no en la vista de administración, es una ruta clásica de XSS almacenado.

Errores comunes que causan XSS almacenado

El XSS almacenado suele ocurrir cuando asumes que el contenido del usuario “ya es seguro” porque proviene de un editor pulido. Un WYSIWYG puede seguir produciendo HTML peligroso (o ser engañado para producirlo), y los parseadores de Markdown a menudo permiten casos límite sorprendentes. Por eso las vulnerabilidades XSS en Markdown aparecen en productos que “solo soportan comentarios”.

Una trampa frecuente es sanitizar solo en el navegador. La limpieza del cliente es fácil de eludir con una petición directa a tu API o re-leyendo una petición desde otro dispositivo. Si el servidor guarda contenido sin sanear, tienes un bug de XSS almacenado esperando a dispararse en todos los lugares donde se muestre ese contenido.

Otra equivocación es permitir HTML crudo dentro de Markdown para mantener funcionalidades (botones personalizados, iframes, estilos fancy). Esa decisión convierte silenciosamente tu función de Markdown en una característica de hosting HTML. Incluso si eliminas etiquetas obvias como \u003cscript\u003e, los atacantes pueden usar manejadores de eventos (onerror), URLs trucadas o payloads basados en SVG según lo que permitas.

Una gran fuente de incidentes son los “renderers secundarios” que olvidas. Puedes sanitizar la página principal de comentarios, pero no la vista de administración, la plantilla de correo ni el flujo de exportación a PDF.

Patrones de fallo recurrentes: tratar la salida del editor como datos confiables y almacenarla tal cual; limpiar solo en el cliente y luego guardar el contenido crudo en el servidor; usar sanitizadores distintos (o allowlists distintas) en distintos lugares; renderizar el mismo contenido guardado en HTML, email y herramientas internas sin volver a comprobar; y registrar o previsualizar HTML crudo en dashboards internos.

Ejemplo: un usuario publica un “comentario inocente” que incluye una imagen con un atributo especialmente diseñado. La página pública es segura, pero el panel de administración usa un renderer diferente para moderación y el payload se ejecuta cuando el personal abre la cola.

Lista rápida de seguridad para comentarios y notas

Los comentarios y las notas son donde las vulnerabilidades XSS en Markdown suelen aparecer primero, porque parecen inofensivos y se lanzan rápido. Antes de activarlos para usuarios reales, haz una revisión rápida con mentalidad de seguridad.

Checklist que captura la mayoría de problemas de XSS almacenado:

  • Confirma que el HTML crudo está totalmente deshabilitado en Markdown, o que se sanitiza después de renderizar. No confíes en “el editor no lo generará”.
  • Usa una allowlist para etiquetas y atributos. Bloquea todos los manejadores de eventos como onclick y evita atributos riesgosos como style salvo que los filtres estrictamente.
  • Valida y normaliza URLs en href y src. Rechaza esquemas javascript: y data: (y todo lo que no explíctamente soportes).
  • Endurece los embeds. Si permites iframes o funciones de pegar vídeo, establece reglas estrictas y considera renderizarlos como enlaces simples en vez de embeds.
  • Revisa todos los lugares donde aparece el contenido, no solo la página principal: vistas de administrador, correos de notificación, webviews móviles, exportaciones (PDF/impresión) y dashboards internos.

Después de la checklist, haz una prueba rápida con payloads reales. El objetivo no es “ver una alerta”, sino confirmar que tu salida se mantiene inerte en todas partes.

Prueba algunas entradas conocidas malas (script tags, atributos de manejadores de eventos y URLs raras) y confirma que se muestran como texto o se eliminan. Verifica la versión almacenada en la base de datos, no solo la vista previa. Repite la prueba en el lado administrativo, ya que los admins suelen ver más contenido y tienen privilegios mayores.

Escenario de ejemplo: un simple sistema de comentarios que se convierte en XSS

Fix Stored XSS Fast
FixMyMess repairs unsafe rendering paths and cleans up risky HTML handling in AI-built apps.

Un fundador lanza un widget de feedback: los usuarios pueden dejar comentarios en Markdown en cada página. Parece seguro porque “es solo texto” y la vista previa se ve bien.

Para soportar texto rico, la app convierte Markdown a HTML y luego lo renderiza en el dashboard de administración. Alguien añadió “cosas útiles”: autolink de URLs, soporte de imágenes y un embed rápido para vídeos.

Un atacante publica un comentario que parece normal en el widget, como un informe de error con un enlace. Pero el Markdown contiene HTML que el convertidor conserva, o esconde un payload en un atributo permitido. Nada le pasa al atacante. Más tarde, cuando un administrador abre el dashboard, el comentario ejecuta código en el navegador del admin.

Lo que rompe después rara vez es sutil. El atacante puede robar la sesión del admin y tomar control de la cuenta, leer feedback privado o notas internas en la misma página y cambiar ajustes (webhooks o claves API) usando los permisos del admin.

Un diseño más seguro lo habría detenido antes de lanzarlo. Trata los comentarios como datos no confiables y limita qué significa realmente “texto enriquecido”: convierte Markdown a un subconjunto HTML limitado, sanitiza con una allowlist estricta, elimina o reescribe atributos riesgosos (especialmente manejadores de eventos y ciertos esquemas de URL), deshabilita embeds por defecto (o permite solo un conjunto pequeño de proveedores con reglas estrictas) y prueba payloads reales en la vista administrativa exacta, no solo en el widget público.

Siguientes pasos: desplegar de forma segura y pedir una segunda opinión

Si quieres lanzar comentarios o notas sin sorpresas, trata el texto enriquecido como una característica que necesita un pequeño plan de seguridad, no como un añadido rápido de UI.

Empieza por documentar decisiones que puedas mantener consistentes en toda la app: elige un modelo de contenido (texto plano, Markdown sin HTML o HTML sanitizado), define elementos y atributos permitidos (sé estricto; la mayoría de apps necesita muy poco), endurece los embeds desde el principio (o evítalos hasta tener tiempo), crea un pequeño conjunto de payloads XSS que cubra tus características (enlaces, imágenes, bloques de código, menciones) y decide dónde ocurre la sanitización (el servidor debe ser la fuente de verdad).

Luego añade una puerta de salida en los despliegues. La meta es sencilla: no se despliega nada a menos que tus payloads guardados se rendericen de forma segura en la UI real. Esto captura problemas que las pruebas unitarias no ven, como un plugin de Markdown en el cliente que habilita HTML sin avisar.

La puerta de despliegue puede ser ligera. Ejecuta el conjunto de payloads contra los flujos de crear, editar y vista previa. Verifica la salida en el navegador, no solo en las respuestas de la API. Confirma que las mismas reglas se aplican en todos los lugares donde aparece el contenido (feed, correo, vistas de admin). Añade una prueba de regresión por cada bug que encuentres para que permanezca corregido.

Si tu app fue generada o asistida por herramientas como Lovable, Bolt, v0, Cursor o Replit, asume que los valores por defecto pueden ser inconsistentes. Una pantalla puede usar un renderer seguro y otra una librería distinta o un modo de vista previa que permite HTML crudo.

Si quieres una segunda opinión de bajo fricción, FixMyMess (fixmymess.ai) se centra en diagnosticar y reparar codebases generados por IA, incluyendo rutas inseguras de renderizado Markdown y texto enriquecido, y puede empezar con una auditoría de código gratuita para localizar riesgos de XSS almacenado y problemas relacionados antes de lanzar.

Preguntas Frecuentes

Why can Markdown be a security risk if it’s “just text”?

Markdown normalmente se convierte en HTML, y ese HTML se renderiza en el navegador de otra persona. Si alguna parte del contenido sobrevive como HTML ejecutable o contiene atributos peligrosos, puede convertirse en un XSS almacenado, incluso cuando la interfaz del editor parece “solo texto”.

What type of XSS is most common with comments and notes?

Suele tratarse de XSS almacenado: un comentario o nota maliciosa se guarda y luego se ejecuta cuando otra persona la ve. Es peor que el XSS reflejado porque puede seguir afectando a administradores, agentes de soporte y clientes mucho tiempo después de la publicación original.

Where does unsafe HTML usually sneak in with Markdown or rich-text?

El soporte de HTML crudo es la causa principal, pero también puedes verse afectado a través de enlaces e imágenes si no restringes los esquemas de URL. Pegar desde editores WYSIWYG también puede añadir etiquetas y atributos inesperados que tu sanitizador no maneja como piensas.

What’s the safest “content model” to choose for user comments?

Por defecto, usa Markdown limitado: permite formato básico y enlaces, y rechaza todo lo demás. Mantén el conjunto permitido pequeño y explícito para evitar alojar mini páginas web dentro de los comentarios.

Should I sanitize on save, on render, or both?

Sanitiza también en el momento de renderizar, no solo al guardar. La sanitización en render ayuda a proteger cuando tu parser Markdown, la configuración del sanitizador o el comportamiento del navegador cambian y el contenido antiguo se vuelve peligroso.

How do I make Markdown links safe against `javascript:` tricks?

Permite solo esquemas seguros como https: (y opcionalmente http: en casos controlados) tras decodificar y normalizar. Bloquea javascript:, data:, file: y otros esquemas inesperados, ya que son vías comunes para introducir ejecución o comportamientos extraños en enlaces Markdown aparentemente normales.

Are images in Markdown dangerous, or just annoying?

Si debes soportar imágenes, trata src como no confiable: restringe esquemas y considera proxificar imágenes para que el navegador no las solicite directamente a servidores controlados por atacantes. Si realmente no necesitas imágenes en comentarios, lo más seguro es desactivarlas.

What’s the safest way to support embeds (videos, tweets, etc.)?

No aceptes HTML arbitrario de iframe desde usuarios. Un enfoque más seguro es permitir que el usuario pegue una URL normal y que tu servidor genere un fragmento de embed fijo solo para proveedores permitidos.

What’s one mistake teams make when they “sanitize” rich-text?

Sanitiza en el servidor, no solo en el navegador. El filtrado cliente es fácil de eludir llamando directamente a la API, lo que puede dejar contenido sin sanear almacenado y luego renderizado en vistas administrativas o correos.

How can I quickly test if my Markdown rendering is vulnerable to XSS?

Prueba un pequeño conjunto de payloads realistas en todos los lugares donde aparece el contenido: la página pública, pantallas de administración/moderación, notificaciones y vistas previas. El objetivo es que la entrada permanezca inerte en todas partes: sin ventanas emergentes, redirecciones, cambios inesperados de UI ni llamadas de red ocultas.