Saltar al contenido
AI Workforce OS
ES EN
Crear cuenta

Modelo de seguridad

Un modelo de lenguaje puede ser engañado: procesa texto no confiable y tiene capacidad de actuar. Por eso AI Workforce OS acota el daño con controles deterministas en el backend (Go), no con instrucciones en el prompt. Esta página es para quien evalúa o opera la plataforma: describe lo que está implementado y, al final, lo que no.

11 min de lectura

En esta página
  1. Principios de diseño
  2. Bóveda de credenciales
  3. Aprobaciones humanas
  4. Mínimo privilegio en conexiones
  5. Topes de gasto, horario y anomalías
  6. Kill switch y modo solo lectura
  7. Registro de auditoría
  8. Aislamiento por organización e identidad
  9. Contenido no confiable y sandbox de código
  10. Lo que no está cubierto
Volver arriba

Principios de diseño#

Los agentes solo producen solicitudes de herramienta; quien decide si se ejecutan es el backend. El Tool Gateway revisa cada solicitud en este orden:

  • Controles: kill switch, pausa del agente, modo solo lectura y herramientas o acciones apagadas.
  • La concesión (grant) de la conexión para ese agente.
  • Validación: estado de la conexión, capacidad concedida, riesgo máximo del grant y límites de uso.
  • Búsqueda de secretos en los argumentos que van a salir.
  • Motivos de aprobación: el motor de políticas reúne todos los veredictos y se queda con el más estricto (denegar gana a aprobar, y aprobar gana a permitir).

Nota

El runtime de agentes nunca recibe credenciales de conexiones. Si una organización guarda su propia clave de modelo, esa clave sí viaja al runtime en cada llamada, porque es quien llama al proveedor.

Consejo

No se promete que el modelo nunca sea influido por un texto malicioso. Se promete que el radio de daño queda limitado a lo que el backend autoriza.

Bóveda de credenciales#

Las credenciales de las conexiones (tokens OAuth, claves de API) y las claves de modelo propias de cada organización se guardan cifradas con AES-256-GCM en un esquema de sobre. Una clave de cifrado de claves (KEK) fuera de la base de datos envuelve una clave de datos (DEK) versionada por organización, y la DEK cifra cada secreto con un nonce aleatorio. Los datos autenticados adicionales incluyen organización, registro, versión y tipo, así que un texto cifrado copiado a otra fila u organización no se descifra.

Sin KEK (CONNECTIONS_KEK, 32 bytes en base64) la bóveda falla cerrada: no guarda ni lee credenciales reales. Puede cargar varias KEK a la vez para rotarlas (CONNECTIONS_KEK_PREVIOUS), y cada credencial tiene versiones activas y retiradas.

  • La API solo devuelve metadatos y, para secretos de 20 bytes o más, los 4 últimos caracteres como pista.
  • Revocar una conexión suspende su uso, llama al endpoint de revocación del proveedor y destruye el texto cifrado. Si el proveedor no responde, el texto cifrado se conserva hasta que un reintento lo consiga.
  • Los valores exactos de los secretos en uso se registran en un redactor que los enmascara en las salidas de herramientas. La auditoría enmascara además todo lo que tiene forma de credencial.
Texto
KEK (outside the DB, CONNECTIONS_KEK)
  └─ wraps → per-organization DEK (versioned)
       └─ AES-256-GCM(secret, random nonce, AAD = org|record|version|kind)

Atención

Guarda la KEK aparte de los backups de la base de datos. Si la pierdes, las credenciales cifradas son irrecuperables; si se filtra junto con un backup, quedan expuestas.

Aprobaciones humanas#

Un agente puede pedir una acción, nunca aprobarla. Deciden las personas con rol admin u owner, y el rol sale del token de sesión, no del cuerpo de la petición. Cada aprobación queda ligada por hash a los argumentos exactos aprobados, se consume una sola vez y se revalida antes de ejecutar, porque las reglas pudieron cambiar mientras esperaba.

Una aprobación de una solicitud interactiva caduca a los 30 minutos (APPROVAL_TIMEOUT), y la caducidad cuenta como rechazo. Las de proyectos no caducan por defecto: el sistema recuerda cada hora que siguen pendientes.

Hay un suelo que ninguna regla de la organización debilita: riesgo alto declarado, acciones de la lista de aprobación obligatoria (APPROVAL_ACTIONS) y agentes en los niveles suggest o approve_each. Encima, la organización puede añadir reglas: umbrales de monto, acciones prohibidas, destinatarios nuevos, categorías sensibles, rol exigido según monto o acción y doble aprobación.

La doble aprobación exige dos personas distintas con el rol requerido, y quien hizo la solicitud no puede aprobar. Un solo rechazo de una persona autorizada es definitivo. Si la acción se ejecuta tras reiniciar el servidor, la aprobación se reclama de forma atómica para que se ejecute como máximo una vez.

