Sécurité

Sur cette page

Le Hub exécute, avec vos droits et dans vos applications, des scripts qu’un modèle a écrits. Cette page dit ce que le Hub borne, les risques qui restent et ce qui quitte votre machine, et donne les mesures rejouées pour elle. Elle s’adresse à qui décide s’il faut brancher un modèle sur ses applications.

Les mesures ont été faites sous Debian 13 avec le Hub 0.1.0 compilé depuis ses sources ; celles qui portent sur un Hub en marche ont utilisé un répertoire personnel jetable avec son propre jeton.

Ce qui quitte la machine

Tout ce que le Hub renvoie va au modèle de langage, donc à qui l’exploite.

DonnéeQuitte la machine
Le script que le modèle a écritOui : le modèle en est l’auteur
stdout, stderr et result d’un scriptOui : ils peuvent reprendre le contenu d’un document
La trace d’erreurOui : elle contient des chemins de la machine
stateOui : il contient du contenu tiré des documents ouverts
Le chemin d’une captureOui : il fait partie de la réponse
L’image d’une captureSeulement si le modèle lit le fichier
Le journal d’audit, le jeton et la configurationJamais

Un utilisateur qui ouvre un document confidentiel dans une application connectée sait que le modèle peut en lire le contenu. Lors du rejeu, get_state a renvoyé l’étiquette Notes d’un calque qu’un script avait créé, avec content_origin à document ; cette étiquette se lit comme se lit le contenu d’un document.

Le Hub ne se connecte qu’aux ponts sur 127.0.0.1. Lancé sous strace -f -e trace=connect,bind,listen pendant une session qui comprenait un script qui dépassait son délai, le Hub a fait au total un seul connect IP : vers 127.0.0.1, sur le port du pont de Blender. cargo test --locked --test invariants lit les sources à la recherche de toute autre connexion.

Qui peut atteindre un pont

Un pont écoute sur 127.0.0.1 seulement, accepte une connexion à la fois et répond à un client qui ouvre avec le jeton. ss -ltn a montré un seul socket en écoute pour le pont de Blender, sur le port réglé pour l’exécution : 127.0.0.1:9313. Quatre sondes lui ont été envoyées avant la connexion du Hub :

  • Un hello avec un mauvais jeton : 0 octet en retour, et la connexion fermée aussitôt.
  • Une requête HTTP : 0 octet en retour, et la connexion fermée aussitôt.
  • Rien d’envoyé : 0 octet en retour, et la connexion fermée après 5,0 s.
  • Un hello avec le bon jeton : 99 octets en retour, la réponse au hello, qui nomme l’application et les versions et ne contient pas le jeton. La connexion est restée ouverte.

Le port UNO propre à LibreOffice n’a pas d’authentification. Avec soffice en écoute sur 127.0.0.1:2102 et aucun jeton présenté, un client Python utilisant python3-uno a lu la version de LibreOffice, 25.2.3.2, ouvert un document Calc et y a écrit une cellule. Ce chemin contourne le jeton, le garde-fou de script et le journal d’audit.

Ce que le Hub borne

Les limites sont des clés de la section [hub] de config.toml, dans le fichier qu’écrit init ; les valeurs ci-dessous sont les valeurs par défaut qu’il liste.

  • Taille du script, max_script_bytes, 262 144 octets : un script de 300 009 octets a été refusé avec script is 300009 bytes; the limit is 262144 bytes.
  • Sortie par flux, max_output_bytes, 1 048 576 octets : un script qui imprimait 2 000 000 de caractères a renvoyé un stdout de 1 048 576 octets et "truncated": true.
  • Durée, default_timeout_s, 30 s : en mode cli, timeout après 30,0 s, aucun processus fils laissé, et l’appel suivant ok en 31 ms. En mode tcp, timeout après 31,2 s (32,0 s lors de la seconde exécution), puis busy pour l’appel suivant.
  • Un script à la fois par application : le busy ci-dessus. En mode tcp, l’application n’a pas été arrêtée ; elle a mené son script de 40 secondes à son terme, et le Hub a répondu de nouveau ok 40,3 s après le départ (40,1 s lors de la seconde exécution).

