Seguridad

En esta página

El Hub ejecuta scripts que escribió un modelo, con sus derechos, dentro de sus aplicaciones. Esta página dice qué limita el Hub, qué riesgos quedan y qué sale de su máquina, y da las mediciones repetidas para ella. Es para quien decide si conecta un modelo a sus aplicaciones.

Las mediciones se hicieron en Debian 13 con el Hub 0.1.0 construido a partir de su código fuente; las de un Hub en marcha usaron un directorio personal desechable con su propio token.

Lo que sale de la máquina

Todo lo que devuelve el Hub va al modelo de lenguaje y, por tanto, a quien lo ejecuta.

QuéSale de la máquina
El script que escribió el modeloSí: el modelo es su autor
stdout, stderr y result de un scriptSí: pueden contener el contenido de un documento
La traza de errorSí: contiene rutas de la máquina
stateSí: contiene contenido tomado de los documentos abiertos
La ruta de una capturaSí: forma parte de la respuesta
La imagen de una capturaSolo si el modelo lee el archivo
El diario de auditoría, el token y la configuraciónNunca

Un usuario que abre un documento confidencial en una aplicación conectada sabe que el modelo puede leer su contenido. En la repetición, get_state devolvió la etiqueta Notes de una capa que había creado un script, con content_origin en document; esa etiqueta se lee igual que el contenido de un documento.

El Hub solo se conecta a los puentes en 127.0.0.1. Ejecutado bajo strace -f -e trace=connect,bind,listen durante una sesión que incluía un script que superó su plazo, el Hub hizo en total un connect por IP: a 127.0.0.1, en el puerto del puente de Blender. cargo test --locked --test invariants lee el código fuente en busca de cualquier otro.

Quién puede llegar a un puente

Un puente escucha solo en 127.0.0.1, acepta una conexión a la vez y responde a un cliente que empieza con el token. ss -ltn mostró un socket en escucha para el puente de Blender, en el puerto fijado para la ejecución: 127.0.0.1:9313. Antes de que el Hub se conectara, se le enviaron cuatro sondas:

  • Un hello con un token incorrecto: 0 bytes de vuelta y la conexión cerrada al instante.
  • Una solicitud HTTP: 0 bytes de vuelta y la conexión cerrada al instante.
  • Nada enviado: 0 bytes de vuelta y la conexión cerrada tras 5,0 s.
  • Un hello con el token correcto: 99 bytes de vuelta, la respuesta al hello, que nombra la aplicación y las versiones y no contiene el token. La conexión siguió abierta.

El puerto UNO propio de LibreOffice no tiene autenticación. Con soffice escuchando en 127.0.0.1:2102 y sin presentar ningún token, un cliente de Python con python3-uno leyó la versión de LibreOffice, 25.2.3.2, abrió un documento de Calc y escribió en una celda. Ese camino esquiva el token, la compuerta de scripts y el diario de auditoría.

Lo que el Hub limita

Los límites son claves de la sección [hub] de config.toml, en el archivo que escribe init; los valores de abajo son los predeterminados que enumera.

  • Tamaño del script, max_script_bytes, 262 144 bytes: un script de 300 009 bytes se rechazó con script is 300009 bytes; the limit is 262144 bytes.
  • Salida por flujo, max_output_bytes, 1 048 576 bytes: un script que imprimía 2 000 000 de caracteres devolvió un stdout de 1 048 576 bytes y "truncated": true.
  • Tiempo, default_timeout_s, 30 s: en el modo cli, timeout a los 30,0 s, sin ningún proceso hijo restante, y la llamada siguiente ok en 31 ms. En el modo tcp, timeout a los 31,2 s (32,0 s en la segunda ejecución), y luego busy para la llamada siguiente.
  • Un script a la vez por aplicación: el busy anterior. En el modo tcp la aplicación no se detuvo; ejecutó hasta el final su script de 40 segundos, y el Hub volvió a responder ok 40,3 s después del inicio (40,1 s en la segunda ejecución).

Solo el modo cli mata un script que supera su plazo. En los modos tcp y uno, el Hub no puede interrumpir un script dentro de la aplicación: responde timeout, rechaza los scripts nuevos con busy y espera.