Nivel de autonomíaComportamiento
suggestToda acción con efecto externo espera una aprobación; aprobada, se ejecuta. Hoy se comporta igual que approve_each.
approve_eachToda acción con efecto externo espera una aprobación.
rulesSe ejecuta sin aprobación salvo que lo exija el suelo o una regla de la organización. Hoy se comporta igual que autonomous.
autonomousSe ejecuta sin aprobación salvo el suelo, las reglas de la organización, las capacidades marcadas como de aprobación obligatoria y las tareas marcadas por sospecha de inyección.

Nota

Cada plantilla de rol o paquete de onboarding fija el nivel inicial de sus agentes.

Mínimo privilegio en conexiones#

Una acción con una conexión externa pasa tres filtros en serie: la capacidad que declara el proveedor, la concesión que la organización da a un agente concreto, y la política y aprobación de la propia acción. Un grant solo puede estrechar lo que permite la conexión. El patrón recomendado es separar una conexión de solo lectura de otra de escritura.

Los límites se calculan sobre la tabla de uso en Postgres, así que sobreviven a reinicios. Un agente no puede cambiar su propia autonomía, permisos ni reglas, y cualquier regla de denegación gana a una de permiso.

LímiteConexiónGrant
Llamadas por minuto, hora y díaSíSí
Escrituras por díaSíSí
Bytes por díaSíNo
Presupuesto mensual (últimos 30 días)SíNo

Topes de gasto, horario y anomalías#

Antes de cada llamada al runtime se reserva el costo máximo estimado contra la solicitud, el agente, el departamento y la organización. Después se concilia con el mayor valor entre el costo que informa el runtime y el que recalcula el backend. Al llegar al tope de la solicitud, del agente o del departamento, el trabajo se pausa y una persona decide si sube el tope; el tope de la organización hace fallar la llamada.

Hay además límites estructurales contra bucles: profundidad de delegación (5), reintentos por tarea, tiempo por tarea, tamaño de planes y proyectos, y una cola por organización (8 llamadas simultáneas por defecto).

El horario de operación es una agenda semanal con zona horaria que solo impide empezar trabajo nuevo fuera de horas; no bloquea aprobaciones ni lo ya admitido. La detección de anomalías aplica cuatro reglas explicables, cada una con una pausa de 15 minutos entre avisos, y registra cada hallazgo en la auditoría. Por sí sola nunca bloquea: el congelamiento automático es opcional y viene apagado.

ReglaSe dispara cuando
spend_spikeEl gasto de la hora actual es al menos 4 veces el promedio horario de las 24 h previas y no menos de 2 USD (necesita 3 h de historial).
tool_request_burstUn agente pide 40 o más herramientas en 60 segundos.
rejected_approvalsSe rechazan 5 o más aprobaciones en 1 hora.
new_recipient_domainsAparecen 3 o más dominios de destinatario nunca vistos en 1 hora (cuenta a partir de 5 dominios conocidos).

Kill switch y modo solo lectura#

El kill switch tiene dos niveles. Cualquier admin u owner puede activarlo; levantarlo exige el rol owner y un motivo escrito. También puede imponerse con la variable de entorno KILL_SWITCH (freeze o lockdown), que ninguna llamada de API puede levantar: hay que quitarla y recargar el proceso.

Hay controles más finos: pausar un agente (deniega sus nuevas llamadas de herramienta y trabajo nuevo), el modo solo lectura de la organización o de un agente (sigue leyendo y proponiendo, pero se deniegan los efectos) y apagar una herramienta o una acción concreta, por ejemplo email o email.send. Las acciones retenidas vuelven a la cola de aprobación y hay que aprobarlas de nuevo: nada se ejecuta por inercia al levantar un control.

NivelEfectoQuién lo levanta
noneOperación normal.No aplica
freezeNo empieza trabajo nuevo, el Tool Gateway deniega todo uso de conexiones (lecturas incluidas) y las acciones retenidas vuelven a la cola de aprobación.owner, con motivo escrito
lockdownTodo lo de freeze y, además, suspende todas las conexiones.owner, con motivo escrito; las conexiones se reanudan después

Registro de auditoría#

La auditoría es una cadena de hash por organización: cada entrada incluye el hash de la anterior (SHA-256 sobre una representación canónica con secuencia, organización, fecha, actor, acción, entidad y detalles). Modificar, borrar, reordenar o insertar una entrada rompe la cadena desde ese punto, y un truncado del final se detecta contra la cabeza de la cadena.

Antes de encadenarse, cada entrada pasa por un depurador que guarda solo metadatos: sustituye los campos de contenido (cuerpos, argumentos, asuntos, resúmenes) y las claves con aspecto de credencial por [redacted], enmascara secretos incrustados y recorta los textos a 300 caracteres. Se auditan, entre otros, la autenticación, las decisiones de política, las aprobaciones, las ejecuciones de herramientas y los cambios de controles y presupuestos.

