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'],});Capa 1 — Flags de permisos
Sección titulada «Capa 1 — Flags de permisos»Siete flags, que se comprueban con wu.ai.permissions.check(flag):
| Flag | Por defecto | ¿Se aplica? | Efecto |
|---|---|---|---|
readStore |
true |
sí | api.getState está presente en el sandbox de acciones |
writeStore |
false |
sí | api.setState está presente — y la acción debe declarar 'writeStore' |
emitEvents |
true |
sí | 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.
Lista de dominios permitidos
Sección titulada «Lista de dominios permitidos»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.
Capa 2 — Límite de peticiones
Sección titulada «Capa 2 — Límite de peticiones»| 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.
Capa 3 — Circuit breaker
Sección titulada «Capa 3 — Circuit breaker»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.
Capa 4 — Protección de bucles
Sección titulada «Capa 4 — Protección de bucles»Dos contadores, ambos acotados por maxDepth (3 por defecto):
- 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. - Cadena causal: cuántas veces se ha entrado en un
traceIddado. 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.
API keys
Sección titulada «API keys»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.
Saneado de prompts
Sección titulada «Saneado de prompts»Los datos que se inyectan en los prompts pasan por sanitizeForPrompt, que:
- Clona en profundidad los objetos, reemplazando por
[REDACTED]los valores cuya clave contengapassword,token,apiKey,secret,credential,authorization,cookieosession(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.
Suplantación de eventos
Sección titulada «Suplantación de eventos»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.
Lo que nada de esto cubre
Sección titulada «Lo que nada de esto cubre»- 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:executeda 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.