Seul le mode cli tue un script qui dépasse son délai. Dans les modes tcp et uno, le Hub ne peut pas interrompre un script à l’intérieur de l’application : il répond timeout, refuse les nouveaux scripts avec busy, et attend.

Le garde-fou de script

Le garde-fou lit le texte d’un script avant son envoi. Un script qui appelait os.remove a été refusé avant d’atteindre Inkscape, avec la catégorie, la construction et la ligne : G-1 (file deletion) at line 3: os.remove. Quand un chemin ne peut pas être vérifié à partir du texte, le garde-fou laisse passer le script et ajoute un avertissement dans stderr, par exemple path not statically verifiable (open()).

Un script qui a écrit un second fichier et l’a exécuté avec runpy a supprimé un fichier qu’il avait créé, et le Hub a répondu ok. C’est la limite que le README du dépôt énonce : le garde-fou ne voit ni runpy, ni pickle, ni http.server, ni les fonctions d’enregistrement des applications, ni un chemin passé en paramètre à une fonction. L’opérateur lève le garde-fou avec allow_dangerous_scripts = true dans la section [security] ; les deux outils ne prennent aucun paramètre de ce genre, et le journal enregistre alors guard: bypassed.

Sur les faux positifs du garde-fou, cargo test --locked --test guard realistic -- --nocapture a imprimé false positives: 0 of 21 realistic scripts (0.0%).

Fichiers, droits et refus de démarrer

Après init, config.toml et token avaient tous deux les droits 600. Une capture demandée par get_state était un fichier aux droits 600 dans un répertoire aux droits 700, sous XDG_RUNTIME_DIR ; elle y était encore après la sortie du Hub avec le code 0.

Le Hub a refusé de démarrer, avec le code de sortie 1 et une ligne d’explication, dans cinq cas :

CasMessage, sans le chemin
le fichier du jeton aux droits 0640has mode 0640; expected 0600 (chmod 600)
pas de fichier de jetondoes not exist
config.toml aux droits 0644configuration file refused: … has mode 0644; expected 0600 (chmod 600)
le répertoire d’exécution du Hub aux droits 0755has mode 0755; expected 0700 (chmod 700)
XDG_RUNTIME_DIR non définieXDG_RUNTIME_DIR is not set; refusing to write volatile files elsewhere

La valeur du jeton n’apparaissait dans aucun de ces messages. libremodular-launch libreoffice a refusé un fichier de pont aux droits 0664 avant de démarrer quoi que ce soit, avec le code de sortie 2 : has mode 0664 and can be changed by the group or by others (chmod go-w).

Le journal d’audit

Chaque appel écrit une ligne dans audit.jsonl du répertoire d’état, aux droits 600, get_state compris. Quatre appels (un script exécuté, un script refusé par le garde-fou, un qui a levé une erreur, un pour une application désactivée) ont écrit quatre lignes. Une ligne contient la date, l’outil, la cible, le statut, la durée, la taille et le SHA-256 du script, le résultat du garde-fou et la taille de la sortie :

{
  "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
}

Le fichier écrit chaque ligne sur une seule ligne ; elle est coupée ici pour être lue.

Ni la chaîne repère placée dans les scripts, ni le jeton n’ont été retrouvés dans le journal. Le texte du script n’est écrit que si l’opérateur règle audit_script_text = true.

Les tests du dépôt

Ces commandes sont celles de l’intégration continue du dépôt. Elles ont été exécutées hors du dépôt (CARGO_TARGET_DIR).

CommandeRésultat
cargo fmt --checkcode de sortie 0
cargo clippy --all-targets --locked -- -D warningscode de sortie 0
cargo test --locked328 réussis, 0 échoué, 3 ignorés : deux demandent un vrai LibreOffice, un demande un utilisateur qui n’est pas root
cargo test --locked --test invariants36 réussis
python3 scripts/build_bridges.py --checkcode de sortie 0
python3 -m unittest discover -s tests/bridges189 tests, OK, 6 ignorés : deux demandent root, quatre ne valent que sous Windows

Pour signaler une vulnérabilité, voir À propos.

SuiteCompatibilité →