HTTP
GET /api/v1/audit                      # query
GET /api/v1/audit/export?format=jsonl  # or format=csv
GET /api/v1/audit/verify               # recomputes the hash chain

Consejo

Guarda fuera del servidor la secuencia y el hash de la cabeza de vez en cuando. GET /api/v1/audit/verify acepta anchor_seq y anchor_hash para comprobar la cadena contra ese ancla.

Aislamiento por organización e identidad#

Las tablas con datos de organización usan seguridad a nivel de fila (RLS) en Postgres. Cada transacción fija la organización con set_config en el ámbito de la transacción, las políticas filtran por ella y la aplicación se conecta con un rol sin BYPASSRLS. Una prueba de integración cubre el aislamiento entre organizaciones.

  • Los tokens de acceso duran 15 minutos y los de refresco 30 días.
  • Tras 5 intentos fallidos la cuenta se bloquea 15 minutos. El inicio de sesión admite además 10 intentos cada 15 minutos por correo y 20 por IP (en Redis si está disponible; si no, en memoria).
  • Para cuentas inexistentes se calcula igualmente un hash de contraseña, de modo que el tiempo de respuesta no revela si el usuario existe.
  • Hay inicio de sesión único con OIDC (con PKCE), dominios verificados, una política de acceso que puede exigir MFA o SSO, MFA con TOTP y aprovisionamiento SCIM 2.0.

Contenido no confiable y sandbox de código#

Todo lo que viene de fuera (correos, respuestas de APIs, archivos) pasa por una tubería determinista antes de llegar al modelo: se normaliza el texto (NFKC, sin caracteres invisibles), se enmascaran credenciales, códigos de un solo uso y números de tarjeta, se neutralizan imágenes y enlaces de markdown, se recorta (4000 caracteres por defecto), se puntúa con heurísticas de inyección y se envuelve como datos en un bloque delimitado. Un puntaje alto marca la tarea y obliga a aprobar sus acciones. El puntaje es una señal, no una barrera: la barrera son los permisos, las aprobaciones y los límites.

El código que un agente quiere ejecutar se muestra completo a una persona, y el backend solo lo envía al ejecutor tras su aprobación. Cambiar el código o una entrada exige una aprobación nueva, y el kill switch aborta una ejecución en curso.

  • Contenedor sin red (--network none) y con sistema de archivos raíz de solo lectura.
  • Usuario sin privilegios, todas las capacidades eliminadas y no-new-privileges.
  • Límites de CPU, memoria y procesos, tiempo máximo y topes de salida, con una lista de extensiones permitidas para los archivos generados.
  • Ninguna variable de entorno del ejecutor y ningún acceso directo al Drive ni al backend.
Texto
<untrusted_data id="msg-18c2f" source="tool:email.read" from="client@example.com" trust="external_untrusted">
subject: ...
body: ...sanitized content...
</untrusted_data>

Atención

El modo escritorio es opcional y viene apagado. Si una organización lo activa (exige reconocer el riesgo de forma explícita), el código limitado a los archivos del propio agente se ejecuta sin aprobación, con topes más pequeños.

Lo que no está cubierto#

No hay certificaciones (ni SOC 2 ni ISO) y el proyecto no está preparado para entornos empresariales regulados. Esta es la lista de límites conocidos:

  • El inicio de sesión único es solo OIDC: SAML no está implementado.
  • La exportación de auditoría es un volcado en JSONL o CSV sin firma, y nada ancla la cabeza de la cadena fuera del servidor de forma automática.
  • La recuperación tras un reinicio supone una sola instancia: con varias réplicas, la cola por organización, las reservas de presupuesto y algunos contadores son por proceso o de último escritor.
  • Una llamada ya enviada al runtime termina aunque se active el kill switch.
  • No existe un permiso de aprobación por nivel de riesgo: decidir equivale a ser admin u owner. La prohibición de autoaprobarse viene apagada (salvo con doble aprobación) por compatibilidad con equipos de una persona.
  • Los límites de tasa HTTP de autenticación dejan pasar las peticiones si su almacén falla.
  • El Caddyfile de producción no define Content-Security-Policy.
  • No se pueden deshacer acciones externas. Sí hay una ventana de 60 a 300 segundos para cancelar un correo y restauración de versiones de archivos del Drive.
  • No hay un tope de gasto unificado entre modelo y conexiones, ni topes por herramienta.

Consejo

Si evalúas el proyecto para producción, trata esta página como el punto de partida de tu propia revisión, no como una garantía.

Atención

El servicio sandbox-runner (perfil sandbox del compose de desarrollo) monta el socket de Docker, lo que equivale a acceso de administrador al host. Para algo más que un único host de confianza, usa un demonio dedicado, sin root o remoto, y un runtime como gVisor (SANDBOX_RUNTIME=runsc).