Instruction — la disposition sur disque d’un bundle, et l’emplacement de l’octroi
Objet. Le dernier manque d’ADR-010 : le mécanisme d’ADR-150 est complet et prouvé — manifeste, octroi, signature, arbitrage, plafond d’image, pose de la cage — mais rien ne relie un
.airappsur disque àspec.granted.AppManifest::read(reader)prend un lecteur, jamais un chemin. C’est l’incrément 6 de la spec couche 2.TRANCHÉ le 2026-08-12 (BDFL). Les trois questions posées — nom et emplacement du manifeste dans le bundle, chemin de l’octroi hors du bundle, sort de
.airservice— sont tranchées telles que proposées ci-dessous. Sous-choix arbitrés dans la foulée : anglais sur le disque (§7.1),/var/lib/air/comme racine des bundles installés (§7.2),0600pour les octrois (§7.3).Rien n’est encore implémenté. Cette note est ce qu’il faut avoir lu avant d’écrire le code.
1. Ce que le code décide déjà, et qu’il ne faut pas re-trancher
La disposition est plus contrainte qu’il n’y paraît : quatre décisions déjà prises la bornent, et deux d’entre elles ne sont écrites nulle part ailleurs que dans un schéma.
- La racine est un répertoire, et le manifeste ne peut désigner que dedans.
Executable.pathest relatif à la racine du bundle, et le lecteur refuse l’absolu comme..— « un manifeste qui désignerait/usr/bin/shou../../autre-bundleferait porter à l’octroi d’une application le lancement d’un binaire qui n’est pas le sien » (air-config-bundle.capnp). - La signature est un artefact séparé, domaine
air.signature,subject = manifest. Signer le manifeste couvre les binaires par transitivité (chacun y porte son SHA-256) — à une condition que le schéma dit en toutes lettres et qu’il faut répéter ici : un exécutable absent du manifeste n’est couvert par rien. - L’octroi vit hors du bundle, sur la partition inscriptible, émis à l’installation,
indexé par
bundleId(ADR-150 D1, spec couche 2 §5ter.6). - La convention de placement existe déjà et est tenue partout dans le dépôt :
/etc/air/…derrièreair-admin-gate(DEFAULT_BASE_DIR), extension.aircfgpour tout artefact sous enveloppeAIRCFGv1.
2. La disposition
<racine>/org.exemple.editeur/
├── manifest.aircfg artefact du domaine air.manifest
├── manifest.signature.aircfg artefact du domaine air.signature (subject = manifest)
├── executables/ les binaires déclarés au manifeste
├── libraries/ bibliothèques embarquées
└── resources/ données en lecture seule
Ni bin/ ni lib/. ADR-029 refuse l’abréviation sur toute surface inventée par Air, et
celle-ci en est une : un développeur la lira dans chaque bundle qu’il construira. La
familiarité de bin vient d’Unix, pas de nous ; l’invoquer serait exactement l’argument
qu’ADR-029 écarte.
Trois racines, choisies par l’installateur :
| Racine | Contenu | Qui écrit |
|---|---|---|
/usr/lib/air/bundles/ | ce que l’image porte | personne — verity |
/var/lib/air/applications/ | les .airapp installés | la porte d’administration seule |
/var/lib/air/services/ | les .airservice installés | la porte d’administration, privilégiée |
3. Le répertoire ne porte pas le suffixe .airapp
Le répertoire installé s’appelle <bundleId>, sans suffixe. La raison n’est pas esthétique :
elle est écrite dans le schéma, à propos du champ kind.
«
.airappou.airservice. Le second ouvre un modèle d’entitlements étendu et une installation privilégiée ; le distinguer dans l’artefact évite qu’il se déduise du chemin d’installation, qui est plus facile à influencer. »
Nommer le répertoire <bundleId>.airservice rouvrirait précisément la tentation que ce
commentaire referme — un lecteur pressé finirait par lire le suffixe plutôt que l’artefact.
La distinction est portée par la racine, que choisit la porte d’administration, et par
kind, que porte l’artefact signé. Le chemin n’entre jamais dans la décision.
Le suffixe .airapp reste le nom de la forme de distribution — l’archive ou le
répertoire qu’un développeur produit et qu’un utilisateur télécharge. L’installation en
dépose le contenu sous la racine. Les deux formes n’ont pas à coïncider, ADR-150 D1
voyant de toute façon une mise à jour comme une désinstallation suivie d’une installation.
4. L’octroi
/etc/air/grants/<bundleId>.aircfg 0600 root:root
/etc/air/grants/<bundleId>.signature.aircfg 0600 root:root
/etc/air parce que c’est la partition inscriptible derrière la porte d’administration.
L’étude de placement (§3.2) classerait l’octroi en « critique — dans l’image », mais
ADR-150 §5ter.6 a déjà tranché l’objection : l’y mettre ferait d’une installation
d’application une image nouvelle. Ce qui le protège à la place est la composition de
trois choses déjà décidées — signé par la machine, écrivable par la seule porte
d’administration, et plafonné par l’enveloppe de l’image à l’étape 3 de l’arbitrage.
0600, et c’est la partie qui mérite d’être justifiée. L’enveloppe air_signed_default
accorde la lecture de tout /etc/air à n’importe quelle application signée
(ceiling.rs). Landlock ne fait que restreindre : il n’ouvre rien que le DAC ferme, mais il
ne ferme rien non plus que le DAC laisse ouvert. Le seul rempart restant est donc le mode du
fichier.
Un octroi n’est pas un secret — il énonce ce que la machine autorise, pas comment y accéder.
Mais l’ensemble des octrois est la carte de ce que la machine autorise, et ADR-150 D4 a
rendu le refus muet précisément pour ne pas offrir d’oracle d’énumération. Un ls /etc/air/grants/ lisible par toute application rendrait D4 sans objet. Rien de non-root n’en
a besoin : le lanceur et la porte d’administration sont root l’un et l’autre.
Coût assumé, et il faut le dire : aucun outil d’inspection non privilégié ne pourra montrer à un utilisateur ce que ses applications ont le droit de faire. Cet outil n’existe pas encore ; le jour où il existera, il passera par un service root qui répond à des questions, pas par un droit de lecture sur le répertoire.
5. .airservice partage la disposition — entièrement
Il ne diffère que par trois choses, dont aucune n’est un format : kind dans l’artefact,
sa racine d’installation, et le fait que son installation exige la porte privilégiée.
Écrire une seconde disposition donnerait deux lecteurs pour un seul schéma, donc deux
occasions de diverger au voisinage d’une décision de privilèges — ce que le Principe 11
cherche à éviter. Et ADR-010 ne demande pas autre chose : il réclame pour .airservice un
« modèle d’entitlements étendu », c’est-à-dire un contenu plus large, jamais un autre
contenant.
6. Ce que la disposition referme — l’écart EXECUTE
C’est le point le plus important de cette note, et il n’a pas été trouvé en concevant la disposition mais en lisant le code pour la concevoir.
6.1 Le constat
ceiling.rs annonce, pour les deux enveloppes par défaut :
« aucune exécution nulle part — même sur
/usr. Un binaire étranger tourne ; il ne lance pas d’autres binaires. C’est ce qui casse la chaîne classique “télécharger puis exécuter”. »
Cette propriété n’est pas appliquée. AirLandlockPolicy::handled_access()
(crates/air-sandbox/src/landlock.rs:325) construit le masque handled_access_fs par
union des droits des règles présentes. Or aucune enveloppe n’accorde execute, donc
aucune règle de cage ne porte EXECUTE, donc EXECUTE n’est pas géré — et Landlock ne
borne que ce qu’on lui déclare géré. La catégorie seccomp Exec étant accordée d’office
(syscall_categories_for), rien ne s’oppose plus à un execve.
Une application confinée peut donc écrire un binaire dans /var/lib/<bundleId> — que
l’enveloppe lui accorde en écriture — et l’exécuter.
Ce que ce n’est pas : une élévation de privilèges. La cage survit à l’execve
(no_new_privs, seccomp et Landlock sont hérités) ; le binaire tombe dans la même cage.
Ce que c’est : la négation du seuil d’exécution — du code non vérifié s’exécute, ce
qu’ADR-046 D6 interdit, et la chaîne « télécharger puis exécuter » n’est pas cassée.
Statut de ce constat : lu dans le code, pas encore démontré par un test. Le premier travail de l’incrément est de l’écrire, et de vérifier qu’il tombe une fois la règle du §6.2 posée. Un constat non reproduit n’est pas un constat — le dépôt s’est déjà fait prendre deux fois à publier un diagnostic plausible et faux.
6.2 La règle d’office, et pourquoi elle n’est pas un octroi
Le lanceur ajoute à la cage, d’office, une règle Landlock
EXECUTEpar exécutable déclaré au manifeste, résolu sous la racine du bundle — plus la lecture de la racine. Ni le manifeste ni l’octroi n’ont à la demander.
C’est le pendant exact de SyscallCategory::Exec, déjà accordé d’office pour une raison
strictement structurelle : une cage posée du dehors s’installe à l’instant qui précède
l’execve du service, et un profil qui interdirait execve tuerait tout service sur son
propre démarrage. Même raisonnement ici : un bundle qui ne peut pas exécuter son propre
binaire n’est pas confiné, il est mort. Ce n’est pas une permission accordée, c’est ce sans
quoi la question ne se pose pas.
Et l’effet de bord est la propriété recherchée. Dès qu’une règle porte EXECUTE,
EXECUTE entre dans handled_access_fs — et tout le reste du système de fichiers cesse
d’être exécutable. La propriété que ceiling.rs annonçait devient vraie, sans qu’aucune
enveloppe change.
Par exécutable plutôt que par répertoire. Une règle sur executables/ serait plus simple
et plus faible : un binaire déposé après coup dans ce répertoire s’exécuterait. Le manifeste
lie déjà chaque exécutable à son SHA-256 ; poser une règle par fichier déclaré rend
exécutable exactement ce qui est signé, et rien d’autre. Coût : un O_PATH par exécutable,
ouvert dans le parent comme le fait déjà AirPreparedCage::prepare.
6.3 Ce que la mise en œuvre a appris — l’interprète ELF
Écrit après coup, le 2026-08-12, parce que la conception du §6.2 était incomplète et que seule la mesure l’a montré.
Nommer le programme ne suffit pas à un binaire lié dynamiquement. Le noyau ouvre aussi
son interprète ELF (open_exec()), donc sous le contrôle de EXECUTE : un /bin/sh
dont seul le chemin est nommé voit son execve refusé (EACCES), et l’enfant sort 127
sans avoir rien exécuté.
Mesuré sur onze montages, dont les trois qui tranchent :
| Politique | Programme | Résultat |
|---|---|---|
EXECUTE sur /usr/bin/dash seul | /usr/bin/dash | refusé (EACCES) |
EXECUTE sur /usr/bin/dash + son interprète | /usr/bin/dash | s’exécute |
EXECUTE sur /bin/busybox seul (statique) | /bin/busybox | s’exécute (puis tué par seccomp — un autre sujet) |
Ce n’est pas un défaut de la conception, c’est ADR-046 D6 appliqué un cran plus tôt.
Les binaires *-linux-air sont statiques : ils n’ont pas d’interprète, et la règle sur le
seul programme leur suffit — la troisième ligne du tableau le prouve. Un binaire glibc, lui,
ne meurt plus de son premier syscall hors socle : il ne démarre pas. Le seuil d’exécution
passe du profil seccomp au refus de Landlock, ce qui est plus tôt et plus net.
Conséquence à porter : un service transitoire encore lié à la glibc devra nommer son interprète pour tourner sous un octroi. La nécessité est visible, datée, et disparaît avec le portage — exactement le régime voulu pour tout ce qui est transitoire. Aucun mécanisme n’est écrit pour l’instant : le premier service transitoire lancé sous octroi le réclamera, et c’est à ce moment-là qu’il faudra choisir entre nommer l’interprète et achever le portage.
7. Les sous-choix, et ce qui les a tranchés
7.1 Anglais sur le disque
Décision BDFL du 2026-08-12 : « on reste et on doit rester en anglais ». Donc grants/
et non octrois/, executables/ et non executables francisé, manifest.aircfg et non
manifeste.aircfg.
C’est la règle du dépôt et elle vaut au-delà de cette note : la documentation est en
français, les noms sur le disque — comme les identifiants de code, les commandes et les
domaines de schéma — sont en anglais. /etc/air/keystore/hostkeys.aircfg,
/etc/air/passwd.aircfg, air.grant : l’usage existant était déjà celui-là, il est
désormais explicite.
7.2 /var/lib/air/ comme racine des bundles installés
Debian mettrait plutôt /opt. /var/lib est retenu parce que c’est déjà là que l’enveloppe
place les données d’application (/var/lib/{bundleId}, ceiling.rs) : une seule racine
inscriptible à connaître plutôt que deux.
7.3 0600 pour les octrois
Justifié au §4. Le mode ferme l’énumération et interdit toute inspection non privilégiée.
8. Ce que cette disposition retire à ADR-010
ADR-010 dit le bundle « autonome et déplaçable ». Dans cette disposition, il ne s’exécute que depuis une racine que la porte d’administration contrôle.
La raison est qu’un répertoire à la fois inscriptible par l’utilisateur et exécutable
annulerait tout le reste — c’est la forme même de l’attaque que le §6 cherche à fermer. Et la
politique de montage l’a déjà tranché de son côté : les supports amovibles sont montés
nosuid,nodev,noexec (étude de placement §3.4), si bien qu’un .airapp posé sur une clé USB
ne s’exécute de toute façon pas.
Le bundle reste déplaçable comme forme de distribution ; il n’est pas exécutable en place. C’est un rétrécissement réel de la Décision d’ADR-010, et il est acquis (BDFL, 2026-08-12). ADR-010 étant fondateur mais en phase de design pré-ouverture publique, l’édition directe est autorisée (ADR-015) : la phrase mérite d’être amendée dans l’ADR plutôt que corrigée seulement ici.
9. Ce qu’il reste à écrire
| # | Objet | Dépend de |
|---|---|---|
EXECUTE du §6.1 | — | |
BundleLoader::load. Son périmètre s’est réduit à l’instruction : tout le décodage existait déjà dans arbitrate (enveloppe, garde de domaine, Cap’n Proto), et ArbitrationInputs ne demande que quatre tranches d’octets. Le chargeur ne fait donc que des entrées-sorties, et aucune décision | — | |
bundleId | — | |
EXECUTE d’officespec.program : le lanceur n’a encore qu’une seule vérité sur ce qu’il exécute. Elle passera aux Executable du manifeste avec la tâche 2 | — | |
spec.granted--bundle <identifiant> + BundleActivator. Trois sous-choix ont dû être tranchés pour y arriver, §10 | — | |
AirMachineKey dans air-keystore, générée au premier démarrage, graine 0600 + clé publique 0640 écrites d’un seul geste | — | |
bundle::install::issue_grant. Établit QUI (signature du manifeste) avant de décider QUOI, refuse ce que l’administrateur nomme sans que le manifeste l’ait demandé, émet et signe avec la clé de la machine | — | |
air-bundle show/grant/revoke, gate d’ADR-101 sur les seules mutations, octroi écrit en 0600 atomiquement, révocation par retrait. La copie de l’arborescence n’en fait PAS partie — §10.7 | — | |
| — | ||
AirFileTrust dans air-filesystem. Le lanceur supprime son type (couche 2, libre) ; le magasin garde le sien comme surface publique et délègue (couche 1 scellée — le renommer serait une rupture pour un gain cosmétique). Les bits de garde n’existent plus qu’à un seul endroit | — |
Pourquoi la tâche 8 existe. Le chargeur porte sa propre garde de confiance
(ArtifactTrust), qui décalque celle d’air-keystore (AirStoreTrust) sans la réutiliser :
celle du magasin est paramétrée par une visibilité propre au magasin de clés, et un lanceur
n’a pas à dépendre du gestionnaire de clés pour ouvrir un fichier. La duplication est une
dette, et elle est nommée dans le code — une garde de sécurité écrite deux fois est une
garde qui divergera. Le dépôt s’est déjà fait prendre : la protection de lecture des
artefacts avait été écrite là où le besoin s’était fait sentir plutôt que là où le domaine
vit, et hostkeys.aircfg s’était retrouvé gardé moins bien que known_hosts.
Le service désigne son bundle par bundleId, jamais par un chemin. Le lanceur résout le
répertoire par convention à partir des trois racines du §2. Une configuration qui nommerait
le chemin laisserait un administrateur pointer un service vers un autre répertoire — donc
séparer l’identité du bundle de son emplacement, alors que toute la chaîne
d’arbitrage suppose qu’ils désignent la même chose.
Pas d’installation par utilisateur dans cet incrément. Un bundle installé dans un répertoire inscriptible par son propre utilisateur serait exécutable et modifiable par lui : le §6 ne tiendrait plus. Si le besoin se présente, il se traitera par un octroi et une racine dédiés, jamais en desserrant celle-ci.
10. Trois sous-choix qu’il a fallu trancher pour câbler l’activation
Aucun n’était dans les trois questions posées au BDFL ; tous trois étaient forcés par la tâche 5 — sans eux, aucun service ne peut être arbitré. Ils sont donc pris, écrits et faciles à reprendre.
10.1 La clé de la machine vit dans le magasin, en /etc/air/keystore/machine_public_key
Cette section a d’abord dit le contraire, et elle avait tort. Elle plaçait la clé dans l’image, « du côté couvert par verity ». C’était inimplémentable : [ADR-121] décrit une image anonyme qui génère son matériel de clé au premier démarrage. Une paire propre à la machine ne peut pas avoir sa partie publique dans une image en lecture seule, partagée par toutes les machines. Corrigé le 2026-08-12, décision BDFL.
La paire est générée au premier démarrage, comme la clé d’hôte d’ADR-121, et rangée dans
le magasin d’[ADR-108] : la partie privée en artefact secret (0600 root), la partie
publique en fichier de clé brut que le lanceur est seul à lire.
Sans enveloppe AIRCFGv1, et c’est délibéré : une clé publique Ed25519 fait 32 octets,
ni plus ni moins. Un fichier de taille fixe n’a besoin d’aucun analyseur — on compare une
longueur. C’est le Principe 11 à son état le plus pur ; l’enveloppe transporterait une
disposition qui n’a pas de variante. L’absence de suffixe .aircfg est le signal que ce
n’est pas un artefact de configuration mais du matériel de clé. La longueur est exacte,
jamais maximale : plus court serait tronqué, plus long porterait quelque chose qu’on n’a pas
regardé.
Deux fichiers plutôt qu’un, la partie publique séparée de la privée : le lanceur n’a besoin que de vérifier, jamais de signer. Un lanceur qui ouvrirait l’artefact secret pour en dériver la clé publique pourrait, sur un défaut, laisser fuir de quoi émettre des octrois. Il est root et pourrait de toute façon lire ce fichier — la séparation ne protège donc pas contre lui, elle protège contre ses bogues.
Ce que la signature d’octroi protège, et ce qu’elle ne protège pas
Il faut le dire, parce que la première rédaction le laissait croire plus fort qu’il ne l’est.
| Menace | Protégé ? |
|---|---|
| Un compte non privilégié fabrique un octroi | oui — il ne peut pas écrire le magasin |
| Un octroi valide transporté depuis une autre machine | oui — l’autre machine a une autre clé |
| Un manifeste réécrit pour demander davantage | oui — l’empreinte lie l’octroi à ce manifeste |
root fabrique un octroi | non — qui peut écrire le magasin peut signer |
Ce n’est pas un renoncement : c’est la position déjà actée par l’étude de placement — « si l’attaquant est root, une signature locale ne l’aurait pas arrêté non plus ». Ce qui protégerait vraiment est un ancrage matériel, qu’Air n’a pas encore. Le jour où il existera, c’est cette ligne du tableau qui changera, et elle seule.
10.2 Les trois racines sont cherchées dans l’ordre, l’image d’abord
Un bundle porté par l’image est couvert par verity ; une installation ultérieure ne doit pas pouvoir le masquer. « Porte ce bundle » se constate sur le manifeste, pas sur le répertoire — sinon un répertoire vide, créé par erreur ou à dessein, masquerait l’installation valide d’une racine suivante. Les deux propriétés sont tenues par un test chacune.
Le genre déclaré doit concorder avec la racine où le bundle a été trouvé. Ce n’est pas le chemin qui décide du genre — le schéma est explicite là-dessus. Mais une incohérence est soit une erreur d’installation, soit une tentative : refuser est fail-closed et n’accorde jamais rien que l’artefact n’ait dit.
10.3 Tout bundle reçoit l’enveloppe étrangère, la plus étroite
ADR-046 A2 veut que la provenance décide de la largeur. Reconnaître une signature Air suppose de savoir quelles clés sont celles d’Air — un registre qui n’existe pas.
Tant qu’il n’existe pas, tout le monde reçoit foreign_default(). C’est le choix
fail-closed : traiter tout le monde en signé Air accorderait à un inconnu ce qui est réservé
à ce qu’on a vérifié, tandis que l’inverse ne fait que restreindre. Le mur est tenu par un
test — le jour où il tombera, c’est que quelqu’un aura élargi sans le registre.
Conséquence pratique à connaître : un bundle ne peut, pour l’instant, obtenir que la lecture
de /usr et son propre /var/lib/{bundleId}. Ni réseau, ni lecture de /etc/air.
10.4 Qui décide ce qui est accordé à l’installation
Décision BDFL du 2026-08-12 : l’administrateur, par un acte explicite.
issue() documentait déjà que proposed est « ce que l’administrateur (ou la politique
locale) propose d’accorder » — ni l’un ni l’autre n’existait. Il n’y aura pas de politique
locale dans un premier temps, et pas d’octroi automatique.
L’installation montre ce que le manifeste demande et n’accorde que ce que l’administrateur nomme. Rien d’implicite, pas de « tout accorder » par défaut.
Pourquoi ce n’est pas une lourdeur mais le sujet même. Accorder automatiquement l’intersection demande ∩ enveloppe reviendrait à laisser le développeur choisir ses propres privilèges, l’enveloppe étant la seule borne. C’est littéralement « déclarer vaut obtenir » — l’erreur d’Android avant sa version 6, qu’ADR-150 D1 cite nommément comme la raison d’être de l’octroi. Un octroi que personne ne décide n’est pas un octroi, c’est un manifeste recopié.
Une politique locale reste possible plus tard, pour une flotte. Elle devra alors être un artefact signé et versionné, et devenir elle-même une cible à protéger — raison de plus pour ne pas commencer par elle.
10.7 L’outil d’administration n’est pas un copieur de fichiers
air-bundle accorde. Il ne dépose pas les octets d’un bundle : ceux-là arrivent par
l’empaquetage — l’image, un .deb, plus tard la logithèque. Un copieur de fichiers de plus
n’ajouterait qu’un second chemin par lequel du code arrive sur la machine, c’est-à-dire
exactement ce qu’ADR-046 D6 cherche à réduire à un seul. Ce que l’administration possède est
l’octroi, pas les octets.
C’est un rétrécissement de ce que la tâche annonçait (« déposer le bundle »), et il est délibéré.
Ce que l’outil fait, et les choix qui s’y voient
- Trois commandes, dont une seule ne mute pas :
shown’est pas gatée. Gater une lecture n’ajouterait qu’une invite là où il n’y a rien à protéger ; ne pas gater une écriture ouvrirait la porte qu’[ADR-101] ferme. Un test tient cette réponse. - Le secret ne passe jamais par
argv— première ligne destdin, commeair-account-clietair-keystore-cli. Un mot de passe en ligne de commande est visible deps, donc de tout compte de la machine. - Un drapeau par droit (
--read-path,--write-path,--connect-port…) plutôt qu’un--path /var/lib/x:rwà décomposer. C’est plus long à écrire et il n’y a rien à analyser. Deux drapeaux sur le même chemin donnent une règle portant les deux droits, pas deux règles concurrentes — sinon « lire + écrire » demandé ne couvrirait aucune des deux moitiés. - Il n’existe pas de drapeau « tout ce qui est demandé ». Ce serait un acte explicite,
donc conforme à la lettre de la décision du §10.4 — mais c’est exactement celui que tout le
monde finirait par taper. Flatpak a perdu son modèle ainsi :
--filesystem=homeest devenu ce que presque toutes les applications réclament. Si nommer les droits un par un se révèle intenable, ce sera une donnée mesurée, pas une commodité offerte d’avance. Un test vérifie qu’aucun raccourci de ce genre n’est accepté. - Une crate à part,
air-bundle-cli, et non une sous-commande d’air-launchd: le lanceur vérifie des octrois, il n’en émet pas. Lui donner la clé de signature de la machine dans son arbre de dépendances ferait tourner en permanence, en root, un processus qui porte de quoi accorder. Même partage queair-keystore-cliface àair-sshd.
L’ordre des écritures, et ce qu’il coûte de le prendre à l’envers
À l’écriture, la signature d’abord : si la seconde échoue, il reste une signature sans octroi — l’étape 2 de l’arbitrage échoue sur un octroi absent, c’est-à-dire une révocation, l’état sûr. L’ordre inverse laisserait un octroi que personne n’a signé.
Au retrait, l’octroi d’abord — l’inverse, pour la même raison : l’état intermédiaire doit être celui qui refuse.
Révoquer ce qui n’est pas accordé n’est pas une erreur. Refuser obligerait l’administrateur à savoir avant d’agir, donc à lire le magasin pour pouvoir le nettoyer.
10.5 Nommer un droit non demandé est un refus, pas un silence
issue() intersecte : proposer plus que ce que le manifeste demande rend simplement
moins. C’est la bonne propriété pour l’invariant — l’octroi ne peut jamais dépasser la
demande, quoi qu’on propose.
Mais le silence serait mauvais ici. Un administrateur qui écrit /etc/shadow sur un
bundle qui ne l’a jamais demandé croirait l’avoir accordé, et repartirait avec une idée fausse
de ce que la machine autorise. issue_grant refuse donc, en nommant tout ce qui cloche —
pas la première erreur, toutes, sinon on corrige une ligne à la fois.
Ce n’est pas une garde de sécurité de plus : l’invariant tient sans elle. C’est une garde contre une méprise, et une méprise sur des privilèges finit par coûter aussi cher qu’une faille.
La comparaison est celle de l’invariant — égalité de chemins et de verbes, jamais inclusion. Un droit d’écriture proposé sur un chemin demandé en lecture seule est « non demandé » : c’est un droit de plus, pas une nuance.
10.6 Ce que l’outil d’administration devra faire, et qui n’est pas encore écrit
Le dépôt reste à faire, et il n’est pas anodin — d’où sa séparation :
- il passe par la porte d’ADR-101 : toute mutation de configuration Air redemande le mot
de passe administrateur, et émettre un octroi en est une.
air-admin-gateexiste déjà et sera consommé tel quel ; - copier une arborescence en imposant les modes a des cas limites propres (liens symboliques dans la source, propriété, atomicité) qui méritent leurs tests plutôt qu’un coin de PR ;
- la révocation est le retrait de l’octroi — donc une opération de l’outil, pas un mécanisme de plus.
Licence du document : MPL 2.0