Aller au contenu

06. Spécification des Scripts Normalisés, Contexte et Confinement Sécurisé

Navigation : Documentation > 06. Scripts Normalisés et Confinement


Sommaire

  1. Normalisation des Scripts de Cycle de Vie
  2. Règles de Normalisation
  3. Confinement de Sécurité Absolu (Zero Egress)
  4. Niveau 1 : Linter AST en CI/CD
  5. Niveau 2 : Assainissement d'Environnement (Environment Scrubbing)
  6. Niveau 3 : Confinement Réseau au Runtime (Zero Egress)
  7. Schéma et Évaluation du Contexte $ctx
  8. Évaluation sous Windows
  9. Évaluation sous macOS

1. Normalisation des Scripts de Cycle de Vie

Chaque paquet peut définir jusqu'à quatre scripts optionnels écrits en Nushell (nu) :

  1. pre_install.nu : Exécuté avant le téléchargement et l'extraction des binaires.
  2. post_install.nu : Exécuté après l'extraction des binaires et le déploiement des trampolines.
  3. pre_uninstall.nu : Exécuté avant la suppression des fichiers du paquet lors de la désinstallation.
  4. post_uninstall.nu : Exécuté immédiatement après la purge et le nettoyage complet.

1.1 Règles de Normalisation

  • Noms obligatoires stricts : pre_install.nu, post_install.nu, pre_uninstall.nu et post_uninstall.nu.
  • Emplacement obligatoire : À la racine du dossier du paquet (packages/<initiale>/<paquet>/).
  • Signature normalisée obligatoire :
    def main [ctx: record] {
        # Logique locale
    }
    

2. Confinement de Sécurité Absolu (Zero Egress)

