07. Architecture Interne du Moteur sur le Poste Utilisateur
Navigation : Documentation > 07. Architecture Interne du Moteur
Sommaire
- Principes de Conception du Moteur Client
- Arborescence Complète des Sources du Moteur
- Sécurité Système Intégrée au Moteur
- Protection et Streaming d'Extraction
- Mitigation TOCTOU sur le Cache
- Sécurisation du Trampoline Rust
- Concurrence et Verrous Granulaires
- Autocomplétion CLI Ultra-Rapide
- Isolation Multi-Utilisateurs et Assainissement ANSI
- Auto-Suffisance des Bibliothèques Partagées
- Déduplication de Blocs et Partage d'Espace (CoW & Reflinks)
- Flux d'Exécution Sécurisé (
lib/lifecycle.nu)
1. Principes de Conception du Moteur Client (~/.erable/core/)
Le moteur client est conçu selon une règle architecturale stricte : la séparation absolue entre la logique métier universelle et les détails bas-niveau propres à chaque système d'exploitation.
- Le Système Commun Universel (
lib/) ne contient aucun embranchementif os == "windows". Il manipule des flux de données typées Nushell, gère le cache TTL des index (24h), ordonnance la politique de rétention des versions (N-1 automatique avec pinning), et applique les contrôles de sécurité cryptographiques et d'extraction étanches. - La Couche d'Abstraction Système (
platform/) fournit une interface standardisée (ledriver) instanciée dynamiquement selon l'OS hôte détecté viasys host | get name. - Routage 100% Trampolines Rust & Fichiers Pointeurs (
.target) : Tout lien symbolique dansbin/est banni. Chaque commande est un trampoline Rust natif validant que sa cible réside dans~/.erable/packages/, supprimant le dossier courant de l'ordre de recherche des DLLs (SetDllDirectoryW(L"")), et lisant un fichier frère.target.
2. Arborescence Complète des Sources du Moteur
L'architecture interne du moteur suit strictement une séparation en 5 couches logicielles découplées (L0 à L4) implémentées en Nushell natif sous engine/ :
~/.erable/core/engine/ (ou dépôt core/engine/)
├── erable.nu # L0 : Point d'entrée CLI unifié, parseur d'arguments & bootstrap
├── pipelines.nu # L1 : Pipelines d'exécution métier, formatage terminal & codes exit
├── interfaces.nu # L2 : Sas étanche de validation des manifestes, plateformes & SHA-256
├── orchestrators.nu # L3 : Orchestrateurs métier, résolution DAG et détection de cycles
├── tasks.nu # L4 : Tâches atomiques pures (7z/MSI, hardlinks CoW, hooks Zero-Egress)
└── errors.nu # Erreurs : Contrats d'erreurs structurées et typées (make-error)
2.1 Découpage et Responsabilités par Couche (L0 - L4)
| Couche | Module | Rôle Architectural | Responsabilités Clés |
|---|---|---|---|
| L0 | erable.nu |
Bootstrap CLI | Déclaration des sous-commandes CLI (install, uninstall, prune, search, list, apply, update, update-trampolines, version), validation des flags et transfert vers L1. Voir le Guide des Commandes CLI. |
| L1 | pipelines.nu |
Pipelines d'Exécution | Orchestration haut-niveau, mise en forme des retours tabulaires ou messages d'erreur, et conversion des statuts en codes retour OS (exit 0 ou exit 1). |
| L2 | interfaces.nu |
Validation Gate (Sas Étanche) | Contrôle contractuel strict des manifestes, paramètres CLI (validate-prune-params), vérification plateforme et intégrité SHA-256. |
| L3 | orchestrators.nu |
Orchestrateurs Métier & DAG | Algorithme DFS de résolution des dépendances sans cycle, orchestration du cycle de vie des paquets et purge multi-paquets (orchestrate-prune). |
| L4 | tasks.nu |
Tâches Atomiques Pures | Primitives I/O élémentaires isolées : téléchargement avec en-têtes Chrome, déballage multi-formats, liaisons physiques NTFS CoW, purge de versions (task-prune-package-versions), bac à sable Zero-Egress. |
| Erreurs | errors.nu |
Contrats d'Erreurs Immuables | Définition des structures d'erreurs structurées (VALIDATION_ERROR, INFRASTRUCTURE_ERROR, CIRCULAR_DEPENDENCY_DETECTED) et fonction de rendu unifiée format-error. |
3. Sécurité Système Intégrée au Moteur
3.1 Protection et Streaming d'Extraction (lib/archive.nu)
- Mitigation Zip Slip (CVE-2018-1002200) : Pour chaque fichier extrait, validation canonique stricte interdisant toute évasion hors du répertoire assigné. Sur les paquets massifs (> 20 000 fichiers), cette vérification s'exécute en streaming natif C/Rust via le helper autonome (
7za/stream Rust) sans round-trip d'interpréteur par fichier. - Mitigation Symlink Traversal (CVE-2021-32658) : Interdiction d'extraire des liens symboliques pointant hors du répertoire assigné ou d'extraire des fichiers à travers des liens symboliques préexistants.
- Neutralisation SUID/SGID (CVE-2021-44733) : Application systématique d'un masque
mode & 0755sous Unix sur tous les fichiers extraits.
3.2 Mitigation TOCTOU sur le Cache (lib/network.nu)
- Les archives sont stockées dans
~/.erable/cache/avec des permissions privées exclusives (0700sous Unix, ACLs réservées à l'utilisateur courant sous Windows). - Le descripteur de fichier est verrouillé en lecture exclusive entre la validation SHA-256 et la décompression.
3.3 Sécurisation du Trampoline Rust
- Anti-Détournement
.target: Le trampoline valide que la cible canonique réside strictement dans~/.erable/packages/. Tout chemin pointant ailleurs est bloqué au runtime. - Anti-DLL Hijacking (CVE-2020-11738) : Appel de
SetDllDirectoryW(L"")avant instanciation du processus cible pour retirer le répertoire courant de l'ordre de recherche des DLLs. - Sécurité Batch & PowerShell : Échappement strict et invocation
cmd.exe /d /e:off /cpour cibles batch (.cmd/.bat) etpowershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Filepour cibles PowerShell (.ps1). - Remplacement Atomique : Écriture sur fichier temporaire et substitution atomique via
ReplaceFileW/rename().
3.4 Concurrence et Verrous Granulaires (lib/lock.nu)
- Verrouillage granulaire par paquet (
~/.erable/locks/<pkg>.lock) permettant à deux sessions ou workers CI d'installer ou mettre à jour des paquets distincts en parallèle. - Verrou global exclusif (
~/.erable/.lock) réservé uniquement pour une durée critique minimale (< 5 ms) lors de l'écriture dans~/.erable/bin/ou la mise à jour des index.
3.5 Autocomplétion CLI Ultra-Rapide (<tab>)
- Génération en arrière-plan d'un fichier plat de complétion précalculé (
~/.erable/cache/completions.txt) lu directement par le shell hôte sans instancier l'interpréteur Nushell.
3.6 Isolation Multi-Utilisateurs et Assainissement ANSI
- Permissions Restrictives : Application systématique d'un
chmod 0700sous POSIX et d'ACLs Windows réservées au SID du propriétaire sans héritage sur la racine~/.erable. - Filtrage ANSI (CVE-2023-45806) : Nettoyage systématique
ansi stripde toutes les métadonnées et sorties de vérification avant rendu dans la console.
3.7 Auto-Suffisance des Bibliothèques Partagées (Hermetic Packaging)
- Chaque paquet embarque l'intégralité de ses bibliothèques dynamiques dans son propre dossier d'installation
packages/<pkg>/<version>/. - Le trampoline positionne
lpCurrentDirectory(Windows) ouRPATH(Linux) sur ce dossier réel, garantissant le chargement exclusif des DLLs et bibliothèques locales sans collision globale.
3.8 Déduplication de Blocs et Partage d'Espace lors des Mises à Jour (CoW & Reflinks)
Pour éviter la duplication de l'espace disque induite par la politique de rétention de la version $N-1$ (nécessaire au rollback instantané en $\mathcal{O}(1)$), le moteur applique une stratégie de partage de blocs physiques et d'extents au niveau du système de fichiers :
- Linux (Btrfs, XFS, ZFS) : Exploitation de
ioctl(FICLONE)etcp --reflink=auto. Les fichiers identiques entre la version $N-1$ et la version $N$ partagent les mêmes blocs physiques sur disque (0 octet physique additionnel alloué pour les blocs inchangés). - macOS (APFS) : Appel de la primitive noyau
clonefile(), créant des clones instantanés pointant vers les mêmes extents physiques de données en 0 milliseconde. - Windows (ReFS / NTFS) :
- Sous ReFS : Clonage d'extents de blocs via
FSCTL_DUPLICATE_EXTENTS_TO_FILE. - Sous NTFS : Création de liens matériels (
CreateHardLinkW) pour les fichiers strictement identiques immuables. L'immutabilité stricte du storepackages/<pkg>/<version>/garantit qu'aucune écriture collatérale ne peut corrompre la version antérieure.
Comportement lors de la suppression d'anciennes versions (erable cleanup / désinstallation) :
La suppression d'une version obsolète n'altère jamais la version active :
- Sur systèmes CoW (Btrfs, XFS, ZFS, APFS, ReFS) : le système de fichiers décrémente le compteur de références des extents. Les blocs partagés restent alloués pour la version courante. Seuls les blocs exclusifs à l'ancienne version sont physiquement libérés.
- Sur NTFS (Hardlinks) : la suppression décrémente le compteur de liens du record MFT. Les données physiques persistent tant que le fichier de la version active existe (
HardLinkCount >= 1). - L'opération est atomique et sans recopie.
4. Flux d'Exécution Sécurisé (lib/lifecycle.nu)
# lib/lifecycle.nu
use ../platform/driver.nu
use lock.nu
use collision.nu
use env_manager.nu
use archive.nu
use network.nu
use crypto.nu
use verifier.nu
export def install-package [pkg_dir: path, target_arch: string, custom_aliases: record = {}] {
let driver = (driver get-driver)
let lock_handle = (lock acquire ($driver.get_default_root | path join ".lock"))
try {
let manifest = (manifest load $pkg_dir)
# 0. Contrôle de compatibilité minimale de la version Nushell
if ($manifest.package.min_nu_version? != null) {
let current_nu = (version | get version)
if ($current_nu | semver-is-less $manifest.package.min_nu_version) {
error make { msg: $"Ce paquet requiert Nushell >= ($manifest.package.min_nu_version) (version actuelle : ($current_nu))." }
}
}
# 1. Arbitrage préventif des collisions d'alias dans bin/
collision check-and-resolve $manifest $custom_aliases $driver
let ctx = (config build-context $manifest $driver)
# 2. Hook pre-install sous confinement réseau hermétique (Zero Egress)
if (($pkg_dir | path join "pre_install.nu") | path exists) {
do $driver.run_sandboxed_hook ($pkg_dir | path join "pre_install.nu") $ctx
}
# 3. Téléchargement sécurisé et vérification anti-TOCTOU
let archive_file = (network download-secure $manifest.targets.$target_arch.url)
do $driver.clean_motw $archive_file
crypto verify-sha256 $archive_file $manifest.targets.$target_arch.sha256
# Vérification SLSA de provenance si requise
if ($manifest.package.security?.slsa?.required? == true) {
crypto verify-slsa-provenance $archive_file $manifest.package.security.slsa
}
# 4. Extraction sécurisée multi-formats (anti-Zip Slip, anti-symlink, SUID stripped)
let install_path = ($driver.normalize_path $ctx.paths.install)
archive extract-secure $archive_file $install_path $manifest.targets.$target_arch.archive_type?
# 5. Re-signature locale si environnement durci WDAC configuré
do $driver.sign_binaries_if_configured $install_path
# 6. Déploiement des TRAMPOLINES ET FICHIERS POINTEURS (.target)
for bin in $manifest.targets.$target_arch.binaries {
let bin_src = ($install_path | path join $bin.source)
let alias_name = ($custom_aliases | get -i $bin.source | default $bin.alias)
let is_gui = ($manifest.package.category? == "gui")
do $driver.ensure_executable $bin_src
do $driver.deploy_trampoline $bin_src $alias_name $is_gui $ctx
}
# 7. Gestion atomique des variables d'environnement permanentes
if ($manifest.environment? != null) {
env_manager register $manifest.package.name $manifest.environment $ctx $driver
}
# 8. Raccourcis OS optionnels
if ($manifest.shortcuts.enabled? == true) {
do $driver.register_shortcut $manifest.shortcuts $ctx.paths.install $ctx
}
# 9. Hook post-install sous confinement réseau hermétique
if (($pkg_dir | path join "post_install.nu") | path exists) {
do $driver.run_sandboxed_hook ($pkg_dir | path join "post_install.nu") $ctx
}
# 10. Validations obligatoires (check_version & check_help)
verifier test-version $manifest.verification.check_version
verifier test-help $manifest.verification.check_help
# 11. Application de la politique de rétention N-1
lifecycle apply-retention-policy $manifest.package.name $ctx
# 12. Mise à jour immédiate du PATH en mémoire dans la session active
$env.PATH = ($env.PATH | prepend $ctx.paths.bin | uniq)
print $"[erable] Succès : ($manifest.package.name) v($manifest.package.version) est prêt."
} catch {|err|
print $"[erable] Erreur : ($err.msg). Déclenchement du rollback..."
error make $err
}
lock release $lock_handle
}