Aller au contenu
Sécurité medium

Lade : injecter les secrets automatiquement avec des hooks shell

16 min de lecture

Vous devez manuellement charger vos secrets avant chaque commande — un source .env, un export VAULT_TOKEN=..., un eval $(op signin) — et vous finissez par tout mettre dans .zshrc par paresse. Lade (prononcé /leɪd/) est une CLI open source qui injecte automatiquement les secrets dans vos processus grâce à des hooks shell transparents. Quand vous entrez dans un répertoire projet, Lade charge les secrets. Quand la commande se termine, il les nettoie.

Ce guide vous apprend à installer Lade, configurer les sources de secrets, utiliser les hooks shell automatiques et personnaliser l’injection par commande et par utilisateur. Il s’adresse aux développeurs qui veulent que leurs secrets soient toujours disponibles sans jamais être exposés.

  • Ce qu’est Lade et comment fonctionnent les hooks shell
  • Comment configurer un fichier lade.yml avec le raw loader, le file loader et les vault loaders
  • Comment filtrer les secrets par commande grâce aux regex
  • Comment gérer les secrets par utilisateur dans une équipe
  • Comment générer des fichiers de secrets temporaires (YAML, JSON) nettoyés automatiquement
  • L’injection manuelle avec lade inject pour les scripts et la CI

Lade est une CLI cross-platform (Linux, macOS) développé en Rust. Il intercepte chaque commande que vous tapez dans le shell grâce à des hooks (preexec/precmd) et injecte les secrets correspondants dans les variables d’environnement du processus — puis les supprime quand la commande se termine.

L’analogie est simple : Lade est un assistant invisible qui prépare vos secrets juste avant que vous en ayez besoin et qui range tout derrière vous. Contrairement à un fichier .env qui reste sur le disque indéfiniment, les secrets de Lade n’existent que pendant la durée de la commande.

AspectDétail
ObjectifInjecter les secrets automatiquement via des hooks shell, les nettoyer après exécution
ApprocheHooks shell (preexec/precmd) + injection par regex de commande
LoadersRaw (valeurs en clair), File (JSON/YAML/TOML/INI), Vault, Infisical, 1Password, Doppler, Passbolt
FiltragePar regex de commande (ex: terraform.*, python.*, .* pour tout)
Per-userSecrets différents selon l’utilisateur (équipe)
Sortie fichierGénération de fichiers YAML/JSON temporaires, supprimés après exécution
LicenceApache-2.0
LangageRust

Lade utilise les mécanismes natifs de votre shell pour intercepter chaque commande :

  1. Avant l’exécution (preexec) : Lade remonte l’arborescence de répertoires à la recherche de fichiers lade.yml, agrège les secrets qui correspondent à la commande que vous êtes sur le point d’exécuter, et les injecte dans les variables d’environnement.

  2. Après l’exécution (precmd) : Lade supprime toutes les variables d’environnement qu’il a injectées — les secrets ne persistent jamais dans la session shell.

Ce mécanisme fonctionne avec Zsh, Bash et Fish.

  • Un système Linux ou macOS
  • Un shell compatible : Zsh, Bash ou Fish
  • Rust/Cargo pour l’installation via Cargo (recommandé)
  • Optionnel : un vault (HashiCorp Vault, 1Password CLI, Infisical, Doppler, Passbolt)
  1. Installer Lade

    Fenêtre de terminal
    # Via Cargo (recommandé)
    cargo install lade --locked
    # Ou via le script d'installation
    curl -fsSL https://raw.githubusercontent.com/zifeo/lade/main/installer.sh | bash
  2. Installer les hooks shell

    Cette commande ajoute les hooks dans votre profil shell (.zshrc, .bashrc ou config.fish) :

    Fenêtre de terminal
    lade install
  3. Redémarrer votre shell

    Fenêtre de terminal
    exec $SHELL
  4. Vérifier l’installation

    Fenêtre de terminal
    lade --version

    Résultat attendu :

    lade 0.13.0

Le fichier lade.yml se place à la racine de votre projet. Sa structure est simple : une regex de commande comme clé, et les secrets comme valeurs.

lade.yml
.*:
DB_HOST: localhost
DB_PORT: "5432"
DB_USER: devuser
DB_PASSWORD: s3cr3t_p4ssw0rd
API_KEY: sk-test-1234567890abcdef

La clé .* est une regex qui matche toutes les commandes. Chaque variable est injectée dans l’environnement de chaque commande exécutée dans ce répertoire (ou ses sous-répertoires).

La puissance de Lade réside dans le filtrage par regex de commande. Vous pouvez injecter des secrets différents selon l’outil utilisé :

lade.yml
# Secrets communs à toutes les commandes
.*:
DB_HOST: localhost
DB_PORT: "5432"
DB_USER: devuser
# Secrets pour Terraform uniquement
terraform.*:
TF_VAR_db_password: s3cr3t_terraform
AWS_REGION: eu-west-1
AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
# Secrets pour Python/Node uniquement
python.*|node.*:
API_KEY: sk-test-1234567890abcdef
DB_PASSWORD: s3cr3t_p4ssw0rd

