Aislamiento por espacio de trabajo
Tu espacio de trabajo es la frontera. Las páginas, el conocimiento, las imágenes, las claves y los miembros pertenecen todos a exactamente uno, y nada lo cruza. Eso se hace cumplir en cuatro capas independientes, cada una de las cuales falla cerrada por sí sola:1
En el borde
Cada ruta que nombra un espacio de trabajo verifica que quien llama es miembro de ese espacio de trabajo antes de que corra cualquier otra cosa. Un identificador malformado se rechaza como solicitud inválida, nunca se convierte en un error interno.
2
En el contexto de la solicitud
Esa verificación de membresía es lo único autorizado a establecer en qué espacio de trabajo opera una solicitud. El código que de algún modo corre sin una, falla; nunca recurre a “todo”.
3
En la capa de datos
El código de la aplicación no puede pedir datos sin un espacio de trabajo, y no puede sobrescribir el espacio de trabajo que recibió: el discriminador no es un parámetro que quien llama pueda suministrar.
4
En el driver de la base de datos
Una red de seguridad final vuelve a aplicar el filtro de espacio de trabajo a cada consulta y cada agregación, incluidas las búsquedas vectoriales, de modo que una consulta escrita a mano igual regresa acotada.
- El índice vectorial no tiene namespaces por cliente, tiene un prefiltro. Tu espacio de trabajo es parte del filtro de absolutamente todas las búsquedas, aplicado antes de calcular la similitud. Es la garantía de aislamiento, no un detalle de rendimiento.
- Los archivos almacenados viven bajo un prefijo por espacio de trabajo y se sirven mediante enlaces firmados de corta duración, nunca desde un bucket público. La única excepción es el arte de marca (tu logo y tu favicon), que una página pública necesita en una dirección estable: una ruta fija en tu propio sitio obtiene el objeto privado con una firma del lado del servidor, así que la dirección es pública y el bucket nunca lo es.
- Cada ruta que recibe un id de recurso nace con una prueba entre espacios de trabajo. El caso que importa no es un extraño pidiendo los datos de otro; eso lo responde el borde. Es un miembro legítimo del espacio de trabajo A apuntando una ruta de A a un id que pertenece a B, algo que solo las capas más profundas pueden atrapar. Esa suite corre en cada cambio, junto con una suite HTTP adversaria que incluye identificadores contrabandeados, ids con forma de operador y enlaces firmados falsificados.
El agente no inventa
La falla más costosa para un producto como este es un agente que promete un precio, una garantía o una fecha de entrega que el negocio nunca ofreció. Cuatro mecánicas la impiden:- Las respuestas están fundamentadas en material recuperado. Las cifras, las afirmaciones y las atribuciones salen de los pasajes que tu conocimiento devolvió para esa pregunta.
- El modelo selecciona; nunca escribe direcciones. Los componentes salen de un catálogo cerrado, las imágenes de tu registro por id, los enlaces de la lista que aprobaste por id. Sus esquemas hacen que una URL sea estructuralmente imposible de expresar, así que el modelo no tiene un campo donde inventarla.
- Cuando el material no respalda una respuesta, el agente lo dice y ofrece un siguiente paso en lugar de llenar el vacío.
- Cada componente se valida, se sanitiza y se resuelve en el servidor, en ese orden, antes de renderizarse. Un componente que falla se descarta y el resto de la respuesta llega igual.
El aviso de IA
Cada página con la que un visitante puede hablar, incluido el widget embebido, indica que está hablando con una IA. El aviso se renderiza en el servidor, así que existe antes de que corra cualquier JavaScript; no tiene control para descartarlo, y no hay configuración que pueda agregar uno; y se renderiza sin importar el tema de la página. Es la transparencia del Artículo 50 del EU AI Act, aplicable desde el 2 de agosto de 2026: un cliente puede reestilizar la página a su alrededor pero no puede quitarlo. Las páginas de documento (páginas de markdown plano) no llevan agente, así que no llevan aviso.Sesiones y credenciales
La sesión de la consola es una cookie, nunca un token en el almacenamiento de tu navegador. Es un valor opaco en una cookie__Host-: HttpOnly, Secure, SameSite=Strict. La base de datos guarda solo su hash, así que un volcado de la base de datos no le entrega a nadie una sesión viva. Se desliza con la actividad hasta una ventana de inactividad de 30 minutos, con un tope duro de 30 días, y es revocable desde el servidor en el momento en que cierras sesión. La falsificación de solicitudes entre sitios se bloquea en tres capas: la cookie SameSite, una verificación de origen en cada mutación y un token de doble envío ligado criptográficamente a la sesión.
Las credenciales de máquina son propias de wondeya, con hash y acotadas:
No hay una clave de toda la plataforma, y una clave sin scope es inválida: “sin scope” nunca se lee como “todos los scopes”. Revocar surte efecto en la siguiente solicitud.
La conexión MCP es OAuth 2.1. Un inicio de sesión en el navegador y una pantalla de consentimiento en la consola ligan la conexión a exactamente un espacio de trabajo, y cada token se verifica contra esta API como su audiencia. Ninguna herramienta de MCP acepta un identificador de espacio de trabajo: el espacio de trabajo sale de la credencial, así que apuntar una herramienta al espacio de trabajo de otro no es una solicitud que se pueda expresar. Las claves de API no funcionan en MCP; pertenecen solo a la API REST. Detalles en MCP.
Entrada no confiable, tratada como no confiable
- Las subidas se parsean fuera de la solicitud. Un archivo se almacena y se encola; la cola hace la lectura. Un PDF malformado no puede atascar la API.
- El tipo declarado solo selecciona un parser; no otorga nada. Un archivo cuyos bytes no coinciden con su parser falla como no soportado: un archivo mal etiquetado se rechaza, nunca se renombra hasta que funcione.
- Una URL que agregas se obtiene por una ruta endurecida: solo
http/https, con los rangos privados, de loopback y link-local rechazados, la dirección resuelta fijada para que no pueda cambiar entre la verificación y la conexión, cada redirección revalidada desde cero, y topes duros de tiempo, tamaño y saltos. - Los rechazos inmediatos se mantienen vagos a propósito. Agregar una URL bloqueada responde un genérico “no permitido” sin razón, para que el endpoint de agregar no pueda usarse como escáner de red. Una URL bloqueada después, durante el rastreo, registra una razón legible por máquina en la fuente (por ejemplo
fetch_blocked_address:link_local) para que su dueño pueda actuar.
La frontera del widget
El widget embebible es un iframe en un origen dedicado, así que no comparte cookies, ni almacenamiento, ni DOM con la página que lo aloja. Su documento incluyedefault-src 'none' más solo lo que necesita, y su frame-ancestors lista solo los orígenes que el dueño del agente registró en su site key. No se aceptan comodines, y una lista vacía se convierte en 'none', nunca en *, así que alguien que tenga una site key pública sigue sin poder enmarcar ese agente en un sitio propio. Los mensajes entre la página y el frame usan un origen de destino explícito en ambas direcciones y un sobre versionado, y nada sensible viaja por él.
Abuso y gasto
El endpoint que responde a los visitantes es anónimo por naturaleza, lo que lo vuelve un objetivo tanto para el scraping como para inflarle la cuenta a alguien. Se defiende en capas: una lista de orígenes permitidos, límites de tasa por IP, una verificación de bots invisible, un periodo de enfriamiento por sesión, topes al largo de la entrada, de la salida y del historial, una caché para preguntas iniciales idénticas y un fusible diario global. Debajo de todo eso, cada espacio de trabajo tiene su propio límite por minuto, su propio techo de gasto diario y un saldo de créditos de mensajes que cada turno gasta de forma atómica y que corta en cero. Un turno rechazado falla cerrado.Datos, borrado y modelos
El borrado es real. Borrar una fuente de conocimiento elimina la fuente, sus pasajes, los vectores dentro de esos pasajes y el archivo almacenado, en una sola cascada. Como cada pasaje contiene su propio vector, no hay ventana en la que la base de datos haya olvidado algo que un índice de búsqueda todavía recuerda. Borrar un agente elimina todo lo que le pertenecía (conocimiento, imágenes, archivos almacenados y sus site keys de widget), mientras que el material adjunto al espacio de trabajo completo sobrevive; borrar una página elimina la página y su dirección pública, nunca el material de su agente. El servidor es dueño de la conversación. La transcripción de un visitante vive en una colecciónconversations para que el navegador nunca pueda falsificar lo que el agente lee: el cliente tiene solo un id de conversación opaco, y el historial que ve el modelo se deriva del lado del servidor. La transcripción expira con un reloj deslizante de 24 horas (un chat activo nunca expira; una pestaña abandonada no vive en la base de datos), guarda como máximo 40 mensajes y nunca contiene la dirección IP del visitante, solo dimensiones gruesas como el país y la clase de dispositivo. Está acotada al espacio de trabajo y a la sesión anónima del visitante. Los turnos pueden rastrearse en las herramientas de observabilidad de wondeya para depuración y calidad de las respuestas.
Tu material nunca entra en los pesos de un modelo. El conocimiento llega a una respuesta solo mediante recuperación al momento de la pregunta. No hay fine-tuning sobre datos de clientes. Es una decisión de borrado tanto como de aislamiento, porque lo que nunca entró en los pesos nunca tiene que quitarse de ellos.