Widgets de chat embebibles: arquitectura y seguridad
El loader en dos pasos y el iframe sandboxeado detrás del widget de Simmetric Chat, con sesiones aisladas, rate limiting y acceso por capas a la base de conocimiento.
Tu base de conocimiento es una superficie de producto
Los documentos que escribe tu equipo - documentación de producto, artículos de soporte, wikis internos - responden a las mismas preguntas que los visitantes de tu sitio ya se están haciendo. Un widget de chat embebido convierte esa base de conocimiento en una puerta de entrada conversacional: los visitantes preguntan con sus propias palabras, y las respuestas fluyen con citas, ancladas en los workspaces que decidas exponer.
La parte difícil no es el chat; es embeber software casi no confiable en la página de otro sin convertirse en una superficie de ataque. El sistema de widgets de Simmetric Chat - nivel Enterprise - está construido en torno a esa restricción. Este artículo recorre la arquitectura: el embed en dos pasos, el runtime sandboxeado, el aislamiento de sesiones, el rate limiting y el modelo de acceso por capas a la base de conocimiento que decide qué puede alcanzar la pregunta de un visitante, y qué no.
El embed en dos pasos: loader e iframe sandboxeado
El widget se entrega en dos pasos, y la división es deliberada:
- El script loader. Añade una etiqueta script a tu sitio, apuntada al servicio widget con tu ID de widget. El loader es un pequeño archivo JavaScript que crea un iframe en la página anfitriona.
- El panel iframe. El iframe carga una aplicación Preact autónoma - compilada como bundle IIFE con CSS en línea - que renderiza el botón flotante, el panel de chat y cada interacción dentro de su propio documento.
<script src="https://your-simmetric-chat.com/widget/loader.js?widgetId=WIDGET_ID"></script>¿Por qué no inyectar la interfaz de chat directamente en la página anfitriona? Porque interpolar cadenas de contenido no confiable en una página anfitriona es el camino clásico del XSS - y todo el flujo de contenido de un widget es casi no confiable: contiene salida del modelo, extractos de documentos y entradas de visitantes. Ejecutar la interfaz dentro de un iframe sandboxeado de origen opaco elimina esa clase de riesgo por completo: el contenido del chat nunca se ejecuta en el contexto de tu página, y los estilos del widget no pueden colisionar con los tuyos (ni viceversa).
Las antiguas rutas de embed por interpolación de cadenas se retiraron del producto exactamente por esa razón - el patrón en dos pasos no es una opción entre varias, es el embed. La compilación IIFE del widget existe por la misma razón: el panel es un único archivo autónomo sin sistema de módulos y sin carga de assets externos en tiempo de ejecución, lo que mantiene predecible la superficie de red del iframe.
Qué corre dónde
El widget toca tres procesos, y conocer el mapa de fronteras explica cada propiedad de seguridad que sigue:
- El servidor principal posee el CRUD y el branding de los widgets, la vinculación a workspaces, la API interna que llama el servicio widget, y los endpoints lead y analytics del lado admin. Todo el router interno de widgets está atado a la feature - cuando la feature widget está apagada, responde
402en cada ruta. - El servicio widget es la superficie de embed pública: loader JS, HTML del iframe, creación de sesiones, entrega de la configuración en caché, el proxy SSE del chat hacia el servidor principal y el endpoint lead. Se autentica ante el servidor principal con una clave API dedicada y lleva sus propios rate limiters.
- El iframe es el cliente Preact: bootstrap de la sesión, streaming SSE, visualización de citas, preguntas sugeridas, la barrera de consentimiento y la tarjeta lead.
El proxy del chat reenvía el mismo formato de eventos de la API de streaming interna - mensajes de estado, anuncios de plan, tokens en streaming, citas y eventos terminales done/error - así que lo que experimenta un visitante es el mismo pipeline que tu equipo usa internamente, con los permisos del widget.
Sesiones aisladas para visitantes anónimos
Los visitantes nunca se autentican; el widget acuña la identidad en su lugar. Cuando el panel se abre, pide una sesión al servicio widget, que pide al servidor principal crearla: un token hexadecimal de 256 bits, del lado del servidor, con caducidad de 24 horas. Cada petición posterior del widget valida contra esa sesión - ningún JWT de las apps de tus usuarios, ninguna cookie que cruce orígenes, ninguna credencial ambiental de ningún tipo.
El aislamiento de sesiones es lo que mantiene el estado de un visitante fuera del alcance de otro: las conversaciones son anónimas, los contadores y el estado de los rate limits se enganchan al token de sesión en lugar de a una IP o a una huella, y la página anfitriona nunca ve el token - vive en el storage del propio iframe, intercambiado con el anfitrión mediante un pequeño protocolo postMessage versionado que ambos lados validan contra el exacto contentWindow que esperan, de modo que los mensajes falsificados por otros scripts se ignoran.
La entrega de la configuración sigue la misma disciplina. La configuración del widget - branding, mensaje de bienvenida, preguntas sugeridas - se entrega al iframe mediante un bloque JSON en el documento del propio iframe, inyectado del lado del servidor, no mediante parámetros de URL que un visitante podría manipular. El visitante no puede influir en nada que el servidor no resuelva: una adición reciente lo consagra aún más, permitiendo que la configuración de cada widget seleccione el provider LLM y el modelo que sirve sus respuestas de chat, resueltos del lado del servidor en cada petición de chat - nunca proporcionados por el cliente.
El rate limiting como contrato de superficie pública
Un widget es un endpoint público sobre tu infraestructura, así que el control de abusos es parte de la arquitectura, no un añadido posterior. Los límites están estratificados por superficie:
- Límites de chat por sesión: 20 mensajes por hora y 5 conversaciones al día por sesión; los límites superados responden
429con una indicaciónretryAfter, y el widget muestra un aviso de rate limit en lugar de fallar en silencio. - Limiter de API por clave: las llamadas del servicio widget al servidor principal están limitadas por clave API hasheada, para que un despliegue defectuoso no pueda agotar la superficie compartida.
- Limiter de leads: el endpoint de lead capture es la barrera más estricta, a 3 peticiones por hora, porque un formulario público es exactamente donde se concentra el abuso de automatización.
- CORS fail-closed: la cabecera
Originde cada petición se valida contra la lista de orígenes permitidos del propio widget, editable desde el panel de administración. Las peticiones preflight no permitidas se rechazan - y si la comprobación de origen no puede consultar la base de datos, el acceso se deniega en lugar de concederse.
El resultado es una superficie de chat pública que degrada con gracia bajo carga o abuso en lugar de colapsar o excluir a todos.
Acceso por capas a la base de conocimiento
Un widget debería responder desde el conocimiento que pretendes publicar - nada más. La estratificación del acceso se impone estructuralmente en lugar de por prompt:
- Whitelist de workspaces. Cada widget está vinculado a uno o varios workspaces, y la whitelist se reemplaza atómicamente en cada actualización. El conocimiento del que un widget puede extraer es exactamente la unión de las colecciones vectoriales y los documentos de los workspaces vinculados - un widget de soporte puede ver tu workspace de documentación pública sin tocar jamás los datos de proyectos internos.
- Atadura a la feature. La creación de widgets requiere el flag de la feature widget y respeta el límite numérico de widgets del plan, para que la superficie no pueda crecer en silencio.
- Citas como fronteras. Las respuestas fluyen con citas que apuntan solo a workspaces permitidos - un visitante puede verificar de dónde viene una respuesta, y la traza de citas no puede apuntar fuera de la whitelist.
El branding - color primario, nombre del bot, logos, posición - es configuración por widget renderizada mediante propiedades CSS personalizadas dentro del iframe, para que una consultora pueda presentar el asistente como propio de su cliente. Los despliegues de nivel Community reciben defaults sensatos aplicados cuando los campos están vacíos; la personalización completa y el white-labeling son flags enterprise.
La checklist de seguridad, resumida
Cada capa de arriba existe porque un widget es software que envías dentro de la frontera de confianza de otro:
- Loader en dos pasos + iframe sandboxeado de origen opaco - sin XSS por interpolación de cadenas
- Configuración inyectada por el servidor - nada relevante para la seguridad lo proporciona el cliente
- Model pin resuelto por el servidor - el modelo que responde no puede ser manipulado por el navegador del visitante
- Sesiones anónimas de 256 bits con caducidad de 24 horas - sin credenciales ambientales
- Rate limits por capas con
429+retryAfter- degradación graciosa bajo abuso - CORS dinámico fail-closed - la confianza de origen decidida en exactamente un punto
- Whitelist de workspaces atómica - el acceso al conocimiento es estructural, no esperado en el prompt
Del panel de administración al snippet funcionando el trayecto son minutos: crea el widget, pon en whitelist los workspaces, añade los orígenes, pega la etiqueta script. La guía completa de integración cubre el branding, el protocolo simmetric:* postMessage y el lead capture de punta a punta.