Pour interdire toute exfiltration de données (clés SSH, tokens d'API, identifiants cloud), les scripts s'exécutent selon une défense en profondeur à 3 niveaux :

flowchart TD
    subgraph Niveau1 ["Niveau 1 : Audit Statique Obligatoire en CI/CD (Dépôt)"]
        PR["Pull Request Paquet (parts/ & scripts .nu)"]
        LINT["Audit Statique Zero Egress (scripts/ci/audit_package_scripts.py)"]
        DENY_RULES["Interdictions Formelles :<br>• http get / http post / curl / wget / nc / ssh<br>• Chemins : ~/.ssh, ~/.gnupg, ~/.aws, %USERPROFILE%\\.ssh<br>• Dynamique : eval, nu -c, run-external"]
        ALLOW_RULES["Allowlist Stricte :<br>• FS local : mkdir, cp, mv, rm, path, open (local)<br>• Données : str, table, math, print, if, match"]
        PR --> LINT
        LINT --> DENY_RULES
        LINT --> ALLOW_RULES
        DENY_RULES -->|Violation| REJECT["Rejet Automatique Bloquant de la PR"]
        ALLOW_RULES --> ACCEPT["Validation et Publication sur le Bucket"]
    end

    subgraph Niveau2 ["Niveau 2 : Assainissement d'Environnement (Client erable)"]
        ACCEPT --> CLIENT["Poste Utilisateur"]
        ENV_HOST["Environnement Hôte (SSH_AUTH_SOCK, AWS_*, TOKENS, SECRETS)"]
        SCRUB["Scrubbing Sélectif :<br>Toutes les variables sensibles sont purgées"]
        ENV_HOST --> SCRUB
        SCRUB --> CLEAN_ENV["Environnement Épuré : Seuls $ctx et PATH/TEMP minimaux"]
    end

    subgraph Niveau3 ["Niveau 3 : Confinement Réseau au Runtime (Zero Egress)"]
        CLEAN_ENV --> RUN["Lancement de pre/post_install.nu ou pre/post_uninstall.nu"]
        LNX["Linux : unshare --net (Network Namespace Isolé)"]
        MAC["macOS : sandbox-exec (deny network*)"]
        WIN["Windows : AppContainer / Shadowing réseau Nushell"]
        RUN --> LNX
        RUN --> MAC
        RUN --> WIN
        LNX --> LOCAL_OK["Exécution Locale Hermétique<br>(Aucun paquet réseau émis)"]
        MAC --> LOCAL_OK
        WIN --> LOCAL_OK
    end

2.1 Niveau 1 : Audit Statique Zero Egress en CI/CD (scripts/ci/audit_package_scripts.py)

Tout script (.nu) soumis dans un bucket est audité statiquement pour garantir le confinement Zero Egress :

  • Interdiction des primitives et exécutables réseau : http get, http post, curl, wget, nc, ncat, netcat, ssh, scp, sftp, telnet, ftp.
  • Interdiction d'accès aux répertoires et secrets sensibles : .ssh, .gnupg, .aws, .azure, .kube, id_rsa, id_ed25519, credentials, token, .env, AppData/Roaming.
  • Interdiction d'évaluation dynamique : eval, nu -c, bash, cmd.exe, powershell, etc.
  • Signature normalisée obligatoire : def main [ctx: record].

2.2 Niveau 2 : Assainissement d'Environnement (Environment Scrubbing)

Avant d'invoquer nu pre_install.nu $ctx, le moteur purge systématiquement :

  • Les clés et secrets cloud : AWS_*, AZURE_*, GOOGLE_*, KUBECONFIG.
  • Les jetons d'accès : GITHUB_TOKEN, GITLAB_TOKEN, NPM_TOKEN, *_KEY, *_SECRET, *_PASSWORD.
  • Les sockets d'agents : SSH_AUTH_SOCK, GPG_AGENT_INFO.

2.3 Niveau 3 : Confinement Réseau au Runtime (Zero Egress)

  • Linux : Invocation dans un namespace réseau vierge : unshare --net -- nu post_install.nu $ctx.
  • macOS : Invocation sous profil Seatbelt interdisant les sockets : sandbox-exec -p '(version 1)(allow default)(deny network*)' nu ....
  • Windows : Invocation sous profil Windows AppContainer (CreateProcess avec SECURITY_CAPABILITIES omettant internetClient et privateNetworkClientServer), rejetant matériellement tout appel Winsock avec WSAEACCES (10013).

3. Schéma et Évaluation du Contexte $ctx

Le contexte typé $ctx: record injecté dans pre_install.nu et post_install.nu expose les chemins résolus et les variables d'environnement natives de l'OS hôte :

3.1 Évaluation sous Windows

$ctx = {
    package: "ripgrep",
    version: "14.1.0",
    os: "windows",
    arch: "x86_64",

    paths: {
        root: "C:/Users/User/.erable",
        bin: "C:/Users/User/.erable/bin",
        install: "C:/Users/User/.erable/packages/ripgrep/14.1.0",
        package_store: "C:/Users/User/.erable/installed/ripgrep",
        cache: "C:/Users/User/.erable/cache"
    },

    os_vars: {
        appdata: "C:/Users/User/AppData/Roaming",
        localappdata: "C:/Users/User/AppData/Local",
        programdata: "C:/ProgramData",
        userprofile: "C:/Users/User",
        systemroot: "C:/Windows"
    }
}

3.2 Évaluation sous macOS

$ctx = {
    package: "ripgrep",
    version: "14.1.0",
    os: "macos",
    arch: "aarch64",

    paths: {
        root: "/Users/user/Library/Application Support/erable",
        bin: "/Users/user/Library/Application Support/erable/bin",
        install: "/Users/user/Library/Application Support/erable/packages/ripgrep/14.1.0",
        package_store: "/Users/user/Library/Application Support/erable/installed/ripgrep",
        cache: "/Users/user/Library/Application Support/erable/cache"
    },

    os_vars: {
        home: "/Users/user",
        xdg_config_home: "/Users/user/.config",
        xdg_data_home: "/Users/user/.local/share",
        xdg_state_home: "/Users/user/.local/state",
        xdg_cache_home: "/Users/user/.cache",
        app_support: "/Users/user/Library/Application Support",
        preferences: "/Users/user/Library/Preferences"
    }
}