
enject : Cachez les secrets .env des regards indiscrets : les secrets vivent dans des stockages locaux cryptés (par projet) et sont injectés directement dans les applications au moment de l'exécution, sans jamais toucher le disque en texte clair.
Cachez les secrets .env des regards indiscrets de l'IA.
(Remarque : ce projet s'appelait auparavant enveil et a été renommé en enject)
Les outils de codage IA comme Claude Code, Copilot, Cursor et autres peuvent lire les fichiers de votre répertoire projet, ce qui signifie qu'un fichier .env en clair est un dépotoir accidentel de secrets prêt à se produire. Ce n’est pas théorique. C’est un problème connu qui m’est arrivé plusieurs fois (même après avoir explicitement dit à Claude de ne pas regarder dans le fichier settings.json de Claude Code). enject résout ce problème en garantissant que les secrets en clair n’existent jamais sur le disque du tout. Votre fichier .env ne contient que des références symboliques ; les vraies valeurs résident dans un stockage local crypté et sont injectées directement dans votre sous-processus au lancement.
Ce projet s’inspire de la solution/article de blog de Filip Hric, qui utilise un concept similaire en s’appuyant sur 1Password. Je voulais une solution autonome qui ne dépende pas de services tiers, ce qui a donné naissance à cette solution. Et oui, ce projet a été construit presque entièrement avec Claude Code, avec beaucoup de vérification et de tests manuels.
Ce projet est principalement conçu pour atténuer le problème connu des outils IA/LLM lisant accidentellement les secrets .env dans votre projet. Les avantages supplémentaires incluent la prévention des fuites de secrets si un .env est accidentellement commité dans un dépôt, la possibilité de partager des fichiers .env contenant des références au lieu de secrets en clair, et la possibilité de partager le stockage crypté lui-même.
Ce projet n’est pas une solution miracle pour empêcher un agent IA d’obtenir vos secrets. Par exemple, un agent peut toujours écrire du code (par accident ou via une injection de prompt) qui exfiltre les secrets vers la sortie terminal ou un fichier au moment de l’exécution. Nous vous déconseillons vivement de vous fier à cet outil, ou aux fichiers .env en général, pour stocker des secrets de production.
Votre fichier .env ressemble à ceci :
DATABASE_URL=en://database_url
STRIPE_KEY=en://stripe_key
PORT=3000
Techniquement, il est sûr de le commiter (mais ne le faites pas, quand même), et plus important encore : sûr pour tout outil IA qui fouinerait accidentellement (ou peut-être pas si accidentellement).
Lorsque vous exécutez enject run -- npm start, il :
en:// à partir de la carte déchiffréeLe fichier de stockage est un blob binaire. Sans le mot de passe maître, il est indistinguable d’un bruit aléatoire. Le nonce est fraîchement généré à chaque écriture, donc la réutilisation du nonce AES-GCM est impossible. Toute modification du texte chiffré — même un seul bit inversé — entraîne un échec d’authentification et le déchiffrement est refusé.
Cette version est encore en alpha, donc nécessite d’ajouter la dernière version lors de l’appel à cargo install
cargo install enject --version 0.2.0-alpha
Nécessite Rust 1.70+.
git clone https://github.com/greatscott/enject
cd enject
cargo build --release
Le binaire compilé se trouve dans target/release/enject. Installez-le une fois dans un emplacement de votre PATH afin de pouvoir l’exécuter depuis n’importe quel projet :
macOS / Linux (bash ou zsh)
# Option A : ~/.local/bin (aucun sudo requis, courant sous Linux)
mkdir -p ~/.local/bin
cp target/release/enject ~/.local/bin/
# Option B : /usr/local/bin (nécessite sudo, disponible à l’échelle du système)
sudo cp target/release/enject /usr/local/bin/
# Option C : ~/.cargo/bin (déjà dans PATH si vous avez utilisé rustup)
cp target/release/enject ~/.cargo/bin/
Si vous avez utilisé l’option A et que ~/.local/bin n’est pas déjà dans votre PATH, ajoutez ceci à votre configuration shell (~/.zshrc, ~/.bashrc ou ~/.bash_profile) :
export PATH="$HOME/.local/bin:$PATH"
Puis rechargez-la :
source ~/.zshrc # ou ~/.bashrc
Vérifiez que cela a fonctionné :
enject --version
Le binaire est installé globalement — vous ne le réinstallez jamais. Mais chaque projet obtient son propre stockage crypté :
cd your-project
enject init
Ceci crée .enject/ dans le répertoire courant avec la configuration du projet et le stockage crypté. Ajoutez-le à .gitignore — il ne doit jamais être commité.
Exécutez ceci une fois par projet, à la racine du projet :
enject init
Ceci génère un sel aléatoire de 32 octets, écrit .enject/config.toml, crée un stockage crypté vide dans .enject/store, et vous invite à définir un mot de passe maître. Ajoutez .enject/ à votre .gitignore — le stockage ne doit jamais être commité.
enject set some_database_url
# prompts: Value for 'database_url': (hidden)
enject set some_api_key
Les valeurs sont toujours saisies de manière interactive. Il n’y a aucun moyen de passer une valeur comme argument en ligne de commande — cela empêche les secrets d’apparaître dans l’historique du shell ou dans la sortie de ps.
.envDATABASE_URL=en://some_database_url
MY_API_KEY=en://stripe_key
PORT=3000
Les lignes simples KEY=VALUE sont transmises sans modification. Seules les références en:// sont résolues.
enject run -- npm start
enject run -- python manage.py runserver
enject run -- cargo run
Tout ce qui suit -- est passé textuellement au système d’exploitation. Le sous-processus hérite de votre environnement shell complet (donc PATH, HOME, etc. sont présents) avec les valeurs de .env superposées par-dessus.
enject list # affiche les noms des clés stockées (jamais les valeurs)
enject delete <key> # supprime un secret
enject import <file> # crypte toutes les valeurs d’un .env en clair, le réécrit comme template en://
enject rotate # récrypte le stockage avec un nouveau mot de passe maître
Il n’y a pas de get ni de export. Imprimer une valeur secrète sur stdout crée un vecteur de fuite lisible par l’IA — l’intérêt même d’enject est de garder les valeurs hors du disque et de tout flux de sortie lisible.
Chaque invariant de sécurité a un test automatisé correspondant et un chemin d’inspection manuelle.
cargo test
31 tests, couvrant toutes les affirmations ci-dessous.
Automatisé : store::password::tests::test_encrypt_decrypt_roundtrip
Sauvegarde un secret, persiste le stockage, le recharge depuis le disque, le décrypte et vérifie que la valeur fait un aller-retour correct. Ne réussit que si les octets sur le disque sont un texte chiffré valide — le texte en clair échouerait au déchiffrement.
cargo test store::password::tests::test_encrypt_decrypt_roundtrip
Inspection manuelle :
enject init # mot de passe : test123
enject set mykey # valeur : my-super-secret
xxd .enject/store | head -5
strings .enject/store
xxd affichera des données binaires. strings ne retournera rien — il n’y a pas de séquences ASCII à extraire. Les 12 premiers octets sont le nonce aléatoire ; tout ce qui suit est le texte chiffré AES-GCM avec une étiquette d’authentification de 16 octets ajoutée à la fin.
Automatisé : store::password::tests::test_nonce_changes_on_each_save
Sauvegarde le stockage deux fois de suite, lit les 12 premiers octets du fichier à chaque fois et vérifie qu’ils sont différents.
cargo test store::password::tests::test_nonce_changes_on_each_save
Inspection manuelle :
xxd .enject/store | head -1 # notez les 12 premiers octets
enject set anotherkey # toute écriture change le nonce
xxd .enject/store | head -1 # les 12 premiers octets sont maintenant différents
Automatisé : store::password::tests::test_wrong_password_returns_err
Crée un stockage avec un mot de passe, puis tente de le déverrouiller avec un mot de passe différent et vérifie que Err est retourné.
cargo test store::password::tests::test_wrong_password_returns_err
Manuel :
enject list # entrez le mauvais mot de passe
# sortie : "Wrong master password or corrupted store."
# code de sortie : 1
AES-GCM produit une étiquette d’authentification de 16 octets sur le texte chiffré. Toute modification — même un seul bit inversé — fait échouer la vérification avant que le déchiffrement ne soit effectué. Le texte en clair n’est jamais exposé.
Automatisé : store::password::tests::test_tampered_ciphertext_returns_err
Inverse un octet dans la région du texte chiffré du fichier de stockage (après le nonce de 12 octets), puis tente le déchiffrement et vérifie que Err est retourné.
cargo test store::password::tests::test_tampered_ciphertext_returns_err
Manuel :
# Inverse l'octet 20 (dans le texte chiffré, après le nonce)
python3 -c "
data = open('.enject/store', 'rb').read()
bad = data[:20] + bytes([data[20] ^ 0xFF]) + data[21:]
open('.enject/store', 'wb').write(bad)
"
enject list
# sortie : "Wrong master password or corrupted store."
en:// non résolueSi une référence dans .env n’a pas de clé correspondante dans le stockage, enject run se termine immédiatement avec un code non nul. Le sous-processus n’est jamais lancé.
Automatisé : env_template::tests::test_unknown_ev_ref_returns_err
Appelle resolve() avec une référence qui n’a pas d’entrée correspondante et vérifie que Err est retourné.
cargo test env_template::tests::test_unknown_ev_ref_returns_err
Manuel :
echo "DB=en://nonexistent_key" > .env
enject run -- env
# sortie : Secret 'nonexistent_key' not found in store. Add it with: enject set nonexistent_key
# code de sortie : 1 (le sous-processus `env` n’a jamais été exécuté)
Implémenter un stockage optionnel/additionnel à l’échelle du système pour une maintenance plus facile des secrets utilisés dans plusieurs projets.
Réduire la nécessité de saisir manuellement le mot de passe du stockage à chaque mise à jour.