Avec cette configuration :

  • terraform plan reçoit DB_HOST, DB_PORT, DB_USER, TF_VAR_db_password, AWS_REGION et AWS_ACCESS_KEY_ID
  • python3 app.py reçoit DB_HOST, DB_PORT, DB_USER, API_KEY et DB_PASSWORD
  • ls reçoit uniquement DB_HOST, DB_PORT et DB_USER

Cette approche limite l’exposition : Terraform n’a pas accès à l’API key, et Python n’a pas accès aux credentials AWS.

Lade supporte 7 loaders pour récupérer les secrets depuis différentes sources.

Le loader par défaut injecte les valeurs telles quelles. Préfixer une valeur par ! force l’utilisation du raw loader (utile pour les valeurs qui commencent par un protocole comme vault://) :

lade.yml
.*:
SIMPLE_VALUE: hello_world
ESCAPED_VALUE: "!vault://this-is-not-a-vault-ref"

Le file loader lit les secrets depuis un fichier JSON, YAML, TOML ou INI local. Le paramètre query utilise la syntaxe JMESPath pour extraire des champs spécifiques :

lade.yml
.*:
DB_HOST: "file://secrets.json?query=.database.host"
DB_PORT: "file://secrets.json?query=.database.port"
DB_PASSWORD: "file://secrets.json?query=.database.password"

Avec le fichier secrets.json :

secrets.json
{
"database": {
"host": "db.prod.example.com",
"port": 5432,
"password": "pr0d_p4ss"
}
}

Le chemin peut être relatif au répertoire du lade.yml, commencer par ~ ou $HOME, ou être absolu.

lade.yml
.*:
DB_PASSWORD: "vault://vault.company.com/secret/myapp/DB_PASSWORD"
API_KEY: "vault://vault.company.com/secret/myapp/API_KEY"

Le format est vault://DOMAINE/MOUNT/CLE/CHAMP. L’authentification utilise le CLI vault local (vous devez être connecté via vault login).

lade.yml
.*:
DB_PASSWORD: "infisical://app.infisical.com/PROJECT_ID/production/DB_PASSWORD"

Le format est infisical://DOMAINE/PROJECT_ID/ENVIRONNEMENT/NOM_SECRET. Lade ajoute automatiquement /api au domaine. L’authentification utilise le CLI infisical local.

lade.yml
.*:
DB_PASSWORD: "op://my.1password.eu/DevVault/Database/password"
API_KEY: "op://my.1password.eu/DevVault/APIKeys/production"

Le format est op://DOMAINE/VAULT/ITEM/CHAMP. L’authentification utilise soit une session op active, soit un OP_SERVICE_ACCOUNT_TOKEN (pour la CI).

Pour utiliser un token de service stocké dans un autre vault (lookup récursif) :

lade.yml
.*:
.:
1password_service_account: "vault://vault.company.com/secret/1password/service-token"
SECRET: "op://my.1password.eu/DevVault/Item/field"
lade.yml
.*:
DB_PASSWORD: "doppler://api.doppler.com/my-project/production/DB_PASSWORD"
lade.yml
.*:
DB_PASSWORD: "passbolt://passbolt.company.com/RESOURCE_ID/password"
LoaderURICLI requisAuthentification
RawValeur directeNonAucune
Filefile://chemin?query=.champNonAucune
Vaultvault://domaine/mount/clé/champvaultvault login
Infisicalinfisical://domaine/projet/env/secretinfisicalinfisical login
1Passwordop://domaine/vault/item/champopSession ou service account
Dopplerdoppler://domaine/projet/env/secretdopplerdoppler login
Passboltpassbolt://domaine/resource_id/champAucun (API)Credentials Passbolt

Pour les scripts non-interactifs et les pipelines CI, utilisez lade inject :

Fenêtre de terminal
# Injecter les secrets et lancer une commande
lade inject -- python3 app.py
# Équivalent à "teller run" pour les utilisateurs de Teller
lade inject -- npm start
# Avec un chemin absolu si nécessaire
lade inject -- /usr/bin/python3 app.py

Résultat :

Lade loaded: DB_PASSWORD, API_KEY, DB_HOST, DB_PORT, DB_USER.
Connexion à localhost:5432
User: devuser
API Key: configurée

Lade affiche les noms des variables chargées (sans les valeurs) sur stderr, puis exécute la commande.

Quand plusieurs membres d’une équipe partagent le même projet, chacun peut avoir des secrets différents. Lade résout automatiquement l’utilisateur courant :

lade.yml
.*:
DB_HOST: localhost
DB_PORT: "5432"
DB_PASSWORD:
alice: alice_db_password
bob: bob_db_password
.: default_password

Avec cette configuration :

  • bob reçoit DB_PASSWORD=bob_db_password
  • alice reçoit DB_PASSWORD=alice_db_password
  • Tout autre utilisateur reçoit DB_PASSWORD=default_password
  • Si la clé "." est absente, les autres utilisateurs ne reçoivent rien
Fenêtre de terminal
# Voir l'utilisateur courant
lade user
# Changer d'utilisateur (utile pour tester)
lade user alice
# Revenir à l'utilisateur OS
lade user --reset

Par défaut, Lade injecte les secrets en variables d’environnement. Vous pouvez aussi les écrire dans un fichier temporaire (YAML ou JSON) qui sera automatiquement supprimé après l’exécution de la commande :

lade.yml
.*:
.:
file: secrets.yml
DB_HOST: localhost
DB_PORT: "5432"
DB_PASSWORD: s3cr3t_p4ssw0rd

Quand vous exécutez une commande, Lade :

  1. Crée le fichier secrets.yml avec les secrets
  2. Exécute la commande
  3. Supprime le fichier secrets.yml

Le format du fichier est déterminé par l’extension (.yml/.yaml pour YAML, .json pour JSON).

Les hooks sont gérés par les commandes on et off :

Fenêtre de terminal
# Désactiver les hooks (temporaire, session courante)
eval "$(lade off)"
# Réactiver les hooks
eval "$(lade on)"
# Installer les hooks (permanent, modifie .zshrc/.bashrc)
lade install
# Désinstaller les hooks (permanent)
lade uninstall

La commande eval "$(lade off)" est utile pour déboguer : si une commande échoue de façon inattendue, désactivez les hooks pour vérifier si Lade est en cause.

.gitlab-ci.yml
deploy:
stage: deploy
image: rust:1.78-slim
before_script:
- cargo install lade --locked
script:
- lade inject -- ./deploy.sh
variables:
VAULT_TOKEN: $CI_VAULT_TOKEN
.github/workflows/deploy.yml
name: Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Lade
run: cargo install lade --locked
- name: Deploy with secrets
run: lade inject -- ./deploy.sh
env:
VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }}
CritèreLadeTeller
InjectionHooks shell automatiques ou lade injectteller run (toujours explicite)
NettoyageAutomatique après chaque commandeLes secrets persistent dans le processus
FiltragePar regex de commandeTous les secrets sont injectés
Per-userOui, natifNon
Scan secretsNonOui (teller scan)
Redact logsNonOui (teller redact)
ExportNon (sortie fichier uniquement)JSON, YAML, env, CSV
TemplateNonOui (moteur Tera)
ProvidersVault, 1Password, Infisical, Doppler, Passbolt, FileVault, AWS SM, AWS SSM, GCP SM, Consul, dotenv
Héritage configRécursif (remonte l’arborescence)Fichier unique .teller.yml

