Ir al contenido

Capas de seguridad

wu.ai tiene cuatro capas de defensa. Todas son reales, todas configurables, y ninguna hace que sea seguro darle a un LLM tu base de datos de producción. Esta página es la versión honesta.

wu.ai.init({
permissions: { writeStore: true },
rateLimit: { requestsPerMinute: 40, requestsPerMinutePerNs: 20, maxConcurrent: 5 },
circuitBreaker: { maxFailures: 5, cooldownMs: 60_000 },
loopProtection: { maxDepth: 2 },
allowedDomains: ['api.example.com', '*.internal.example.com'],
});

Siete flags, que se comprueban con wu.ai.permissions.check(flag):

Flag Por defecto ¿Se aplica? Efecto
readStore true api.getState está presente en el sandbox de acciones
writeStore false api.setState está presente — y la acción debe declarar 'writeStore'
emitEvents true api.emit está presente
executeActions true no no se lee en ningún punto del código
readDOM false no no se lee en ningún punto del código
modifyDOM false no no se lee en ningún punto del código
allowDirectKey false parcial se fuerza a false en producción; el bloqueo de la clave vive en el proveedor

Fíjate también en la dirección de la comprobación: permissions en una acción significa “estos flags deben ser true para que la acción se ejecute siquiera”. Cualquier flag listado que sea false rechaza la llamada con Permission denied: <flag> y emite ai:action:denied.

wu.ai.permissions.setAllowedDomains(['api.example.com', '*.internal.example.com']);
wu.ai.permissions.getPermissions();

isDomainAllowed(url) admite hosts exactos y comodines *.sufijo. Una lista vacía significa “permitir todo”. Es un helper que tus propios handlers de acciones pueden llamar antes de hacer un fetch: wu no intercepta fetch por ti.

Límite Por defecto Clave de configuración
Peticiones por minuto, global 20 requestsPerMinute
Peticiones por minuto, por namespace 10 requestsPerMinutePerNs
Peticiones concurrentes en vuelo 3 maxConcurrent

Una ventana deslizante de 60 segundos; las marcas de tiempo más antiguas se podan en cada comprobación. Superar un límite no es una excepción: send() se resuelve con content: '[blocked] Global rate limit (20/min) exceeded', stream() emite un chunk error, y un paso de agente vuelve con type: 'blocked'.

El valor por defecto de 20/min es bajo a propósito. Un único trigger desbocado lo alcanza en un minuto en vez de costarte dinero durante una hora.

CLOSED ──3 fallos consecutivos──► OPEN ──30s de enfriamiento──► HALF-OPEN ──éxito──► CLOSED
└──fallo──► OPEN
Ajuste Por defecto
maxFailures 3
cooldownMs 30000
rapidFireThreshold 5 peticiones
rapidFireWindowMs 2000 ms

Mientras está OPEN, las llamadas se rechazan con Circuit breaker OPEN (Ns remaining). Tras el enfriamiento se deja pasar una petición en HALF-OPEN: si tiene éxito, el breaker se cierra y el contador se reinicia; otro fallo lo vuelve a abrir durante 30 segundos más.

Hay una segunda condición de disparo: 5 peticiones en 2 segundos abren el breaker por rapid fire. Ojo: solo cuenta las peticiones registradas durante fallos, así que es un detector de ráfagas de errores más que un límite general de rendimiento.

Solo cuentan como fallos los errores de proveedor o de transporte. Una llamada bloqueada, un abort o un modelo que devuelve sinsentidos no mueven el breaker.

Dos contadores, ambos acotados por maxDepth (3 por defecto):

  1. Profundidad: cuán honda es la recursión causal actual. Una llamada a tool dentro de send() es profundidad 1, un trigger que reacciona a un evento emitido desde esa tool es profundidad 2, y así sucesivamente.
  2. Cadena causal: cuántas veces se ha entrado en un traceId dado. Aunque la profundidad se mantenga baja, una cadena que sigue reentrando se corta.

La cadena la transporta api.emit dentro de los handlers de acciones, que adjunta meta: { traceId, depth } al evento. Un trigger que dispara con ese evento reutiliza el trace e incrementa la profundidad.

Los pasos del agente corren deliberadamente a profundidad 0 con un trace nuevo: maxSteps es el presupuesto del agente, y mezclar ambos haría que una ejecución legítima de 10 pasos pareciera recursión.

wu.ai.provider('openai', { endpoint: '', apiKey: 'sk-…' });

Si la página parece de producción, la clave se elimina y se registra un aviso: la petición sale sin autenticar y falla. La heurística (NODE_ENV, las formas de localhost, .local/.localhost/.test, los rangos RFC1918) está documentada en Proveedores, junto con la desactivación unsafeAllowDirectKey: true para hosts de staging.

Por separado, permissions.configure() fuerza allowDirectKey a false en producción.

Ninguna de las dos cosas hace segura una clave alojada en el navegador. La única arquitectura correcta es un proxy en servidor que guarde la clave, autentique a tu usuario y reenvíe la petición.

Los datos que se inyectan en los prompts pasan por sanitizeForPrompt, que:

  • Clona en profundidad los objetos, reemplazando por [REDACTED] los valores cuya clave contenga password, token, apiKey, secret, credential, authorization, cookie o session (hasta profundidad 10).
  • Trunca los valores a 2000 caracteres.
  • Envuelve el contenido no confiable en <user_data>…</user_data> para poder decirle al modelo que lo trate como datos.

wu.ai y el puente MCP emiten bajo los nombres de app reservados wu-ai y wu-mcp-bridge, usando un token secreto que acuña el bus de eventos al construirse. En modo estricto, el bus rechaza las emisiones que reclamen esos nombres sin un token que coincida, así que una micro-app no puede falsificar eventos de IA.

  • Shadow DOM no es una frontera de seguridad. El aislamiento va de CSS y de colisiones accidentales, no de contener código hostil. Consulta Modelo de amenazas.
  • Las acciones son tan seguras como sus handlers. Wu valida los parámetros contra tu esquema y te entrega una API pequeña; todo lo que el handler capture por clausura queda fuera del alcance de wu.
  • El registro de auditoría es solo de memoria, con un tope de 100 entradas. Envía ai:action:executed a algún sitio si necesitas un histórico.
  • Las confirmaciones caducan a los 30 segundos y por defecto deniegan: una interfaz de aprobación que llega tarde equivale a un rechazo.
  • Nada de esto limita por usuario ni por coste. Son contadores por sesión de página en la memoria del navegador. Un presupuesto de verdad va en tu proxy.