Las políticas de OpenShell en Bromure, y el razonamiento detrás de cada conexión
OpenShell de NVIDIA describe en un único archivo de política lo que un agente de programación puede tocar. Bromure Agentic Coding 5.4 aplica esas políticas en la VM del agente, con Landlock y seccomp dentro y las reglas de red en tu Mac, y vigila el kernel del invitado con un módulo centinela. Luego vincula cada conexión de red con los procesos que la abrieron, la llamada a herramienta que los lanzó y el razonamiento que dio el agente para esa llamada.
Un agente de programación necesita acceso real para ser útil, y una política dice cuánto. Funciona mejor como un único documento que puedas leer, comprobar y aplicar, en lugar de ajustes repartidos entre un firewall, opciones de montaje y la propia configuración del agente.
OpenShell de NVIDIA
OpenShell es el runtime de código abierto de NVIDIA para agentes, publicado bajo la licencia Apache 2.0. Construimos sobre su formato de política, en el que un solo documento YAML cubre cuatro áreas:
filesystem_policyylandlock: qué rutas puede leer y escribir el agente, aplicadas por el módulo de seguridad Landlock del kernel de Linux.process: con qué usuario y grupo se ejecuta el agente.network_policies: a qué destinos puede llegar cada programa y qué solicitudes puede enviar allí, hasta el método y la ruta HTTP. Todo lo que no está permitido se deniega: “OpenShell deniega toda conexión saliente de un sandbox salvo que una regla de network_policies la permita.”
OpenShell incluye además un prover que usa un solver SMT para comprobar si una política permite algo más allá de otra. Una organización puede fijar un límite y obtener un sí o un no para cada cambio, en lugar de leer diffs.
Es un diseño cuidadoso, hecho por gente que claramente ha estudiado cómo fallan los agentes, y es abierto, así que partimos de él.
OpenShell en Bromure
En Bromure Agentic Coding 5.4, un espacio de trabajo puede usar una política de
OpenShell en lugar de las reglas de firewall propias de Bromure. Tomamos el
formato de la política y su semántica, no el código: el motor es nuestro, en el
Mac, junto con nuestros propios componentes en el invitado Linux. Para
asegurarnos de que los dos coinciden, ejecutamos nuestro motor y el motor de
referencia de OpenShell sobre 125.666 casos generados que abarcan reglas de red,
reglas HTTP, MCP, GraphQL, WebSocket y comprobaciones de dirección de destino. No
discreparon en ninguno. Esas pruebas sacaron a la luz trucos de ruta como ..%3b
y barras doblemente codificadas (%252F), que ahora tratamos igual que
OpenShell.
Bromure se diferencia de OpenShell en dónde se aplica la política. OpenShell arranca los sandboxes a través de un gateway y un runtime de contenedores. Bromure ejecuta cada espacio de trabajo en su propia VM Linux desechable en tu Mac, así que la política se reparte entre los dos lados:
- En la VM: Landlock para el sistema de archivos, seccomp,
no_new_privsy el cambio al usuario de la política. La siguiente sección explica cómo. - En el Mac: las reglas de red. Cada conexión sale de la VM a través del
switch virtual de Bromure, que comprueba el destino y el puerto, y de su proxy
TLS, que comprueba los métodos y rutas HTTP y los mensajes MCP, GraphQL y
WebSocket. El Mac resuelve y fija las direcciones, así que el DNS no sirve para
colarse por detrás de una regla
allowed_ips. Root dentro de la VM no puede editar una regla que vive fuera de ella.
La política también puede nombrar el programa autorizado a abrir una conexión.
Con el modo estricto activado, Bromure comprueba qué binario abrió la conexión,
por su hash: curl puede llegar a api.github.com, y un script de Python no.
Combinado con una regla HTTP como solo GET en api.github.com/repos/**, un
DELETE se rechaza en el proxy. Landlock no puede hacer nada de esto: solo
confina archivos.
Cuando una organización fija un límite desde bromure.io, Bromure comprueba cada
política de espacio de trabajo contra él con el openshell-prover de NVIDIA si
está instalado, y con una comprobación integrada más estricta si no lo está. Una
política que se sale del límite no se puede guardar.
Hicimos un cambio deliberado: cada sección se aplica solo si está presente.
Una política sin filesystem_policy deja Landlock desactivado, y una sin
process no cambia de usuario. OpenShell rellena valores por defecto porque sus
drivers siempre envían un documento completo. En Bromure, adoptar OpenShell nunca
rompe un espacio de trabajo sin avisar. Eso da tres niveles:
| La política tiene | Se aplica en la VM | Lo que garantiza |
|---|---|---|
| Solo reglas de red | Nada; el agente conserva sudo | La política de red, y la observación del centinela del kernel |
filesystem_policy | Landlock, aplicado cuando aún es root | Un confinamiento del sistema de archivos que se mantiene incluso frente a root |
Una sección process, o el modo estricto | Landlock, seccomp, no_new_privs, un usuario no root; sudo y docker revocados | Todo lo anterior, ningún camino hacia root y ninguna forma de detener los procesos propios de Bromure |
Algunos campos se aceptan pero no se aplican. Los principales son los campos de reescritura de credenciales y de firma: Bromure ya guarda las credenciales reales en el Mac y las sustituye a la salida, así que el agente solo tiene sustitutos.
Dentro de la VM: Landlock, seccomp y una reducción de privilegios
La mitad del invitado es una reimplementación del código de aplicación del crate
sandbox-linux de OpenShell. Está escrita en Python con ctypes, porque tiene
que ejecutarse dentro de la VM durante el arranque, donde no hay toolchain de
Rust. Hace las mismas llamadas al sistema que los crates de Rust landlock y
seccompiler, y un banco de pruebas diferencial compila los crates reales de
OpenShell y compara ambos ruta por ruta y llamada al sistema por llamada al
sistema.
Un supervisor root, bromure-sandboxd, arranca cada sesión del agente. Mueve la
sesión a su propio cgroup, el cgroup del sandbox, y luego la confina en tres
pasos.
Landlock. Landlock permite que un proceso sin privilegios se restrinja a sí mismo, y a todos los procesos que lance, a una lista de rutas. El supervisor:
- llama a
landlock_create_rulesetcon los derechos que gobierna el conjunto de reglas. En el kernel 6.8 del invitado (ABI 4 de Landlock) eso es ejecutar, leer y escribir archivos, listar directorios, crear y eliminar cada tipo de archivo, renombrar entre directorios (REFER) y truncar; - abre cada ruta de
read_onlyyread_writeconO_PATHy la añade conlandlock_add_rule, como una reglaPATH_BENEATHque lleva los derechos de lectura o todos ellos; - llama a
landlock_restrict_self.
A partir de ahí, cualquier acceso que gobierne el conjunto de reglas y que
ninguna regla conceda falla con EACCES, lo pida quien lo pida. Landlock
comprueba la tarea, no el usuario, así que sudo no lo amplía. Un proceso puede
apilar más conjuntos de reglas pero nunca quitar uno, y por eso un cambio en el
sistema de archivos requiere reiniciar la VM, mientras que las reglas de red se
aplican en directo. landlock.compatibility decide qué pasa en un kernel sin esa
ABI: best_effort sigue adelante con lo que ofrezca el kernel, y
hard_requirement se niega a arrancar la sesión.
seccomp. En modo estricto, o cuando la política tiene una sección process,
el supervisor activa no_new_privs e instala los tres filtros seccomp de
OpenShell:
- El filtro principal permite por defecto y bloquea lo que puede servir para
saltarse el sandbox. Eso incluye
ptrace,bpf,io_uring_setup,userfaultfd,perf_event_open,process_vm_readvyprocess_vm_writev, toda la familia de montaje, ysetns. Bloqueamemfd_createyexecveatconAT_EMPTY_PATH, porque ejecutar código desde un archivo anónimo se salta Landlock. BloqueaunshareycloneconCLONE_NEWUSER, porque un nuevo namespace de usuario reparte capacidades. Y bloquea los sockets de las familias packet, Bluetooth y vsock, y los netlink que no sean de enrutamiento. - Un filtro de compatibilidad responde a
clone3conENOSYS. Un filtro seccomp puede leer los valores de los registros pero no seguir punteros, yclone3pasa sus flags a través de un puntero.ENOSYShace que glibc recurra aclone, cuyos flags sí puede comprobar el filtro principal. - Un filtro de endurecimiento impide que la sesión llegue a su supervisor, que se
ejecuta como el mismo usuario: nada de
ptrace,process_vm_*,pidfd_getfdnikcmp, y ninguna señal al proceso ni al grupo del supervisor.
Nos apartamos de OpenShell en dos puntos, y solo en lo que se informa o en cómo
se dirigen las señales. Nuestros filtros llevan SECCOMP_FILTER_FLAG_LOG, así que
una llamada al sistema rechazada llega al hook de auditoría del kernel, donde la
ve el centinela. Y la regla de señales está acotada para que el control de
trabajos de la shell (kill %1) siga funcionando en una terminal interactiva.
La reducción de privilegios. run_as_user cambia la sesión al usuario de la
política, sin ninguno de los grupos del usuario por defecto, así que sin sudo
ni docker. El directorio personal del usuario se monta en la sesión mediante
un montaje idmapped, de modo que los archivos que crea la sesión siguen
perteneciendo en disco al usuario por defecto, y la configuración del agente
sigue aplicándose.
Un centinela en el kernel del invitado
Para registrar lo que un agente hizo de verdad, Bromure carga su propio módulo del kernel en la VM: el centinela. Su diseño parte de que el agente puede convertirse en root dentro de la VM, así que nada que root pudiera detener cuenta como prueba: la captura, el búfer y el transporte se ejecutan todos en el kernel.
Captura. El centinela registra kprobes desde el espacio del kernel:
- arranques de programas, en
security_bprm_committed_creds, con la línea de comandos y la hora de inicio del proceso; - cambios de credenciales: las familias
setuidysetgid,capsetycommit_creds; ptrace, cargas y descargas de módulos,bpf,kexec_load,mount,unshareysetns;- flujos de red nuevos: TCP cuando sale el primer SYN (
tcp_connect), UDP en el primer envío a un destino, e ICMP y sockets raw, cada uno con la cadena de hasta ocho procesos que hay detrás; - llamadas al sistema rechazadas, en
audit_seccomp; - operaciones de archivo rechazadas, mediante kretprobes en los hooks de
seguridad de archivos del kernel:
security_file_open, y los hookssecurity_path_*para mkdir, rmdir, unlink, symlink, link, rename y truncate. Cuando un hook devuelveEACCESoEPERMpara un proceso del cgroup del sandbox, eso es una denegación. Ejecutar un archivo que Landlock no permite aparece como una apertura rechazada conFMODE_EXEC.
El supervisor le da al centinela el ID del cgroup del sandbox una sola vez, mediante un parámetro del módulo que se puede fijar pero nunca cambiar. Root no puede apuntarlo a otro sitio para que la actividad del agente se etiquete como propia de Bromure.
Transporte. Un hilo del kernel abre un socket AF_VSOCK hacia el Mac con
sock_create_kern. El socket no tiene descriptor de archivo, así que no aparece
bajo ningún proceso en /proc y nada en el espacio de usuario puede cerrarlo.
Los eventos pasan por un anillo de 256 posiciones, y cada tipo está limitado a 64
por segundo, con ráfagas de hasta 256. Cada trama es una longitud de 4 bytes
seguida de JSON.
Seguir cargado. El módulo no tiene función de salida, así que el kernel se
niega a descargarlo, y además mantiene una referencia a sí mismo. Justo después
de cargarlo, el cargador eleva el lockdown del kernel a integrity, o a
confidentiality cuando el espacio de trabajo exige el centinela. A partir de
ahí, root no puede cargar un módulo sin firmar, escribir en la memoria del kernel
a través de /dev/mem ni hacer kexec a un kernel sin el centinela.
Demostrar que está vivo. Cada segundo, el centinela envía un latido con un número de secuencia, los recuentos de eventos descartados y limitados, y cuántas sondas están armadas. Ese recuento, por sí solo, se puede engañar: desactivar todas las kprobes a la vez mediante debugfs deja intactos los flags propios de cada sonda, así que el recuento sigue pareciendo sano aunque nada se dispare. Para detectarlo, el centinela mantiene una kprobe en una función vacía propia y la llama una vez por latido. Si el contador de impactos no se mueve, las sondas no se están disparando, sea cual sea el motivo, y el latido lo dice.
Demostrar que es el centinela. Cada carga genera un secreto de 32 bytes. El
primer saludo lo lleva, y el Mac lo fija para ese arranque. Una reconexión nunca
repite el secreto. En su lugar envía
sha256(secret, boot_id, connection number), así que un proceso que leyera el
secreto de la memoria después del primer saludo sigue sin poder suplantar al
módulo. Un segundo centinela, un secreto incorrecto, un número de conexión
reutilizado, un hueco en la secuencia o 15 segundos de silencio cuentan como
manipulación. Lo único que le queda a root es colgar o apagar la VM, y el Mac lo
ve como silencio.
Distribución. Los módulos no se compilan en la VM. La CI compila uno para cada una de las 20 versiones del kernel de Ubuntu 24.04, dos veces, y las dos compilaciones tienen que coincidir byte a byte. Cada módulo y el catálogo que los lista se firman con una clave ed25519, y la app rechaza un módulo cuya firma o hash no cuadre.
Una lección de construirlo: durante un tiempo, la sonda de denegaciones de
archivo comparaba un valor de retorno sin signo con -EACCES y no se disparó ni
una vez. Las pruebas solo comprobaban que la sonda estaba registrada, y siempre
lo estaba. Ahora comprueban que una denegación produce un evento.
Juzgar los eventos. En el Mac, los eventos se juzgan por lo que significan, no por qué llamada al sistema se disparó. Cada uno suma un peso a una puntuación, y 20 en 60 segundos disparan la alarma:
| Evento | Peso |
|---|---|
| Manipulación del centinela, o root obtenido desde dentro de un sandbox estricto | 20 |
Un módulo sin firmar, una carga de BPF, un kexec, un intento de rebajar el lockdown; un programa rechazado 20 veces en un minuto | 10 |
Un ptrace, o una llamada al sistema rechazada por seccomp | 3 |
| Un montaje o un cambio de namespace; eventos descartados | 2 |
| Una operación de archivo rechazada por el sandbox | 1 |
Un sudo en un espacio de trabajo que permite sudo | 0, se muestra en la cronología |
Una alarma que salta en el primer minuto enseña a la gente a ignorar las de verdad. Así que las tareas internas de Bromure se ejecutan fuera del sandbox y nunca aparecen como denegación, y los eventos de Bromure montando la VM durante el arranque se cuentan, pero no disparan la alarma, dentro de una ventana de arranque fija. Cuando salta la alarma, Bromure puede poner en cuarentena el espacio de trabajo: corta la red de la VM y la pausa.
Linaje: el razonamiento detrás de cada conexión
Un registro de red muestra que algo en la VM se conectó a un host, pero no por qué. Cuando el trabajo lo hace un agente, el porqué es lo que necesitas saber.
Bromure puede decírtelo, porque ve los dos extremos. Su proxy de IA está entre el agente y el proveedor del modelo y lee cada respuesta antes de que el agente actúe, así que conoce cada llamada a herramienta, el comando que va a ejecutar y lo que el modelo escribió justo antes. El centinela informa de cada programa que arranca la VM, con su línea de comandos, y de cada conexión nueva, con la cadena de procesos que hay detrás. Bromure une las dos cosas.
Supón que le pides al agente que añada un health check que reintente tu API de
pagos. Decide que necesita un cliente HTTP y ejecuta npm install axios. npm
descarga el paquete de registry.npmjs.org, que la política permite. Entonces
arranca un script postinstall incluido en la descarga e intenta llegar a
telemetry.example.net, que ninguna regla permite. La conexión se rechaza en el
proxy del Mac, y el linaje muestra cómo ocurrió:


El agente nunca escribió un comando que llegara a telemetry.example.net. Un
registro de red normal mostraría un proceso de Node hablando con un host
desconocido; el linaje muestra que fue el script de instalación de una
dependencia, bajo el npm install que el agente ejecutó por una razón declarada.
La unión se hace en tres pasos:
- Llamada a herramienta. El proxy de IA guarda durante diez minutos cada
llamada a herramienta de la respuesta del modelo: su ID (
tool_use_id, o elcall_idde OpenAI), el comando y hasta 2.000 caracteres de lo que el modelo escribió antes en el mismo turno. - Shell. A partir de los arranques de programas del centinela, el Mac
mantiene una tabla de procesos para cada VM, indexada por pid y hora de
inicio, así que un pid reutilizado no puede heredar la historia de otro. Una
shell que arranca bajo el agente en los 30 segundos siguientes a la respuesta
se vincula a la llamada a herramienta cuando el comando aparece en su línea de
comandos, una vez deshecho el entrecomillado propio del agente. Claude Code,
por ejemplo, ejecuta los comandos como
bash -c '… eval '"'"'<command>'"'"' …'. Eso es una coincidencia exacta. Cuando el agente reescribió el comando, Bromure recurre a la primera palabra, al momento y a la ascendencia, y marca la coincidencia como aproximada. - Decisión. Un flujo se empareja con el permiso o la denegación del switch por su protocolo, destino, puerto y puerto de origen en un margen de 5 segundos, y con la decisión del proxy cuando este inspeccionó la solicitud.
Toda conexión por debajo de la shell hereda el vínculo. Eso incluye el tráfico que pasa por el puente de proxy de Bromure, que de otro modo parecería todo conexiones hacia localhost. El puente empieza cada flujo hacia el Mac con una línea que indica el puerto de origen del cliente, para que el Mac pueda relacionar la conexión local con el destino real.
En la ventana de Seguridad, Show the Whole Chain en una fila de red abre esa vista.
Algunos modelos van directos a la llamada a herramienta sin escribir nada antes. En ese caso, la vista muestra el prompt y la intención declarada de la llamada. Los servicios en segundo plano y todo lo que arranca fuera de una llamada a herramienta se marcan como no rastreados hasta un agente.
El linaje también cambia lo que pasa después de un bloqueo. Un agente que solo ve un reset de conexión o un error de permisos lo toma por un fallo y prueba otro host, otro puerto u otra herramienta, lo que desde fuera parece un agente descontrolado. Bromure le dice al agente, en su propia sesión, que la política rechazó la solicitud y por qué, y que te pregunte en lugar de buscar un rodeo. El panel Sandbox de un espacio de trabajo en ejecución lista lo que se bloqueó, con un botón Permitir para cada cosa, y el asesor de políticas redacta reglas a partir de las denegaciones para que tú las apruebes o no.


El razonamiento del agente siempre se muestra en tu propia Cronología de seguridad. Solo se envía a la consola de una organización en bromure.io si esa organización lo activa, cosa que no ocurre por defecto, y nunca en el caso de perfiles privados. Los tokens de las líneas de comandos se ocultan antes de que nada salga del Mac.
Cómo activarlo
Las políticas de OpenShell, el centinela del kernel y el linaje llegan con Bromure Agentic Coding 5.4. Vienen desactivados por defecto. Para activarlos en un espacio de trabajo, abre Ajustes, luego la Política de sandbox del espacio de trabajo, y pon Sandbox engine en OpenShell + Sentry. Puedes escribir la política ahí mismo o importar un archivo YAML de OpenShell que ya tengas.


- Las reglas de red se aplican en directo. Los cambios del sistema de archivos surten efecto en el siguiente arranque de la VM, porque Landlock solo puede restringir lo que un proceso en ejecución puede hacer.
- Por defecto, el centinela funciona en modo de mejor esfuerzo. Ponlo en Required y Bromure eleva el lockdown del kernel a su nivel más estricto, y trata un centinela que nunca se conecta como manipulación.
- Con el centinela obligatorio, la shell de un espacio de trabajo está lista en 4 segundos cuando el módulo de su kernel está en caché, y en 6 segundos cuando Bromure tiene que descargarlo.
Para verlo en acción, mira los tres episodios de Claudia: escribe una política, la ajusta mientras un agente trabaja y rastrea un ataque a la cadena de suministro a las 2 de la madrugada hasta el razonamiento del agente.
Gracias al equipo de OpenShell en NVIDIA por publicar en abierto el formato de política, el motor de referencia y el prover. Construimos sobre los tres.