Use Warden
CLI reference
Complete maelys-warden command reference for local and OCI targets, I/O, network frontends, MIR and receipts.Forme générale
maelys-warden run SOURCE-DE-POLICY [OPTIONS] -- COMMAND [ARGUMENT...]La sous-commande run distingue l’exécution des sous-commandes image inspect et image import, ainsi que des futures commandes de compilation de
policy. Le séparateur -- termine définitivement les
options Warden. Tout ce qui le suit devient l’argv exact du workload.
Sans --image, cette commande crée un TargetSpec local. Avec --image REFERENCE@sha256:DIGEST, elle crée un TargetSpec OCI. --backend apple-container avec une forme locale échoue donc sur le type de cible au lieu
de convertir silencieusement la commande en image.
CLI directe et TrustedRunSpec
Pour OCI, la façade construit le quintuplet scellé suivant :
TargetSpec + plan + DeploymentContext + SelectionPolicy + ExecutionBudget
-> TrustedRunSpecLe cœur Executor produit les preuves ABI 10 et le rapport de sélection pour les
deux sortes de cibles. La façade sérialise v1 par défaut pour une commande
locale afin de conserver son contrat produit. Une commande OCI exige et
produit v7 ; v2 à v6 restent gelés pour vérifier d'anciens receipts, pas comme
sorties OCI sélectionnables en 0.40. Elle n'interprète jamais -- command comme une
image et ne fabrique jamais de digest implicite.
Images OCI hors ligne
Le support d'image est fourni par le paquet commun optionnel
maelys-warden-oci-tools. Il est partagé par les drivers Krun et
Containerization direct, sans appartenir à l'un d'eux. Le CLI principal découvre automatiquement
maelys-warden-oci-materializer à côté de lui ou sous libexec. S'il manque,
les commandes image échouent avec le conseil d'installer
maelys-warden-oci-tools; les backends locaux restent utilisables sans ces
dépendances.
# Lecture seule : vérifie la structure et liste les variantes disponibles.
maelys-warden image inspect ./tool.oci
# Écriture : importe l'unique variante dans le store privé local.
maelys-warden image import ./tool.oci
# Source multi-plateforme : sélection explicite, sans contacter de registry.
maelys-warden image import ./bundle.oci.tar \
--platform linux/arm64 \
--digest sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefSOURCE peut être un répertoire OCI Image Layout ou une archive contenant ce
layout. La détection repose sur oci-layout et index.json, pas sur le suffixe
du chemin. inspect ne modifie rien. import vérifie toute la fermeture des
descripteurs, applique les layers dans un graphe Linux byte-exact puis fabrique
directement un ext4 déterministe. Il ne contacte jamais le réseau.
Pour une image à une seule variante, la plateforme et le digest sont lus dans
la source vérifiée. Lorsqu'il existe plusieurs variantes, l'absence de
sélecteur est refusée : l'utilisateur copie les valeurs affichées par
inspect. Il ne calcule pas lui-même le SHA-256.
Le store est choisi dans cet ordre :
MAELYS_WARDEN_OCI_STORE;$XDG_DATA_HOME/maelys-warden/oci;$HOME/.local/share/maelys-warden/oci.
Une importation réussie imprime par exemple :
oci@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef linux-arm64 /Users/alice/.local/share/maelys-warden/oci/objects/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef/linux-arm64Le premier champ donne la référence immuable utilisable par run, le deuxième
la variante et le troisième l'artefact local. Celui-ci contient root.ext4,
artifact.json pour l'audit et artifact.seal pour la validation mécanique.
Pendant l'import, Warden supprime les éventuels /maelys-init,
/maelys-bwrap et /maelys-guest-supervisor fournis par l'image et insère les
trois helpers arm64 statiques du bundle Warden vérifié. Leurs digests sont
inscrits dans le seal MWOCI/3 et couverts
par le digest de root.ext4 : l'image OCI ne choisit ni l'init ni le sandbox
interne.
Le même root.ext4 scellé est consommé par Krun et par le backend
apple-containerization direct. Le produit Apple Container service garde
son propre store et ne consomme pas cet artefact. K4 livre cette importation et
sa validation ; K5 lance ce disque ext4 en lecture seule avec Krun et monte les
vues explicitement demandées. K6
sélectionne krun-bubblewrap quand la MIR exige
des règles READ/WRITE récursives ; DENY, les règles littérales et
PROTECT_CREATE restent refusés. K7 fournit le proxy Egress aux deux candidats
sur AF_VSOCK 4520, sans activer TSI ni une carte réseau invitée. K8 confine
enfin le VMM lui-même dans un profil Seatbelt hôte deny-default sondé avant
l'enregistrement du candidat. image list relit et valide les objets du store
privé ; image remove supprime uniquement la variante immuable explicitement
nommée, sans suivre de symlink. Ces deux commandes sont livrées depuis K9.
image pull reste hors de la surface actuelle : image import demeure
strictement hors ligne et ne contacte jamais une registry.
Depuis 0.32, Krun transmet le RunSpec canonique après le boot sur MI/6. L'argv, l'environnement et les credentials ne transitent donc jamais par la cmdline noyau ; le même framing borné est partagé avec Containerization direct.
CLI OCI multiplateforme
La présence de --image est l'unique sélecteur de cible OCI : sans cette
option, la commande reste une cible locale.
maelys-warden run SOURCE-DE-POLICY \
--image REFERENCE@sha256:DIGEST \
[--platform linux/arm64|linux/amd64] \
[--guest-workspace /workspace] \
[--guest-cwd /workspace] \
[--backend NAME] \
[--ephemeral-rw [--ephemeral-rw-size SIZE]] \
[--vm-cpus N] [--vm-memory SIZE] \
[--memory SIZE] [--cpu-quota USEC] [--cpu-period USEC] \
[--tasks N] [--wall-time MS] \
[--network-connections N] \
[--network-bytes-per-connection SIZE] \
[--network-bytes-total SIZE] \
[--receipt-schema v7] \
-- COMMAND-DANS-L-IMAGE [ARGUMENT...]Exécution native arm64, avec le workspace hôte courant monté en lecture seule
dans /workspace :
maelys-warden run --ro \
--image ghcr.io/maelys/tool@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
-- /usr/bin/tool --argumentLa plateforme peut être omise seulement si le DeploymentContext scellé ne
présente qu'une plateforme native : sur Apple Silicon, cela donne
linux/arm64 pour les candidats Apple comme pour les deux candidats Krun,
ces derniers n'acceptant d'ailleurs aucune autre plateforme. Si plusieurs plateformes natives sont
disponibles, ou si seule une traduction est possible, --platform devient
obligatoire. La forme longue, recommandée dans un script reproductible, est :
maelys-warden run --ro \
--image ghcr.io/maelys/tool@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
--platform linux/arm64 \
--guest-workspace /workspace \
--guest-cwd /workspace \
-- /usr/bin/tool --argumentExécution explicite de la variante amd64 sous Rosetta :
maelys-warden run --rw \
--image ghcr.io/maelys/tool@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \
--platform linux/amd64 \
--net github.com:443 \
--network-frontend proxy \
--receipt execution.json \
--network-connections 16 \
--network-bytes-per-connection 32M \
--network-bytes-total 256M \
--receipt-schema v7 \
-- /usr/bin/tool buildCette commande signifie précisément :
- résoudre la variante
linux/amd64de l'image identifiée par son digest ; - monter le workspace hôte dans
/workspaceselon la policy--rw; - demander au planner un candidat OCI satisfaisant la MIR ;
- exiger Rosetta disponible, sans installation interactive ni fallback arm64 ;
- utiliser Egress via le frontend proxy : Apple emploie son pont UDS/vsock et Krun le port AF_VSOCK 4520, sans réseau IP ambiant dans la VM ;
- appliquer les plafonds réseau dans l'instance Egress propre à l'exécution ;
- lancer
/usr/bin/tool builddans l'image et produire un receipt v7.
Nouvelles options OCI
| Option | Contrat |
|---|---|
--image REFERENCE@sha256:DIGEST | crée un TargetSpec OCI ; digest complet obligatoire, tag mutable seul refusé |
--platform linux/arm64|linux/amd64 | architecture du workload OCI ; inférée seulement si une unique plateforme native est prouvée |
--guest-workspace PATH | destination absolue du workspace hôte dans l'invité ; défaut : /workspace |
--guest-cwd PATH | répertoire de travail absolu de la commande dans l'invité ; défaut : valeur de --guest-workspace |
--backend NAME | autorise exactement ce candidat ; aucune option laisse le planner choisir |
--ephemeral-rw | exige une racine OCI writable jetable ; aucune racine locale ni fallback readonly |
--ephemeral-rw-size SIZE | borne exacte du tmpfs upper/work ; suffixes K, M, G, défaut 256 Mio, plage 16 Mio–64 Gio |
--vm-cpus N | nombre exact de vCPU, 1–16 ; défaut 2 |
--vm-memory SIZE | mémoire exacte de la VM, alignée au Mio, 512 Mio–64 Gio ; défaut 1 Gio |
--memory SIZE | memory.max du workload ; défaut mémoire VM moins max(256 Mio, arrondi_au_Mio_supérieur(mémoire_VM / 32)) |
--cpu-quota USEC | quota cgroup CPU par période ; entier décimal, défaut 100000 |
--cpu-period USEC | période cgroup CPU ; entier décimal 1000–1000000, défaut 100000 |
--tasks N | plafond pids.max, 16–32768 ; défaut 256 |
--wall-time MS | deadline après READY, 1 s–7 jours ; entier décimal, défaut 600000 |
--network-connections N | connexions Egress simultanées, 1–4096 ; exige une policy médiée |
--network-bytes-per-connection SIZE | octets relayés maximum par connexion Egress, 4 Kio–1 Tio |
--network-bytes-total SIZE | octets relayés cumulés maximum pour toute l'exécution, 4 Kio–1 Tio ; omission = illimité |
--receipt-schema v7 | contrat JSON obligatoire pour OCI ; v1 reste le défaut local |
Les trois options réseau sont des ressources de déploiement, pas une
allowlist. Elles n'autorisent aucune destination : au moins un --net HOST:PORT ou une policy MIR/JSON mediated reste nécessaire. Warden les
refuse avec network:none, direct, fd4 ou sans --image, car dans ces
formes Egress ne pourrait pas prouver leur application.
--image est volontairement une paire référence/digest atomique pour éviter
les combinaisons partielles. Le CLI sépare la référence du suffixe
@sha256:... avant d'appeler maelys_target_spec_create_oci(). La commande
après -- est toujours l'argv invité exact : elle n'est ni l'entrypoint hôte,
ni une commande shell implicite.
Pour une cible OCI, --workspace DIR désigne toujours la source hôte fiable.
--guest-workspace désigne sa destination dans l'invité. --ro ou --rw
contrôle les droits de cette vue et continue de produire une MIR, tandis que le
mapping hôte-invité appartient au plan mécanique et non à la décision MIR.
Dans la première CLI OCI, un chemin relatif de --allow-read ou
--allow-write est résolu sous le workspace hôte puis projeté sous le workspace
invité avec le même suffixe relatif. Un chemin absolu est refusé. Une règle
filesystem JSON ou MIR utilisant une racine hôte absolue fournie par
l'utilisateur est également refusée ; les racines symboliques fiables de
runtime et de répertoire temporaire restent compilées séparément. La CLI
n'expose jamais automatiquement /chemin/hôte comme /chemin/hôte dans
l'image. Une future syntaxe explicite --mount-ro HOST:GUEST /
--mount-rw HOST:GUEST pourra introduire cette autorité ; elle ne fait pas
partie de ce premier jalon. L'API C basse reste disponible pour les mappings
mécaniques revus par l'intégrateur.
Sélection du backend
Sans --backend, le planner décide à partir de la cible, des capacités MIR et
des preuves disponibles :
TargetSpec OCI linux/arm64 ou linux/amd64
│
▼
candidate planner + budget ABI 10
├── apple-container (refus : limites non représentables)
├── apple-container-bubblewrap (VM Apple + cgroup Warden)
├── apple-containerization
├── apple-containerization-bubblewrap
├── krun
└── krun-bubblewrap
│
plateforme + capacités
│
▼
artefact scellé--backend est un override avancé de reproductibilité et de diagnostic :
# Le planner choisit le candidat qui satisfait exactement la MIR.
image='ghcr.io/maelys/tool@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
maelys-warden run --ro --image "$image" -- /usr/bin/tool
# Le driver direct est épinglé ; noyau, init ou Rosetta absent => échec.
maelys-warden run --ro --image "$image" \
--platform linux/amd64 --backend apple-containerization -- /usr/bin/tool
# MicroVM Krun + règles filesystem récursives dans Bubblewrap invité.
maelys-warden run --rw --workspace "$PWD" --image "$image" \
--platform linux/arm64 --backend krun-bubblewrap -- /usr/bin/tool
# Virtualization.framework direct + noyau, init et ext4 Warden.
maelys-warden run --rootfs-only --image "$image" --platform linux/arm64 \
--backend apple-containerization -- /usr/bin/tool
# Même VM directe, avec règles récursives abaissées par Bubblewrap invité.
maelys-warden run --ro --workspace "$PWD" --allow-read . \
--image "$image" --platform linux/arm64 \
--backend apple-containerization-bubblewrap -- /usr/bin/tool
# Service Apple 1.3 + Bubblewrap et cgroup-v2 Warden dans l'invité.
maelys-warden run --ro --image "$image" --platform linux/arm64 \
--backend apple-container-bubblewrap -- /usr/bin/toolIl ne signifie jamais « essayer ce backend puis dégrader ». En 0.40, épingler
apple-container produit toujours un rejet avant RUN, car son workload root
peut modifier le cgroup du service. Épingler apple-container-bubblewrap
sélectionne au contraire le composite ABI 10 : VM dimensionnée par le service,
cgroup workload appliqué et observé par le bootstrap Warden, puis vue
Bubblewrap read-only. Le record physique
conserve les preuves de cette distinction.
Le rejet explique la décision au lieu d'imprimer seulement 0x30000 :
maelys-warden: backend apple-container rejected
Missing required guarantees:
- resource-vm-limits (required by resource budget)
- resource-guest-cgroup (required by resource budget)
Why they are required:
Warden applies the default OCI resource budget:
VM: 2 vCPU, 1024 MiB
workload: 768 MiB, CPU quota 100000/100000,
256 tasks, wall time 600000 ms
apple-container cannot prove all required guarantees.
Capability masks: required=0x00000000000300cf available=0x00000000000040cf missing=0x0000000000030000Les valeurs exactes dépendent de la MIR et des options de la commande ; le mot
default devient caller-supplied lorsqu'une limite a été fournie. Les masques
restent présents pour la corrélation avec les outils, mais les noms et leur
provenance sont la surface destinée à l'utilisateur. Il n'existe pas de
--budget none : toute cible OCI conserve un budget scellé obligatoire.
--rootfs-only est une troisième source de politique inline, réservée aux
cibles OCI. Elle ajoute la racine runtime et /tmp à la MIR, mais aucune règle
workspace, refuse --workspace, --guest-workspace, --allow-read et
--allow-write, puis choisit / comme cwd invité implicite. Elle sert notamment
au candidat direct, dont le vrai NETWORK_NONE interdit toute vue hôte
susceptible d'exposer une socket Unix. --ro et --rw conservent exactement
leur sens de vue /workspace, sans dégradation vers NETWORK_IP_NONE.
Avec apple-containerization, la racine ext4 est immuable : l'image doit donc
déjà contenir le répertoire /workspace lorsque --ro ou --rw est utilisé.
Une image construite avec WORKDIR /workspace satisfait naturellement ce
contrat. --rootfs-only ne crée et ne requiert aucune vue /workspace.
--ephemeral-rw ne rend jamais root.ext4 modifiable. Warden exige le bit
ABI 10 ROOT_EPHEMERAL_WRITE; maelys-init monte un tmpfs privé et borné comme
upper/work d'OverlayFS, puis place les vues utilisateur au-dessus. Après le
reap, l'overlay est démonté et son résultat apparaît dans le receipt v7 :
maelys-warden run --rootfs-only --ephemeral-rw-size 512M \
--image "$image" --platform linux/arm64 \
--receipt run.json -- /usr/bin/tool buildkrun, krun-bubblewrap, apple-containerization et
apple-containerization-bubblewrap annoncent cette
capacité depuis 0.35. Le composite Apple service est sélectionnable pour une
racine read-only, mais refuse --ephemeral-rw plutôt que d'ignorer la demande.
Validations fail-closed
--platformsans--imageest refusé ;--guest-workspaceet--guest-cwdsans--imagesont refusés ;- un
--allow-read/--allow-writeabsolu ou une règle utilisateur à racine hôte absolue est refusé pour une cible OCI ; - référence sans digest, digest malformé ou plateforme inconnue sont refusés avant toute résolution réseau ;
linux/amd64ne retombe jamais surlinux/arm64;--network-frontend fd4est refusé pour les quatre candidats OCI, Apple comme Krun, tant que le broker invité fd4/vsock n'est pas livré : aucun d'eux n'annonceNETWORK_EXTERNAL_CHANNEL;autochoisitproxy;--io isolated-duplexest refusé tant que le canal applicatif fd 3 n'est pas transporté explicitement à travers le driver ;- une cible OCI produit exclusivement v7 ; une cible locale conserve v1 tant qu'un schéma enrichi n'est pas demandé explicitement ;
- aucune disparition de driver, helper, runtime ou preuve après
preparene provoque de resélection auspawn.
maelys-warden run --ro -- codex --model gpt-5.6 --full-auto
maelys-warden run --rw -- npm test -- --runInBand
maelys-warden run --ro -- /bin/sh -c 'printf "%s\n" hello'Dans le deuxième exemple, le premier -- appartient à Warden ; le second est
transmis à npm test.
Sources de policy
Une et une seule source est obligatoire :
maelys-warden run --ro -- COMMAND
maelys-warden run --rw -- COMMAND
maelys-warden run --rootfs-only --image REF@sha256:HEX -- COMMAND
maelys-warden run --policy policy.json -- COMMAND
maelys-warden run --plan policy.mir -- COMMAND| Source | Rôle |
|---|---|
--ro | construit une MIR avec le workspace récursivement lisible |
--rw | construit une MIR avec le workspace récursivement modifiable |
--rootfs-only | construit une MIR OCI sans aucune vue du workspace hôte |
--policy FILE | compile un JSON source v3 strict en MIR canonique en mémoire |
--plan FILE | décode et vérifie une MIR v3 binaire déjà canonique |
Ces trois raccourcis ne sont pas des noms de profil et ne chargent aucun fichier
caché. La façade appelle directement le builder MIR. Ils ajoutent le runtime
minimal en lecture, le répertoire temporaire en écriture, network:none par
défaut et l’héritage du confinement aux descendants. --ro/--rw ajoutent le
workspace ; --rootfs-only l'omet et reste réservé à OCI.
Il n’existe volontairement ni --profile ni --preset. Une policy durable et
nommée est un fichier JSON explicite ; sa forme compilée et reproductible est
un fichier .mir fourni à --plan.
Composition inline
Les options inline ne fonctionnent qu’avec --ro, --rw ou
--rootfs-only. Les options filesystem sont toutefois refusées avec
--rootfs-only, puisqu'elles réintroduiraient précisément la vue hôte omise.
Toutes sont refusées avec --policy et --plan, de sorte qu’une policy externe ou scellée
ne puisse jamais être élargie implicitement.
Filesystem
maelys-warden run --ro \
--allow-read /opt/toolchain \
--allow-write build \
-- make all- un chemin relatif est résolu sous le workspace fiable ;
- un chemin absolu désigne un chemin hôte explicite ;
- la règle est récursive (
scope: tree) ; - la cible doit exister au moment de la résolution de policy ;
- les options sont répétables.
--ro signifie « workspace en lecture seule », pas « aucun octet inscriptible
sur toute la machine » : le répertoire temporaire reste volontairement
inscriptible et --allow-write peut accorder une sous-arborescence précise.
Réseau
maelys-warden run --ro \
--net github.com:443 \
--net registry.npmjs.org:443 \
-- npm ciChaque --net HOST:PORT ajoute une destination TCP à l’allowlist et implique
automatiquement network.mode=mediated. Il n’existe donc pas d’option
redondante --network mediated. Les hôtes sont des noms DNS canoniques ; les
ports doivent être compris entre 1 et 65535.
Sans --net, une policy inline utilise network:none. L’autorisation large
est volontairement bruyante :
maelys-warden run --ro --net direct -- COMMAND--net direct autorise le réseau IP ambiant et ne peut pas être combiné avec
des destinations médiées. Le mot-clé n'est reconnu que lorsqu'il constitue la
valeur entière de l'option. --net direct:443 suit donc la grammaire
HOST:PORT et désigne l'hôte DNS littéral direct, pas le mode réseau direct.
Frontend réseau
--network-frontend ne donne aucune autorisation. Il choisit seulement
l’interface par laquelle une policy médiée est appliquée par Egress.
| Valeur | Client | Chemin |
|---|---|---|
auto | défaut produit | proxy sur Linux et macOS |
proxy | curl, git, npm et outils non modifiés | variables HTTP/SOCKS → Egress |
fd4 | programme Maelys-aware | requête fd 4 → Egress → SCM_RIGHTS |
Exemple explicite, équivalent au défaut pour un outil standard :
maelys-warden run --ro \
--net github.com:443 \
--network-frontend proxy \
-- curl https://github.com/Le frontend est sans effet pour network:none et network:direct. Aucun
échec du médiateur ne bascule vers le réseau direct.
Toutes les options d’exécution
| Option | Effet |
|---|---|
--workspace DIR | racine fiable substituée à workspace dans la MIR ; défaut : répertoire courant |
--backend NAME | sélection exacte du backend, sans fallback vers POSIX |
--network-frontend auto|fd4|proxy | frontend d’une policy médiée ; défaut : auto |
--environment inherit|empty | base d’environnement ; défaut inherit en local et empty en OCI ; inherit est refusé en OCI |
--io captured-stdio|isolated-duplex | choisit entre les flux Unix ordinaires et un canal protocolaire isolé sur le fd 3 ; voir ci-dessous |
--env NAME[=VALUE] | overlay répété ; NAME importe sa valeur depuis l’environnement du CLI sans la placer dans argv ; NAME=VALUE est l’override explicite visible dans argv |
--invocation-id ID | donne une même clé de corrélation au receipt d’exécution et aux receipts Egress |
--emit-mir FILE | écrit avant le spawn la MIR canonique effective, en mode 0600 |
--receipt FILE | écrit après l’attente le receipt global d’exécution JSON, en mode 0600 |
--network-receipts FILE | écrit après l’attente un receipt Egress JSON Lines par connexion |
--relay-bootstrap FILE | remplace explicitement le helper Linux Bubblewrap normalement autodécouvert |
--version | affiche la version |
--help | affiche l’aide |
Modes d’entrée/sortie
--io captured-stdio
C’est le mode par défaut et celui des commandes ordinaires : shell, compilateur,
curl, git, npm, agent en ligne de commande, etc. Warden crée trois pipes
séparés et les relaie sans changer leur signification :
terminal / appelant workload confiné
stdin ──────────────────────────► fd 0
stdout ◄────────────────────────── fd 1
stderr ◄────────────────────────── fd 2Par exemple, le texte fourni sur stdin atteint cat et sa sortie revient sur
le stdout de Warden :
printf 'bonjour\n' |
maelys-warden run --ro --io captured-stdio -- /bin/catLe résultat est :
bonjourL’option peut être omise puisque ce mode est le défaut :
maelys-warden run --rw -- npm test -- --runInBandIci, le second -- est un argument de npm test, et ses sorties restent des
sorties terminal ordinaires.
--io isolated-duplex
Ce mode est destiné à un enfant conçu pour parler un protocole sur le fd 3,
par exemple un provider MCP ou maelys-code-runner. Executor crée un
socketpair full-duplex et installe une extrémité exactement sur le fd 3 de
l’enfant. Le CLI transporte des octets opaques sans interpréter JSON, MCP ou
le protocole applicatif :
appelant maelys-warden enfant
stdin ─── trames requête ──────► relais ────────────────────► fd 3
stdout ◄── trames réponse ─────── relais ◄──────────────────── fd 3
stderr ◄── diagnostics ─────────────────────────────────────── fd 2
fd 0 = /dev/null
fd 1 = diagnosticsLe stdout de l’enfant est redirigé vers ses diagnostics afin qu’un printf
accidentel ne puisse pas corrompre le protocole. Le stdout du CLI reste, lui,
réservé aux octets lus sur le fd 3. Une fermeture de stdin produit une
demi-fermeture : l’enfant peut encore envoyer sa dernière réponse.
Exemple minimal d’un enfant conscient du contrat fd 3 :
printf 'ping\n' |
maelys-warden run --ro --io isolated-duplex -- \
/bin/sh -c \
'IFS= read -r frame <&3; printf "reply:%s\n" "$frame" >&3'Le stdout protocolaire du CLI contient :
reply:pingUne commande standard qui ne lit que le fd 0, telle que cat, ne doit pas être
lancée dans ce mode. isolated-duplex est également indépendant du frontend
réseau fd4 : le fd 3 transporte le protocole applicatif ; le fd 4 demande à
Egress une connexion réseau autorisée.
Combiner --io et --network-frontend
Les deux options répondent à des questions différentes :
--iodéfinit comment l’appelant dialogue avec le workload ;--network-frontenddéfinit comment le workload obtient une connexion autorisée par Egress.
Elles sont donc orthogonales. --network-frontend fd4 ne remplace ni stdin,
ni stdout, ni le canal protocolaire fd 3.
--io | Frontend réseau | fd visibles par le workload | Usage typique |
|---|---|---|---|
captured-stdio | proxy/auto | 0 stdin, 1 stdout, 2 stderr | curl, git, npm, Codex non modifié |
captured-stdio | fd4 | 0/1/2 ordinaires, 3 neutralisé, 4 contrôle Egress | CLI Maelys-aware qui affiche normalement sur stdout |
isolated-duplex | proxy/auto | 0 neutralisé, 1 diagnostics, 2 diagnostics, 3 protocole | worker protocolaire utilisant les bibliothèques HTTP/SOCKS standards |
isolated-duplex | fd4 | 0 neutralisé, 1/2 diagnostics, 3 protocole, 4 contrôle Egress | worker entièrement Maelys-aware |
Le frontend fd4 n’est installé que pour une policy network:mediated. Avec
network:none ou --net direct, il n’existe pas de canal réseau fd 4.
captured-stdio + fd4
L’exemple livré example-fd4-client demande une socket à Egress sur le fd 4,
mais copie la réponse HTTP sur son stdout ordinaire :
make examples
maelys-warden run --ro \
--net example.com:80 \
--io captured-stdio \
--network-frontend fd4 \
-- build/release/bin/example-fd4-client example.com 80Le chemin fonctionnel est :
terminal ◄── réponse HTTP ── stdout fd 1 du client
client
│ demande example.com:80 sur fd 4
▼
broker fd4 → Egress
│ SCM_RIGHTS
▼
nouveau stream connectéLe code du client appelle
maelys_netclient_connect(4, ...). Egress vérifie l’allowlist et lui transfère
un nouveau stream connecté avec SCM_RIGHTS; le fd 4 reste le canal de contrôle.
isolated-duplex + fd4
Cette combinaison réserve le fd 3 aux requêtes/réponses applicatives et le fd 4
aux demandes réseau. L’exemple example-fd3-fd4-worker attend la commande
fetch sur fd 3, demande example.com:80 à Egress sur fd 4, puis renvoie la
réponse HTTP sur fd 3 :
make examples
printf 'fetch\n' |
maelys-warden run --ro \
--net example.com:80 \
--io isolated-duplex \
--network-frontend fd4 \
-- build/release/bin/example-fd3-fd4-worker example.com 80appelant
│ "fetch" plan réseau scellé
▼ │
Warden stdin ──► enfant fd 3 ▼
│ broker fd4 → Egress
│ demande sur fd 4 │
│────────────────────────►│ vérifie example.com:80
│◄────────────────────────│ SCM_RIGHTS(stream)
│
├── HTTP sur le stream reçu
│
Warden stdout ◄─────┘ réponse HTTP sur fd 3Le code du worker illustre cette séparation.
Le descripteur connecté rendu par SCM_RIGHTS est un troisième objet, distinct
des fd 3 et 4, et doit être fermé par le workload. Les appels qui partagent le
canal de contrôle fd 4 doivent être sérialisés.
Corrélation avec --invocation-id
--invocation-id ID attribue une identité logique à une exécution. Warden la
copie dans le receipt global et Egress dans chaque receipt de connexion. Cela
permet à un orchestrateur ou à une CI de joindre les deux fichiers sans se
fonder sur un PID, un nom de fichier ou une proximité temporelle.
run_id='ci-4821-attempt-2'
maelys-warden run --ro \
--net github.com:443 \
--invocation-id "$run_id" \
--receipt execution.json \
--network-receipts connections.jsonl \
-- curl https://github.com/On retrouve alors la même valeur dans les deux sorties :
jq -r '.invocationId' execution.json
jq -r '.invocationId' connections.jsonlL’identifiant contient de 1 à 256 octets et aucun caractère de contrôle. Ce n’est ni un secret, ni un droit, ni une preuve d’identité : c’est seulement une clé de corrélation. Lors d’une exécution médiée sans valeur explicite, l’adaptateur Egress génère un identifiant local ; fournir le sien reste nécessaire pour corréler l’exécution avec un système externe.
MIR et receipts
Les trois options de sortie correspondent à trois moments et trois granularités différents :
policy inline / JSON / MIR
│
▼
compilation canonique
│
├──► --emit-mir avant le spawn, 1 policy binaire
▼
spawn
│
├──► Egress observe 0..N connexions
▼
wait
│
├──► --receipt 1 résultat global JSON
└──► --network-receipts 0..N résultats réseau JSONL--emit-mir FILE
Cette option sérialise la policy effective, validée et canonique sous sa
forme MIR v3 binaire. Le fichier est écrit atomiquement en mode 0600 avant le
spawn. Si l’écriture échoue, le workload n’est pas lancé.
maelys-warden run --ro \
--allow-read /opt/toolchain \
--net github.com:443 \
--emit-mir effective.mir \
-- /usr/bin/trueeffective.mir contient les décisions de policy : règles filesystem, réseau,
médiateur attendu et confinement de l’arbre de processus. Il ne contient ni le
résultat du workload, ni le backend finalement sélectionné, ni ses observations.
Comme il est canonique, on peut le hasher, l’archiver et le rejouer :
shasum -a 256 effective.mir
maelys-warden run --plan effective.mir -- /usr/bin/trueUne MIR produite par --ro ou --rw inclut notamment le droit de lire
l’exécutable exact. Il faut donc la considérer comme le plan de policy du
workload prévu, pas comme une policy universelle pour n’importe quelle commande.
--receipt FILE
Cette option écrit un objet JSON global après l’attente du workload. La CLI
locale produit actuellement maelys.execution-receipt/v1, avec :
- digest MIR/décision et digest du plan mécanique ;
- backend sélectionné, version, capacités requises et disponibles ;
- mode, frontend et médiateur réseau ;
- début, tentative d'arrêt forcé, complétion, code de sortie ou signal ;
invocationId, lorsqu’il existe.
Le receipt v2 reste le format gelé du chemin RunSpec ABI 7. Executor ABI 8 produit les preuves nécessaires aux receipts v3/v4 : cible, rapport de candidats, capacités exactes et couches VM, sandbox et traduction. v4 ajoute la fermeture runtime Krun attestée. La CLI locale émet v1 par défaut. Les receipts v3/v4/v5 restent gelés pour les exécutions historiques. Executor ABI 10 et Warden 0.38 ajoutent v6 : budget VM/workload demandé et appliqué, observations cgroup-v2 et preuve de cleanup. Warden 0.39 ajoute v7 sans modifier v6 : quotas Egress demandés/appliqués/observés et preuve du failsafe contrôleur Krun lorsqu'il existe.
maelys-warden run --ro \
--invocation-id local-build-7 \
--receipt build.execution.json \
-- make test
jq '{invocationId, backend: .backend.name, outcome}' \
build.execution.jsonCette requête jq vise le receipt v1, qui reste le format par défaut d'une
exécution locale. Avec un ancien receipt v3/v4/v5/v6 ou le receipt OCI v7, le backend retenu se lit
dans .selection.candidate et la chaîne d'isolation détaillée dans .layers,
énumérée du plus extérieur au plus intérieur : pour les candidats Krun,
seatbelt-host, puis krun, puis bubblewrap pour le seul composite.
Le receipt décrit l’exécution entière. Il n’est disponible avec son issue
finale qu’après wait; il ne peut donc pas être écrit avant le spawn. C’est un
document d’observation auditable, pas à lui seul une attestation signée.
Le schéma local est dans
execution-receipt-v1.schema.json ;
le schéma v2 gelé documente ABI 7 ;
le schéma v3 gelé documente ABI 8 ;
le schéma v4 ajoute la fermeture
dynamique Krun (variant, features, components, closureEvidence). Depuis
0.33.0, les couches Krun et Containerization direct ajoutent aussi
guestKernel : contrat commun guest-kernel/v1, format raw-arm64, chargeur
VMM et preuve SHA-256. Le champ reste optionnel dans le schéma afin que les
receipts v4 émis avant 0.33 restent vérifiables.
Le schéma v5 déclare le registre
Executor ABI 9. Sa section root atteste read-only ou ephemeral-write, le
stockage tmpfs, les bornes demandée/appliquée et le cleanup. Une couche
ephemeral-root-overlay contribue exactement la capacité correspondante ;
outcome.rusage transporte les compteurs directs de wait4. Les octets
root.scratch.requestedBytes/appliedBytes bornent exclusivement le scratch
de la racine overlay. Ils ne représentent pas un quota total d'écriture et ne
couvrent pas une vue /workspace RW, qui est une ressource hôte séparée avec
ses propres bornes.
Le schéma v6 déclare Executor ABI
10. resources.vm et resources.workload séparent les valeurs requested,
applied et observed. Le plafond mémoire, le quota CPU, pids.max et la
deadline couvrent le cgroup du workload ; ils ne comptabilisent ni le PID 1,
ni le relay Egress, ni une vue VirtioFS /workspace RW.
Le schéma v7 ajoute
resources.network et resources.controller. Le premier compte seulement les
octets de payload admis et relayés par l'Egress de l'exécution. En l'absence de
médiation, ses valeurs appliquées sont zéro et sa source vaut
not-applicable. Le second distingue le worker VMM Krun d'un service VMM
externe, mais reste applied: false/not-applicable dans les deux cas. Les
tests physiques ont invalidé RLIMIT_CPU comme mécanisme compatible avec
libkrun 1.19.4 ; Warden n'atteste donc aucune protection hôte non prouvée.
ABI 8 ne réinterprète pas v2 : v2 garde une seule couche/candidat. La chaîne
VM, Bubblewrap et Rosetta apparaît dans v3/v4. --receipt continue à produire
v1 pour une cible locale tant que l'appelant ne choisit pas explicitement un
schéma enrichi.
--network-receipts FILE
Cette option écrit du JSON Lines : chaque ligne est un objet JSON autonome pour
une tentative de connexion traitée par Egress. Une ligne contient notamment la
destination, le résultat, les durées, les compteurs d’octets, le digest de
policy, le principal, la génération de policy et invocationId.
maelys-warden run --ro \
--net github.com:443 \
--invocation-id download-19 \
--network-receipts download.connections.jsonl \
-- curl https://github.com/
jq -c '{host, port, result, bytesFromClient, bytesToClient}' \
download.connections.jsonlLe fichier peut contenir zéro ligne si aucune connexion médiée n’a eu lieu, et
plusieurs lignes si le workload ouvre plusieurs connexions. Il n’apporte pas
d’observation utile pour network:none ou --net direct, puisque ces modes ne
font pas passer les connexions par Egress.
Les trois fichiers sont facultatifs et ne sont jamais mélangés au stdout du
workload. Les écritures du CLI sont atomiques et utilisent le mode 0600.
Exemple combiné :
maelys-warden run \
--policy examples/policies/github-mediated.json \
--invocation-id release-2026-08-26 \
--emit-mir effective.mir \
--receipt execution.json \
--network-receipts connections.jsonl \
-- curl https://github.com/effective.mir est écrit avant le spawn : il peut être validé, hashé ou
réutilisé ensuite avec :
maelys-warden run --plan effective.mir -- curl https://github.com/--relay-bootstrap FILE
maelys-guest-supervisor est un petit binaire exécutable livré dans
libexec. L'option avancée ci-dessous configure son rôle de relay pour la
combinaison suivante :
Linux + backend Bubblewrap + policy réseau médiée + frontend proxyDans le namespace réseau privé Bubblewrap, une instance du helper démarre le relay L4 loopback, attend sa readiness, puis lance le workload. Le fichier est partagé par l’installation, mais chaque exécution possède ses propres processus bootstrap/relay, son socket privé et son instance Egress. Le relay ne décide aucune permission : Egress authentifie et applique l’allowlist.
Le CLI le découvre normalement, notamment à côté de l’installation puis dans
son répertoire libexec; l’utilisateur n’a rien à fournir. L’override est
réservé aux bundles hermétiques, tests, conteneurs ou installations sous un
préfixe non conventionnel :
maelys-warden run --ro \
--backend bubblewrap \
--net github.com:443 \
--network-frontend proxy \
--relay-bootstrap /opt/maelys/libexec/maelys-guest-supervisor \
-- curl https://github.com/Le chemin explicite doit être absolu, canonique et désigner un fichier régulier
exécutable fiable. Avec Seatbelt et une policy médiée/proxy, cette option est
refusée : Seatbelt rejoint directement le port loopback privé de Egress. Elle
n'est pas consommée par le frontend fd4, network:none ou --net direct :
la validation la refuse dans ces combinaisons au lieu de l'ignorer. Pour une
cible OCI médiée, elle remplace explicitement le relay du bundle installé ;
le chemin reste donc une autorité hôte avancée. Une erreur de découverte ou de readiness interdit le
lancement du workload ; il n’existe aucun fallback réseau ambiant.
Le nom de l'option reste centré sur ce rôle réseau. Le même fichier est aussi
monté automatiquement comme superviseur ABI 10 du composite Apple Container ;
ce second usage ne se configure jamais avec --relay-bootstrap.
Codes de sortie
- sortie normale : code du workload ;
- terminaison par signal :
128 + signal; 64: ligne de commande invalide ;70: préparation, confinement ou supervision impossible ;74: workload réussi mais écriture d’un receipt impossible ;127peut provenir du workload si sonexecinterne échoue.