ADR-089 — Réservation du SecurityManager : sécurité active per-process (couche 1)
Statut : Accepté (2026-07-10, décision BDFL) — ADR de réservation d’architecture.
Grave l’existence, le rôle, le placement et le seam du SecurityManager ;
le moteur (politique, règles, détection, enforcement concret) fera l’objet d’un ADR
ultérieur. Companion ADR-032 (préservation des données),
ADR-077 (Managers), ADR-073
(config binaire).
Catégorie : Architecture (sécurité, couche 1). N’altère aucune API scellée ; introduit un seam (interface + défaut permissif) câblé additivement.
Contexte
Air est secure-first, au service de la préservation des données confiées par l’utilisateur (charte ; ADR-032). La sécurité compte dès la couche 1, à deux étages :
- Sécurité passive — ce que le kernel offre (contrôle d’accès fichiers par uid/gid, DAC, capabilities). Nécessaire mais insuffisante : elle ne protège pas contre l’abus dynamique d’un exécutable déjà autorisé (un binaire lancé par l’utilisateur qui tente d’allouer des pages énormes, boucle en fork-bomb de shells, ou subit un flood réseau).
- Sécurité active — une décision au moment de l’opération : autoriser ou refuser, et le
journaliser. C’est ce qui manque, et c’est l’objet du
SecurityManager.
On façonne les Managers de domaine en ce moment (ADR-077,
ADR-088 — AirTaskManager vient d’être posé).
Réserver dès maintenant le point de consultation évite un rétro-fit transverse coûteux et
risqué une fois les Managers stabilisés (Principe d’ingénierie 5 : sur-sécuriser puis mesurer,
jamais l’inverse ; Principe 4 : validation en amont).
Décision
-
Existence & rôle. Le
SecurityManager(couche 1) est l’oracle de sécurité active d’Air. Tous les Managers sensibles consultent leSecurityManagerAVANT une opération sensible. Il répond une décisionAllow/Denyet journalise la raison du oui/non dans les logs machine (AirLog/journald) — traçabilité obligatoire. -
Placement : per-process, embarqué par
libair— PAS un daemon. Un démon central devrait répondre à tous les process → goulot d’étranglement + point de défaillance unique. LeSecurityManagervit dans chaque process, fourni parlibair, et est sollicité par les autres Managers du même process. La décision est locale ; l’enforcement passe par les mécanismes kernel partagés que la couche 0 expose déjà (famille security : eBPF — map de drop réseau —, cgroups — quotas mémoire/CPU —, seccomp, Landlock) : une décision locale a donc un effet machine-wide via le kernel. Les règles proviennent de la configuration binaire (ADR-073). -
Seam (ce qu’on pose maintenant). Une interface
SecurityManager—check(operation) -> Decision { Allow | Deny(motif) }, le motif étant journalisé — avec un défaut PERMISSIF (allow-all + log). Les Managers sensibles (AirTaskManager,AirNetworkManager,AirFileManager,AirMemoryManager…) consultent unSecurityManager. Le câblage de la consultation est incrémental (ajouté quand on touche chaque Manager), pas un big-bang. Tant que le moteur n’existe pas, le défaut permissif rend la consultation sans effet (mais la place et la trace de log existent). -
Exemples cadrant le besoin (non normatifs, pour la spéc du moteur) : > 50 tentatives de connexion en < 5 s depuis une même IP → poser dynamiquement une règle eBPF qui drop cette source ; exécutable qui tente d’allouer des pages énormes ou boucle en fork-bomb → refus + log.
-
Explicitement DIFFÉRÉ (ADR ultérieur). Le moteur : format des règles, détection d’abus, rate-limiting, intégration concrète eBPF/cgroups, agrégation d’états, politique par défaut. À aligner sur
air-sshd(ADR-074), premier vrai consommateur de sécurité active (flood de connexions).
Conséquences
- Les Managers sensibles gagnent, au fil de l’eau, un point de consultation à défaut permissif — coût quasi nul, zéro effet fonctionnel tant que le moteur est absent.
- Ne bloque pas le chantier PAL (ADR-088) : le
PAL binde les Managers ; ce sont les Managers qui consultent le
SecurityManager, pas le PAL. On peut poursuivre le reprofilage de la cible et l’implémentation du PAL. - À terme, le
SecurityManagerest le bras actif du « rempart devant le kernel » (doctrine userland Rust safe) : la sécurité passive (kernel) + la sécurité active (SecurityManager) se cumulent (defense in depth). - Placement per-process ⇒ le manager ne porte pas d’état machine-wide en propre ; l’agrégation et l’enforcement transverses passent par le kernel (maps eBPF, cgroups).
Alternatives rejetées
- Démon de sécurité central : rejeté — goulot d’étranglement (répond à tous les process), point de défaillance unique, latence sur chaque opération sensible.
- Ne rien réserver, décider plus tard : rejeté — rétro-fitter la consultation dans chaque Manager une fois l’API stabilisée est coûteux et risqué (Principe 5). Le coût de la réservation maintenant est quasi nul.
- Sécurité passive kernel seule (DAC) : insuffisante contre l’abus dynamique d’un exécutable déjà autorisé (flood, fork-bomb, huge-alloc) — d’où le besoin d’un étage actif.
- Tout spécifier maintenant (moteur inclus) : rejeté — dériverait le chantier PAL en cours ;
le moteur mérite son propre ADR, informé par
air-sshd.