En résumé : Lade est idéal pour le workflow développeur quotidien grâce aux hooks transparents et au filtrage par commande. Teller excelle pour le scanning de secrets et la manipulation programmatique (export, redact, template, copy).

SymptômeCause probableSolution
lade: command not foundBinaire pas dans le PATHVérifier avec which lade ou ajouter ~/.cargo/bin au PATH
Secrets non injectés en shell interactifHooks non installésExécuter lade install puis redémarrer le shell
Secrets non injectés en scriptHooks inactifs en non-interactifUtiliser lade inject -- <commande>
command failed avec lade injectCommande non trouvée dans le PATHUtiliser le chemin absolu : /usr/bin/python3
Mauvais secret per-userUtilisateur Lade différent de l’OSVérifier avec lade user, réinitialiser avec lade user --reset
Conflit entre fichiers lade.ymlHéritage récursif des répertoires parentsLe lade.yml le plus proche du CWD a priorité
vault:// non résoluCLI vault non authentifiéExécuter vault login avant d’utiliser Lade
op:// non résoluCLI op non authentifiéExécuter eval $(op signin) ou configurer un OP_SERVICE_ACCOUNT_TOKEN
Fichier de sortie non crééBloc .: absent ou mal formatéVérifier que .: { file: secrets.yml } est sous le bon regex
Lade loaded: mais variables absentesRegex de commande ne matche pasVérifier que le regex matche la commande (.*: pour tout matcher)
  • Lade injecte les secrets automatiquement grâce à des hooks shell — vous tapez simplement votre commande, sans wrapper ni préfixe.

  • Les secrets sont nettoyés après chaque commande : ils n’existent que pendant la durée de vie du processus enfant.

  • Le filtrage par regex de commande permet de n’exposer que les secrets nécessaires à chaque outil : Terraform reçoit les credentials AWS, Python reçoit les clés API, rien de plus.

  • Les secrets par utilisateur permettent à chaque membre de l’équipe d’avoir ses propres credentials tout en partageant le même lade.yml dans Git.

  • 7 loaders couvrent les principaux vaults du marché : Vault, 1Password, Infisical, Doppler, Passbolt, plus un file loader et un raw loader.

  • lade inject est l’équivalent de teller run pour les environnements non-interactifs (CI/CD, scripts).

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn