Core concepts
Backend capability matrix
Compare the exact filesystem, network, process, lifecycle and platform guarantees of every backend.Ce document est la référence de l'Executor ABI 10 pour les garanties annoncées
par les backends de libmaelys-executor. Les tableaux décrivent les
capacités retournées par probe() sur un hôte où le backend est disponible.
Une primitive du système d'exploitation n'est jamais une garantie Warden tant
que le backend ne l'annonce pas et ne sait pas abaisser le plan exact.
État des implémentations
| Nom | Cible | Plateforme | État dans Warden | Frontend produit actuel |
|---|---|---|---|---|
posix | exécutable hôte | POSIX | enregistré et sélectionnable | CLI, C, Python, Node |
bubblewrap | exécutable hôte | Linux amd64/arm64 | enregistré et sélectionnable | CLI, C, Python, Node |
seatbelt | exécutable hôte | Apple Silicon, macOS 15+ | enregistré et sélectionnable | CLI, C, Python, Node |
apple-container | image OCI scellée | Apple Silicon, macOS 26 ; linux/arm64 natif ou linux/amd64 via Rosetta attestée | driver détectable, mais inéligible au budget ABI 10 : API couplée et cgroupfs mutable par root | API de driver historique seulement |
apple-container-bubblewrap | image OCI scellée, helper invité injecté | même hôte et plateformes ; bundle TCB arm64 attesté | budget ABI 10 exact : VM service-attestée, cgroup workload Warden protégé et observé | CLI, C, Python, Node via TrustedRunSpec |
apple-containerization | ext4 OCI Warden scellée | Apple Silicon, macOS 26 ; linux/arm64 natif ou linux/amd64 via Rosetta attestée | driver Swift direct, noyau/maelys-init Warden, Seatbelt hôte deny-default | CLI, C, Python, Node via TrustedRunSpec |
apple-containerization-bubblewrap | même ext4 scellée, Bubblewrap Warden injecté | mêmes hôte et plateformes | même VM directe et même PID 1 ; ajoute READ/WRITE récursif via le seam invité commun | CLI, C, Python, Node via TrustedRunSpec |
krun | image OCI scellée, ext4 déterministe | Apple Silicon ; linux/arm64 | backend ABI 10 auto-découvert ; MI/6, VM exacte, cgroup-v2, réseau NONE ou proxy Egress, racine éphémère | CLI, C, Python, Node via TrustedRunSpec |
krun-bubblewrap | même ext4 scellée, Bubblewrap Warden injecté | Apple Silicon ; linux/arm64 | mêmes limites ABI 10 sur tout le sous-arbre Bubblewrap ; READ/WRITE récursif et procfs neuf | CLI, C, Python, Node via TrustedRunSpec |
maelys_executor_create_default() découvre les drivers optionnels Apple puis
Krun. Pour Apple Container, il
enregistre apple-container, puis le composite seulement si le bundle invité
installé est sûr et attestable. Ce bundle appartient à Warden : bwrap, le
relay/bootstrap arm64 et le probe amd64 ne viennent jamais de l'image OCI. Une
absence ou une mutation du bundle retire le composite ou fait échouer
l'artefact ; elle ne déclenche aucun fallback vers le candidat simple.
Les candidats apple-containerization et
apple-containerization-bubblewrap forment une branche distincte : ils ne dépendent
pas du CLI Apple Container. Il consomme le store d'artefacts OCI et l'ext4
déterministe de Warden, un noyau brut scellé et maelys-init via MI/6. Il ne prend part à la
sélection que lorsque driver, noyau et init sont tous disponibles. Il livre
linux/arm64 natif ou linux/amd64 traduit, captured stdio et les réseaux
NONE/IP_NONE ; Egress n'est pas annoncé. Le candidat simple n'annonce pas
FS_ACCESS_RULES. Le composite ajoute seulement les règles READ/WRITE
récursives ; Bubblewrap, injecté dans l'ext4 et couvert par son seal, est lancé
par maelys-init après un preflight borné.
Son processus possède directement la VM et annonce donc
PROCESS_TREE_LIFECYCLE, contrairement aux deux candidats utilisant le service
Apple. Son VMM est entouré d'un profil Seatbelt hôte deny-default dont les
refus fichier et réseau sont prouvés avant sélection ; l'entitlement
Virtualization reste une permission d'exécution, pas cette garantie. Le planner ne lui attribue aucune préférence
cachée : force et coût statiques sont alignés sur apple-container simple.
Les vues hôte du candidat direct sont abaissées dans l'unique device VirtioFS
virtiofs, sous des noms privés liés au plan. maelys-init valide puis
bind-monte uniquement les entrées admises et retire la vue de staging avant
READY. Comme la racine ext4 est read-only, /workspace doit déjà exister
dans l'image pour utiliser --ro ou --rw; --rootfs-only reste indépendant
de ce point de montage.
Le backend Krun possède l'adaptateur maelys_executor_backend_ops, le protocole
MWDP/1, un artefact OCI→ext4 scellé et un lifecycle borné. Depuis 0.33.0, il
charge exactement le même noyau brut arm64 que Containerization direct via
krun_set_kernel. La bottle Warden external-kernel-only supprime entièrement
le chemin libkrunfw depuis 0.34.0. Noyau et manifeste sont épinglés et
revalidés avant la création de la VM, sans fallback. krun annonce la
racine ext4 read-only, les mappings VirtioFS, tmpfs, l'absence de réseau et la
frontière VM. krun-bubblewrap ajoute uniquement FS_ACCESS_RULES pour
READ/WRITE récursif. Le matérialiseur remplace toujours /maelys-bwrap par le
binaire du bundle signé et lie son digest au seal ; un binaire de l'image ne
participe jamais. Le même contrat MWOCI/3 remplace et lie aussi le relay invité.
Pour une MIR médiée, le relay ouvre uniquement AF_VSOCK CID hôte/port 4520 ;
Egress conserve l'identité du principal, l'allowlist, DNS/SNI, les quotas et les
receipts. Le pont AF_UNIX utilise un principal lié à l'endpoint sans secret.
Les deux processus VMM Krun sont en outre confinés par un profil Seatbelt hôte
deny-default attesté avant leur enregistrement.
Depuis 0.34.0, la release installe le runtime warden-headless (libkrun construit avec
blk,external-kernel-only, sans libkrunfw). Le backend sait aussi relire l'ancien
manifeste upstream-gpu, dont la fermeture comprend en plus libepoxy,
virglrenderer et MoltenVK. Les deux variantes passent par le même
compilateur Seatbelt : sa table de chemins est construite depuis la fermeture
réellement admise, puis chaque entrée est canonisée, épinglée et injectée comme
paramètre SBPL. Le profil GPU n'est donc pas perdu ; il n'est simplement plus
la dépendance par défaut de Warden.
TrustedRunSpec n'est pas une API réservée aux VM. Il accepte deux sortes de
TargetSpec : local et oci. Avec une cible locale, le planner peut choisir
POSIX, Seatbelt ou Bubblewrap selon le DeploymentContext, la
SelectionPolicy et les capacités requises par la MIR. Avec une cible OCI, il
considère les candidats Apple et, lorsque son paquet optionnel est présent, le
candidat Krun pour linux/arm64. La CLI, la
façade C et les SDK utilisent le même chemin RunSpec ; seul le receipt de sortie
local reste v1 par défaut pour compatibilité produit.
Architecture comparée
POSIX, Seatbelt et Bubblewrap exécutent un binaire du système hôte. Les deux
candidats Apple acceptent exclusivement un TargetSpec OCI : ils ne
convertissent jamais un exécutable local en image. apple-container-bubblewrap
est un candidat composite unique, préparé et reçu comme tel ; ce n'est pas un
fallback tardif du candidat simple.
Apple Container sélectionne explicitement une variante linux/amd64 et
active Rosetta dans sa VM arm64 uniquement si le driver en fournit une preuve
stable. Le candidat direct applique la même règle avec son propre probe
Containerization. L'absence de Rosetta rejette le candidat avant prepare ;
Warden ne l'installe jamais. Krun sur Apple Silicon n'est pas une alternative
amd64 : libkrun n'expose pas le partage Rosetta de Virtualization.framework.
Dans le composite visé, linux/amd64 ne signifie pas « toute la VM devient
amd64 ». Le driver, le noyau Linux invité, le Bubblewrap statique injecté et le
relay Egress restent arm64. Bubblewrap applique l'isolation nativement, puis
binfmt_misc dirige seulement l'entrypoint amd64 vers Rosetta. La
compatibilité de plateforme est évaluée séparément du masque de
capacités MIR : un backend peut posséder EXECUTION tout en refusant une
plateforme ou une traduction indisponible.
Masques de capacités ABI 10
| Capacité Executor | POSIX | Bubblewrap | Seatbelt | Apple Container | Apple + Bwrap | Krun | Krun + Bwrap | Containerization | Containerization + Bwrap |
|---|---|---|---|---|---|---|---|---|---|
EXECUTION | oui | oui | oui | oui | oui | oui | oui | oui | oui |
FILESYSTEM | non | oui | oui | oui | oui | oui | oui | oui | oui |
FS_ACCESS_RULES | non | READ/WRITE/DENY ; récursif, ou littéral pour fichiers | READ/WRITE littéral ou récursif | non | READ/WRITE récursif seulement | non | READ/WRITE récursif seulement | non | READ/WRITE récursif seulement |
FS_DENY_READ | non | oui | non | non | non | non | non | non | non |
FS_PROTECT_CREATE | non | non | non | non | non | non | non | non | non |
FS_VIEW_MAPPING | non | oui | non | oui | oui | oui | oui | oui, /workspace | oui, /workspace |
FS_TMPFS | non | oui | non | oui | oui | oui | oui | oui, /tmp | oui, /tmp |
NETWORK_NONE | non | oui | oui | oui | oui | oui | oui | oui, sans vue hôte | oui, sans vue hôte |
NETWORK_IP_NONE | non | oui | oui | non | non | non | non | oui | oui |
NETWORK_EXTERNAL_CHANNEL | non | oui | oui | non | non | non | non | non | non |
NETWORK_MEDIATED_PROXY | non | oui | oui | oui | oui | oui | oui | non | non |
SYSCALL_FILTER | non | oui | non | non | non | non | non | non | non |
UNIX_SOCKET_CONTROL | non | oui | non | non | non | non | non | non | non |
PROCESS_CONFINEMENT | non | oui | oui | oui | oui | oui | oui | oui | oui |
PROCESS_TREE_LIFECYCLE | non | non | non | non | non | oui | oui | oui | oui |
ROOT_EPHEMERAL_WRITE | non | non | non | non | non | oui | oui | oui | oui |
RESOURCE_VM_LIMITS | non | non | non | non | oui | oui | oui | oui | oui |
RESOURCE_GUEST_CGROUP | non | non | non | non | oui | oui | oui | oui | oui |
Le masque n'exprime pas la compatibilité de la cible, le mode d'I/O ou toutes
les contraintes d'un plan. Le planner vérifie d'abord le type de cible et le
masque ; prepare() refuse ensuite toute forme que le backend ne peut pas
représenter exactement.
Budgets de ressources ABI 10
Chaque cible OCI porte un ExecutionBudget canonique, même lorsque
l'utilisateur conserve les valeurs par défaut. krun, krun-bubblewrap,
apple-containerization, apple-containerization-bubblewrap et
apple-container-bubblewrap appliquent exactement
le nombre de vCPU et la mémoire VM, puis installent dans l'invité un cgroup-v2
privé pour le workload :
maelys-init hors cgroup workload
├── relay Egress éventuel, hors comptabilité workload
└── cgroup workload
├── memory.max + memory.swap.max=0
├── cpu.max
├── pids.max
└── workload ou bwrap + tout son sous-arbreSur les backends Warden directs, le workload reçoit un nouveau namespace cgroup
et une vue où la hiérarchie est absente. Sur le composite Apple service, le
bootstrap reste hors du groupe, puis Bubblewrap expose cgroupfs en lecture
seule et sans capabilities. Dans les deux cas, le workload rejoint son groupe
avant execve. Le
receipt v7 atteste valeurs demandées/appliquées, pics/événements observés,
suppression du groupe et, pour une policy médiée, quotas/compteurs Egress. Le
PID 1, le relay et les écritures d'une vue VirtioFS RW ne sont pas inclus dans
les compteurs cgroup. Le receipt conserve une entrée de contrôleur hôte
candidat-spécifique, mais elle reste explicitement non appliquée : les gates
physiques macOS 26 ont démontré qu'un RLIMIT_CPU fini bloque libkrun 1.19.4.
Le candidat simple Apple Container n'annonce aucun des deux bits : son workload
root peut modifier le cgroup du service. Le composite annonce les deux. Son
driver relit le dimensionnement VM dans inspect; le bootstrap Warden applique
les limites workload, observe les pics/événements, détruit le groupe et remet le
PID 1 dans le domaine suivi par Apple avant de produire MWAR/2. Le
spike physique documente la preuve qui a
conduit à cette séparation, sans fork du service Apple.
Rapport de sélection structuré
Le maelys_candidate_report_t opaque sépare désormais les quatre informations
qu'un intégrateur doit pouvoir traiter sans analyser une chaîne d'erreur :
required capabilities
├── required by MIR
├── required by target
└── required by resource budget
candidate
├── available capabilities
└── missing = required & ~availableLes getters maelys_candidate_report_required_capabilities(),
maelys_candidate_report_required_capabilities_from(),
maelys_candidate_report_available_capabilities_at() et
maelys_candidate_report_missing_capabilities_at() conservent les masques pour
les outils. maelys_backend_capability_name() fournit leur nom canonique. Une
contrainte de cible qui n'est pas un bit reste exprimée par le code de rejet
target-kind ou target-platform ; elle n'est pas artificiellement transformée
en capacité.
Filesystem
| Sémantique | Bubblewrap hôte | Seatbelt hôte | Apple Container | Apple + Bwrap | Krun | Krun + Bwrap | Containerization direct |
|---|---|---|---|---|---|---|---|
| racine d'exécution | vue vide reconstruite | filesystem hôte filtré par SBPL | racine OCI read-only | racine OCI read-only, puis vue Bubblewrap invitée | ext4 scellée RO ou overlay tmpfs éphémère | ext4 scellée puis overlay tmpfs, avant la vue Bubblewrap | même ext4 Warden scellée RO ou overlay tmpfs éphémère |
| READ/WRITE portable | récursif ; littéral pour fichiers réguliers | littéral ou récursif | refusé | récursif seulement | refusé | récursif seulement | refusé |
| chemin absent | ERROR/SKIP | ERROR/SKIP | sans objet | ERROR/SKIP | sans objet | ERROR/SKIP | sans objet |
| DENY prioritaire | overlay final mode 000 | refusé | refusé | refusé | refusé | refusé | refusé |
| mapping RO/RW | oui, bind mount épinglé | refusé | oui, VirtioFS épinglé | oui, VirtioFS extérieur | oui, VirtioFS épinglé | oui, avant le sandbox intérieur | un répertoire épinglé vers /workspace |
| tmpfs | oui | refusé | oui | oui | oui | oui | /tmp uniquement |
| protection de création d'un chemin absent | refusée | refusée | refusée | refusée | refusée | refusée | refusée |
Le candidat Apple simple sait construire la vue OCI avec des mappings
VirtioFS et des tmpfs, mais il refuse toute liste fs_rule. Le composite ajoute
des bind mounts Bubblewrap dans l'invité. Un bind mount porte sur un sous-arbre,
donc seules les règles READ/WRITE de portée RECURSIVE ont aujourd'hui un
abaissement exact ; une règle LITERAL prétendrait protéger le chemin sans
accorder ses descendants, ce que cette construction ne garantit pas.
La case FS_ACCESS_RULES est un préfiltre de famille, pas la promesse que
toutes les variantes sont acceptées. Après le masque de capacités, prepare()
vérifie chaque règle et refuse LITERAL, DENY et PROTECT_CREATE. Le
composite installe bien un procfs neuf dans son PID namespace, mais cette
garantie de visibilité des processus est indépendante de la compilation des
règles filesystem et du filtrage d'appels système. Warden n'annonce donc
toujours ni DENY, ni FS_PROTECT_CREATE, ni filtre seccomp intérieur.
Exemples concrets :
READ RECURSIVE /workspaceautorise /workspace, /workspace/src et tout ce qui se trouve dessous. Un
--ro-bind /workspace /workspace représente naturellement ce sous-arbre.
READ LITERAL /workspaceautoriserait l'objet répertoire /workspace, mais pas
/workspace/src. Un bind mount du répertoire expose nécessairement ses
descendants : le composite le refuse donc. Pour un fichier régulier, cette
distinction n'existe pas, mais le compilateur composite est volontairement
uniforme et n'accepte aujourd'hui que RECURSIVE.
READ RECURSIVE /workspace
DENY RECURSIVE /workspace/.secretsdemande qu'une interdiction plus spécifique gagne sur l'autorisation parente,
y compris face aux renommages. Le compilateur Bubblewrap invité ne construit
pas encore l'overlay final nécessaire et refuse DENY.
DENY /workspace/future-secret missing=PROTECT_CREATEdemande de protéger un chemin qui n'existe pas encore contre sa création future. On ne peut pas bind-mounter un objet absent ; il faut protéger ou reconstruire son parent. Cette garantie n'est pas implémentée.
Réseau
NETWORK_INHERIT est un choix explicite de réseau direct et ne requiert aucun
bit de capacité, mais reste soumis aux contraintes de prepare(). Les deux
candidats Krun le refusent tant que leur contrat ne fournit aucune carte réseau.
Il ne doit jamais être utilisé comme fallback d'un mode plus strict.
| Mode / frontend | POSIX | Bubblewrap | Seatbelt | Apple Container | Apple + Bwrap | Krun | Krun + Bwrap | Containerization | Containerization + Bwrap |
|---|---|---|---|---|---|---|---|---|---|
direct (INHERIT) | ambiant | namespace hôte partagé | règle (allow network*) | réseau Apple Container | réseau Apple Container | refusé | refusé | refusé | refusé |
aucun (NONE) | refusé | netns + seccomp | SBPL deny | aucune attache réseau | aucune attache + netns intérieur | aucune carte réseau, TSI désactivé | même frontière VM ; pas d'unshare réseau intérieur | aucune interface ; aucun mapping hôte | même VM sans interface |
aucun IP (IP_NONE) | refusé | netns | SBPL deny | refusé | refusé | refusé | refusé | aucune interface ; /workspace admis | idem |
médié fd4 | refusé | canal Unix datagram + SCM_RIGHTS | même contrat sous SBPL | refusé | refusé | refusé | refusé | refusé | refusé |
médié proxy | refusé | relay loopback privé → AF_UNIX → Egress | port loopback Egress exact | relay invité → UDS/vsock → Egress hôte | même pont, relay hors Bubblewrap intérieur | relay invité → AF_VSOCK 4520 → Egress hôte | même pont, relay hors Bubblewrap intérieur | non livré | non livré |
NONE et IP_NONE sont deux contrats différents :
NONEinterdit le réseau IP et les sockets Unix adressées par chemin ;IP_NONEinterdit le réseau IP mais conserve les sockets Unix explicitement rendues accessibles par le plan, par exemple un canal de contrôle local.
Apple Container sait satisfaire NONE en créant le conteneur sans attache
réseau. Il n'annonce pas IP_NONE, car un socket Unix hôte ne peut pas être
transporté comme un socket vivant par un mapping VirtioFS à travers la VM. Le
composite ajoute un namespace réseau Linux intérieur, mais cela ne crée pas le
canal Unix trans-VM exigé par IP_NONE; il est donc refusé lui aussi plutôt que
d'être silencieusement remplacé par le contrat plus restrictif NONE.
Le frontend fd 4 natif repose sur un datagramme Unix de contrôle puis sur
SCM_RIGHTS, qui transmet un descripteur ouvert entre processus du même
noyau. Un descripteur de socket macOS ne peut pas être installé de cette
façon dans la table de descripteurs d'un processus Linux invité. Les deux
backends Apple refusent donc NETWORK_EXTERNAL_CHANNEL.
Bubblewrap hôte fonctionne parce qu'un namespace n'est pas un noyau :
Le workload Bubblewrap voit une autre racine, d'autres PID et éventuellement
un autre namespace réseau, mais ses descripteurs sont toujours des objets du
noyau Linux hôte. Warden peut donc installer le datagramme de contrôle sur son
fd 4 et Egress peut lui transmettre un stream avec SCM_RIGHTS.
Apple Container ajoute au contraire une frontière de noyau :
Le pont proxy transporte des octets, pas un fd macOS. Même avec Bubblewrap dans l'invité, le passage macOS→Linux reste nécessaire avant que des opérations SCM_RIGHTS internes au noyau invité deviennent possibles.
Un futur frontend Maelys-aware invité devra demander la connexion par vsock à un broker invité, puis recevoir un stream Linux relayé dont Egress garde le pair hôte. Ce sera un nouveau pont explicite ; ce n'est pas le contrat SCM_RIGHTS natif actuel. Les deux backends Apple Container proposent le frontend proxy : le relay invité est aveugle au protocole et à la policy, tandis que Egress reste l'unique moteur d'authentification, DNS/pinning, connexion, relay et receipts. Le candidat Containerization direct refuse aujourd'hui toute médiation plutôt que de prétendre réutiliser ce pont sans l'avoir physiquement prouvé.
Processus, syscalls et lifecycle
- Bubblewrap et Seatbelt annoncent que leur politique suit les descendants.
- Apple Container applique la décision au domaine conteneur/VM et le composite ajoute l'héritage de la politique Bubblewrap dans l'invité.
- Seul Bubblewrap hôte annonce actuellement un filtre syscall : filtre BPF vérifié par architecture et adapté au mode réseau.
- Les deux candidats Krun et
apple-containerizationannoncentPROCESS_TREE_LIFECYCLE. Le candidat direct le prouve avec son propremaelys-initPID 1, ses process groups et la destruction de sa VM. POSIX, Bubblewrap, Seatbelt et les deux candidats Apple Container ne l'annoncent pas ; un arrêt borné existe, mais Warden ne prétend pas y prouver la disparition de tout descendant hostile ni, après la mort forcée du driver Apple, la suppression du domaine VM par le service Apple.
PROCESS_CONFINEMENT et PROCESS_TREE_LIFECYCLE sont donc volontairement
distincts : propagation de politique n'est pas supervision exhaustive.
I/O et environnement des candidats VM
Les candidats Apple Container, Containerization direct et Krun refusent actuellement :
- l'environnement hôte hérité ; utilisez
EMPTYouEXPLICIT; ISOLATED_DUPLEXet son protocole workload fd 3 ;- le frontend réseau workload fd 4.
Le candidat Containerization direct n'accepte en plus que CAPTURED_STDIO :
stdin, stdout et stderr empruntent trois connexions vsock distinctes. Sa console
noyau est privée et ne rejoint stderr qu'en tant que diagnostic de boot échoué.
Le contrôle Warden↔driver utilise lui aussi un fd numéroté 4, mais dans le
processus driver hôte. Warden y installe une extrémité d'un
AF_UNIX/SOCK_STREAM avant exec du driver. Il ne transmet pas ce fd au
guest. Le fd 3 applicatif et le fd 4 réseau sont, eux, des conventions de la
table de descripteurs du workload natif, activées seulement par les modes
qui les demandent. Le schéma complet est dans
warden-driver-protocol.md.
Comment lire une sélection
Une absence de runtime, une preuve différente, une capacité manquante ou une forme non représentable produit une erreur explicite. Aucun chemin confiné ne retombe sur POSIX, sur le réseau direct ou sur le candidat Apple simple.