La compuerta de scripts

La compuerta lee el texto de un script antes de enviarlo. Un script que llamó a os.remove se rechazó antes de llegar a Inkscape, con la categoría, la construcción y la línea: G-1 (file deletion) at line 3: os.remove. Cuando una ruta no se puede comprobar a partir del texto, la compuerta deja pasar el script y añade una advertencia a stderr, por ejemplo path not statically verifiable (open()).

Un script que escribió un segundo archivo y lo ejecutó con runpy borró un archivo que había creado, y el Hub respondió ok. Ese es el límite que declara el README del repositorio: la compuerta no ve runpy, pickle, http.server, las funciones de guardado de las aplicaciones ni una ruta pasada a una función como parámetro. El operador levanta la compuerta con allow_dangerous_scripts = true en la sección [security]; las dos herramientas no admiten ese parámetro, y el diario registra entonces guard: bypassed.

Sobre los falsos positivos de la compuerta, cargo test --locked --test guard realistic -- --nocapture imprimió false positives: 0 of 21 realistic scripts (0.0%).

Archivos, modos y negativas a iniciar

Tras init, config.toml y token tenían ambos el modo 600. Una captura pedida con get_state era un archivo con el modo 600 en un directorio con el modo 700, bajo XDG_RUNTIME_DIR; seguía allí después de que el Hub terminara con el código 0.

El Hub se negó a iniciar, con el código de salida 1 y una línea de explicación, en cinco casos:

CasoMensaje, sin la ruta
el archivo del token con el modo 0640has mode 0640; expected 0600 (chmod 600)
sin archivo del tokendoes not exist
config.toml con el modo 0644configuration file refused: … has mode 0644; expected 0600 (chmod 600)
el directorio de ejecución del Hub con el modo 0755has mode 0755; expected 0700 (chmod 700)
XDG_RUNTIME_DIR sin definirXDG_RUNTIME_DIR is not set; refusing to write volatile files elsewhere

El valor del token no apareció en ninguno de estos mensajes. libremodular-launch libreoffice rechazó un archivo de puente con el modo 0664 antes de iniciar nada, con el código de salida 2: has mode 0664 and can be changed by the group or by others (chmod go-w).

El diario de auditoría

Cada llamada escribe una línea en audit.jsonl en el directorio de estado, con el modo 600, get_state incluida. Cuatro llamadas (un script que se ejecutó, uno que rechazó la compuerta, uno que produjo un error y uno para una aplicación desactivada) escribieron cuatro líneas. Una línea contiene la fecha, la herramienta, el destino, el estado, la duración, el tamaño y el SHA-256 del script, el resultado de la compuerta y el tamaño de la salida:

{
  "ts": "2026-10-21T08:56:27.564Z",
  "tool": "execute_script",
  "target_app": "inkscape",
  "status": "refused",
  "duration_ms": 0,
  "script_bytes": 60,
  "script_sha256": "3696b8a4f24f23da34ba239bbb401c56cd78fb53c2bef48ab2f7d668fd1fdbf8",
  "guard": [
    "G-1"
  ],
  "output_bytes": 524
}

El archivo guarda cada línea en una sola línea; aquí se partió para poder leerla.

Una cadena marcadora colocada en los scripts no se encontró en el diario, y el token tampoco. El texto del script solo se escribe si el operador establece audit_script_text = true.

Las pruebas del repositorio

Estos comandos son los de la CI del repositorio. Se ejecutaron fuera del repositorio (CARGO_TARGET_DIR).

ComandoResultado
cargo fmt --checkcódigo de salida 0
cargo clippy --all-targets --locked -- -D warningscódigo de salida 0
cargo test --locked328 superadas, 0 fallidas, 3 ignoradas: dos necesitan un LibreOffice real, una necesita un usuario que no sea root
cargo test --locked --test invariants36 superadas
python3 scripts/build_bridges.py --checkcódigo de salida 0
python3 -m unittest discover -s tests/bridges189 pruebas, OK, 6 omitidas: dos necesitan root, cuatro son solo para Windows

Para informar de una vulnerabilidad, véase Acerca del proyecto.

SiguienteCompatibilidad →