Tema
Seguridad y límites
Como el canal de agentes es público, ur trae dos capas de defensa entre el mensaje del usuario y el modelo: límites de abuso (cuánto se puede hablar) y filtros de contenido (qué se puede decir).
Las dos se configuran en el panel. Lo importante para tu integración es cómo se manifiestan en la respuesta, porque no se parecen a un error de API.
El canal público es anónimo salvo que pidas usuario
/embed/* no lleva ningún secreto, y es una decisión deliberada: un <script> en el navegador no puede guardar uno, así que su modelo de confianza es distinto al de la API, que usa un access token. Por defecto, la identidad de conversación es un subjectId que genera el propio navegador, y lo que contiene el abuso son los límites de esta página.
La lista de orígenes permitidos del canal es una medida higiénica (evita que alguien copie tu snippet en otro sitio), no un control de acceso: un cliente que no sea un navegador puede omitir la cabecera Origin.
Lo que exija saber quién pregunta va con un usuario identificado: tu backend firma un token corto para el usuario de tu aplicación, el navegador lo manda en cada petición y ur lo verifica antes de abrir la conversación. La conversación pasa a ser de ese usuario, y las tools reciben quién es. El token lo firma siempre tu backend: ninguna clave va al navegador.
El orden en que se aplica todo
Cada mensaje pasa por estas puertas, en este orden:
- Límite de peticiones — por IP: el límite del agente y un tope general sumando todos los agentes. Devuelve un error HTTP de verdad (
429, conRetry-After).- Usuario identificado, si el agente lo pide: el token se verifica aquí, después del canal y el origen. Si no pasa,
401, sin abrir conversación ni gastar nada de lo que sigue. Ver Usuarios identificados.
- Usuario identificado, si el agente lo pide: el token se verifica aquí, después del canal y el origen. Si no pasa,
- ¿El agente está activo? — el interruptor general del agente.
- ¿Le quedan turnos a la conversación? — si el agente tiene límite de turnos y ya se agotó, el mensaje se descarta.
- Longitud — se recorta a
maxMessageLength. - Límite de ráfaga — mensajes demasiado seguidos.
- Límite por hora — cuántos mensajes por cliente y hora.
- Filtros de contenido — las cinco comprobaciones de abajo.
Si alguna bloquea, el turno no llega al modelo y se responde con un mensaje del catálogo.
Un bloqueo no es un error HTTP
Esto es lo que más sorprende al integrar
Un mensaje bloqueado devuelve 200, no 4xx. Para el usuario final es indistinguible de una respuesta normal: recibe un texto y la conversación sigue.
En el widget y el SDK llegan los tokens del mensaje de bloqueo y luego el fin de turno, igual que cualquier respuesta. onError no dispara.
Si tu UI depende de detectar bloqueos, no puede: por el canal público un bloqueo no se distingue de una respuesta, a propósito.
La excepción es lo anterior a todo esto: el límite de peticiones, que sí devuelve 429, y el token de un usuario identificado que no pasa, que devuelve 401. Y el fin de conversación por límite de turnos, que sí se distingue en todas las vías, porque tu UI tiene que cerrar la caja de texto.
Por defecto, el texto que se devuelve no dice qué comprobación saltó: todas las categorías del catálogo salen con el mismo texto genérico, y conviene dejarlo así para no darle a quien sondea un mapa de los filtros. El motivo real de los bloqueos por entropía, palabras prohibidas o firewall semántico queda en el registro de intercepciones de la ficha del agente; los bloqueos por límite de ráfaga o por hora no se registran ahí.
Límites de abuso
| Límite | Por defecto | Comportamiento |
|---|---|---|
| Longitud máxima | 2000 caracteres | Recorta en silencio, no rechaza |
| Mensajes por hora | Sin límite | Bloquea con mensaje de catálogo |
| Ráfaga | Desactivado | Bloquea mensajes demasiado seguidos |
| Turnos por conversación | Sin límite | Tras el último, la conversación se cierra |
| Peticiones por minuto desde una IP | 180 | Siempre activo. Al pasarlo, 429 hasta el minuto siguiente (widget y SDK) |
El límite por IP es por seguridad
Los demás límites reconocen al visitante por una huella que guarda su navegador, y un script puede inventarse una en cada petición; la IP no. Por eso este no se puede apagar. Cuenta todo lo que el widget o el SDK piden al agente desde una IP: cargarse, recuperar la conversación y cada mensaje. Detrás de una oficina o una VPN todos salen por la misma IP: si el agente se usa así, súbelo en su sección Seguridad. Por encima hay un tope general por IP para todos los agentes juntos (Ajustes → API, 600 por defecto), que es un freno de emergencia.
La longitud recorta, no falla
Un mensaje más largo que el límite no se rechaza: se corta y se procesa el principio. Si tu aplicación permite pegar textos largos, valida tú la longitud antes de enviar, o el usuario verá una respuesta a media pregunta sin saber por qué.
Quién cuenta como "un cliente" a efectos de estos límites: en el widget y el SDK es una huella que el navegador guarda aparte y que no se reinicia al empezar conversación nueva — justo para que resetear no sirva para esquivar el límite. Con un usuario identificado, el cliente es ese usuario: su cuota es la misma desde cualquier navegador.
Límite de turnos por conversación
Acota cuánto puede hablar alguien en una conversación, pensado sobre todo para un chat en una web pública. Se configura en la ficha del agente, sección Seguridad, con dos campos:
- Turnos máximos por conversación. El último turno recibe su respuesta normal y, detrás, el mensaje Límite de conversación del catálogo. A partir de ahí la conversación está cerrada: cualquier mensaje que llegue se descarta sin llamar al modelo y sin guardarse, y se vuelve a contestar con ese mismo texto.
- Olvidar la conversación tras (horas). Pasado ese tiempo sin que el navegador envíe nada, el widget o el SDK estrenan una conversación nueva, con los turnos a cero. Sin este ajuste, quien agota los turnos no puede volver a escribir desde ese navegador.
Qué cuenta como turno: un mensaje que llega al modelo. Los bloqueados por las puertas de arriba (seguridad, ráfaga, límite por hora, agente desactivado) no gastan turno, y un turno en el que el modelo falla se devuelve.
«Cerrada» no se guarda en ningún sitio: se calcula comparando los turnos hechos con el máximo vigente. Si subes o quitas el límite, las conversaciones cerradas se reabren; si lo bajas, se cierran las que ya lo superan. Los turnos se cuentan desde que se instaló la versión que trae el límite; las conversaciones anteriores empiezan en cero.
Cómo le llega el cierre a cada vía:
- Widget — pinta el mensaje de cierre como una burbuja propia, deja la caja de texto visible pero deshabilitada y, mientras el agente tenga límite, no ofrece Reiniciar conversación.
- SDK —
onConversationEnd(texto), y en el storeconversationEnded: truecon el mensaje marcadoisConversationEnd. Ver SDK JavaScript.
No es una barrera
La conversación vive en el navegador (un identificador en localStorage). Quien lo borre, o abra una ventana privada, empieza una conversación nueva con todos sus turnos. Sirve para la mayoría de visitantes, no contra alguien decidido; para eso están el límite por hora y el de ráfaga, que se cuentan con una huella que no se reinicia con la conversación, y contra un script que se invente la huella, el límite por IP.
Filtros de contenido
Cinco comprobaciones, en orden fijo. Las dos primeras son atajos: si pasan, se salta el resto.
| # | Comprobación | Qué hace |
|---|---|---|
| 1 | Palabras seguras | Si el mensaje completo coincide con una frase de la lista, pasa directo |
| 2 | Solo emojis | Un mensaje sin texto pasa directo |
| 3 | Entropía | Detecta cargas ofuscadas (cadenas hexadecimales, ruido, leet excesivo) |
| 4 | Palabras prohibidas | Coincidencia literal, con detección leet y difusa opcionales |
| 5 | Firewall semántico | Compara el significado con frases de ejemplo, por similitud |
En las palabras seguras, «coincide» quiere decir el mensaje entero. No cuentan las mayúsculas, las tildes, los signos de puntuación ni los espacios de más, pero solo unos pocos: «¡Hola!» pasa con la frase hola, y con más de 16 de esos caracteres (signos, espacios, caracteres invisibles…) el mensaje ya no pasa por este atajo. Todo lo demás cuenta: las letras y números de cualquier alfabeto («hola» seguido de una frase en chino no es hola) y los emoji y demás símbolos («hola 👋» tampoco; el mensaje que es solo emoji tiene su propio atajo, el siguiente). Para dejar pasar «gracias 🙏», añade la frase tal cual: casa con ese emoji, con cualquier tono de piel, y solo con él (gracias a secas necesita su propia frase). Una frase en otro alfabeto (ありがとう, привет) casa con ese mismo texto, y un mensaje que esconde texto en caracteres invisibles no pasa por este atajo.
Las reglas son globales; los interruptores, por agente
Las listas de palabras, las palabras seguras y las frases del firewall se definen una vez para toda la instalación, en Ajustes → Seguridad agentes del panel, una sección cada una (Palabras prohibidas, Firewall semántico, Palabras seguras y Detector de entropía). Sus categorías y el modelo de embeddings del firewall están en Ajustes → Sistema → Seguridad. Lo que se elige por agente es cuáles de las cinco comprobaciones se aplican y qué texto se devuelve al bloquear.
Las cuatro comprobaciones con configuración propia (palabras seguras, entropía, palabras prohibidas y firewall semántico) tienen en el panel un botón Probar texto para ver qué habría saltado. En la ficha del agente, Probar mensaje evalúa las cinco de una vez con los interruptores que tengas puestos en pantalla. Ninguno invoca al modelo del agente — el del firewall semántico sí calcula el embedding del texto que le pases. Y los dos recortan el texto como el chat: Probar mensaje, a la longitud máxima guardada del agente, y Probar texto, a 20.000 caracteres, lo más que puede recibir un agente.
Los textos que ve el usuario
Todos los mensajes de bloqueo salen de un catálogo editable — en el panel, Ajustes → Mensajes agentes: agente desactivado, límite por hora, ráfaga, límite de conversación, entropía, proveedor saturado, error del modelo, y uno por cada categoría de palabras prohibidas y del firewall.
Se resuelven en este orden: texto propio del agente → texto global → constante de emergencia. Así que puedes dejar el global para toda la instalación y cambiar solo el de un agente concreto.
Las entradas se crean solas al arrancar; en el panel solo se editan los textos.
Requisito de infraestructura
Sin Redis no hay chat
El límite de ráfaga y el rate limit por IP de todas las rutas públicas cuentan en Redis (el tope por hora se persiste en Postgres). Si Redis no está disponible, las rutas con rate limit devuelven 503 en vez de dejar pasar el tráfico — falla cerrado, a propósito. Tenlo presente al diagnosticar una caída: un 503 en /embed/* apunta a Redis, no al modelo.