
enject: Nascondi i segreti .env dagli occhi indiscreti: i segreti risiedono in archivi crittografati locali (per progetto) e vengono iniettati direttamente nelle app in fase di esecuzione, senza mai toccare il disco come testo in chiaro.
Nascondi i segreti del tuo .env dagli occhi delle AI.
(Nota: questo progetto in precedenza si chiamava enveil ed è stato rinominato in enject)
Strumenti di AI per la scrittura di codice come Claude Code, Copilot, Cursor e altri possono leggere i file nella directory del tuo progetto, il che significa che un file .env in chiaro è un dump di segreti accidentale in attesa di accadere. Non è teorico. È un problema noto che mi è successo diverse volte (anche dopo aver detto esplicitamente a Claude di non sbirciare nel file settings.json di Claude Code). enject risolve a questo garantendo che i segreti in chiaro non esistano mai su disco. Il tuo file .env contiene solo riferimenti simbolici; i valori reali risiedono in un archivio locale crittografato e vengono iniettati direttamente nel tuo sottoprocesso all'avvio.
Questo progetto è ispirato alla soluzione / articolo del blog di Filip Hric, che utilizza un concetto simile basato su 1Password. Volevo una soluzione autonoma che non dipendesse da servizi di terze parti, da cui è nata questa soluzione. E sì, questo progetto è stato costruito quasi interamente con Claude Code con un sacco di verifica manuale e test.
Questo progetto è progettato principalmente per mitigare il problema noto degli strumenti AI/LLM che leggono accidentalmente i segreti del tuo .env. Vantaggi aggiuntivi includono la prevenzione della fuga di segreti se un .env viene accidentalmente committato in un repository, la possibilità di condividere file .env contenenti riferimenti invece di segreti in chiaro, e l'opzione di condividere l'archivio crittografato stesso.
Questo progetto non è un proiettile d'argento per impedire a un agente AI di ottenere i tuoi segreti. Ad esempio, un agente può comunque scrivere codice (per errore o tramite injection di prompt) che esfiltra segreti nell'output del terminale o in un file in fase di esecuzione. Sconsigliamo vivamente di fare affidamento su questo strumento, o sui file .env in generale, per memorizzare segreti di produzione.
Il tuo file .env appare così:
DATABASE_URL=en://database_url
STRIPE_KEY=en://stripe_key
PORT=3000
Tecnicamente è sicuro da committare (anche se forse è meglio non farlo), e soprattutto: sicuro per qualsiasi strumento AI che accidentalmente (o forse non così accidentalmente) vi sbirci dentro.
Quando esegui enject run -- npm start, questo:
en:// nella mappa decrittografataIl file dell'archivio è un blob binario. Senza la password principale, è indistinguibile da rumore casuale. Il nonce viene generato fresco a ogni scrittura, quindi il riutilizzo del nonce AES-GCM è impossibile. Qualsiasi modifica al testo cifrato — anche un singolo bit capovolto — causa il fallimento dell'autenticazione e la decrittografia viene rifiutata.
Questa versione è ancora in alpha, quindi richiede di aggiungere la versione più recente all'installazione quando si chiama cargo install
cargo install enject --version 0.2.0-alpha
Richiede Rust 1.70+.
git clone https://github.com/greatscott/enject
cd enject
cargo build --release
Il binario compilato si trova in target/release/enject. Installalo una volta in una posizione nel tuo PATH in modo da poterlo eseguire da qualsiasi progetto:
macOS / Linux (bash o zsh)
# Opzione A: ~/.local/bin (non richiede sudo, comune su Linux)
mkdir -p ~/.local/bin
cp target/release/enject ~/.local/bin/
# Opzione B: /usr/local/bin (richiede sudo, disponibile a livello di sistema)
sudo cp target/release/enject /usr/local/bin/
# Opzione C: ~/.cargo/bin (già nel PATH se hai usato rustup)
cp target/release/enject ~/.cargo/bin/
Se hai usato l'opzione A e ~/.local/bin non è già nel tuo PATH, aggiungi questa riga alla configurazione della tua shell (~/.zshrc, ~/.bashrc o ~/.bash_profile):
export PATH="$HOME/.local/bin:$PATH"
Poi ricaricala:
source ~/.zshrc # o ~/.bashrc
Verifica che funzioni:
enject --version
Il binario è installato globalmente — non devi mai reinstallarlo. Ma ogni progetto ha il proprio archivio crittografato:
cd your-project
enject init
Questo crea .enject/ nella directory corrente con la configurazione del progetto e l'archivio crittografato. Aggiungilo a .gitignore — non dovrebbe mai essere committato.
Esegui questo una volta per progetto, nella root del progetto:
enject init
Questo genera un sale casuale di 32 byte, scrive .enject/config.toml, crea un archivio crittografato vuoto in .enject/store e ti chiede di impostare una password principale. Aggiungi .enject/ al tuo .gitignore — l'archivio non dovrebbe mai essere committato.
enject set some_database_url
# richiede: Valore per 'database_url': (nascosto)
enject set some_api_key
I valori vengono sempre inseriti in modo interattivo. Non c'è modo di passare un valore come argomento della riga di comando — questo impedisce ai segreti di apparire nella cronologia della shell o nell'output di ps.
.envDATABASE_URL=en://some_database_url
MY_API_KEY=en://stripe_key
PORT=3000
Le righe CHIAVE=VALORE semplici vengono passate invariate. Solo i riferimenti en:// vengono risolti.
enject run -- npm start
enject run -- python manage.py runserver
enject run -- cargo run
Tutto ciò che viene dopo -- viene passato letteralmente al sistema operativo. Il sottoprocesso eredita il tuo ambiente shell completo (quindi PATH, HOME, ecc. sono presenti) con i valori di .env sovrapposti.
enject list # stampa i nomi delle chiavi memorizzate (mai i valori)
enject delete <key> # rimuove un segreto
enject import <file> # crittografa tutti i valori in un .env in chiaro, lo riscrive come template en://
enject rotate # ricrittografa l'archivio con una nuova password principale
Non esiste get né export. Stampare un valore segreto su stdout crea un vettore di fuga leggibile dall'AI — l'intero scopo di enject è mantenere i valori fuori dal disco e da qualsiasi flusso di output leggibile.
Ogni invariante di sicurezza ha un corrispondente test automatizzato e un percorso di ispezione manuale.
cargo test
31 test, tutti che coprono le affermazioni seguenti.
Automatizzato: store::password::tests::test_encrypt_decrypt_roundtrip
Salva un segreto, persiste l'archivio, lo ricarica dal disco, lo decrittografa e verifica che il valore sia corretto. Funziona solo se i byte su disco sono testo cifrato valido — il testo in chiaro fallirebbe la decrittografia.
cargo test store::password::tests::test_encrypt_decrypt_roundtrip
Ispezione manuale:
enject init # password: test123
enject set mykey # valore: my-super-secret
xxd .enject/store | head -5
strings .enject/store
xxd mostrerà dati binari. strings non restituirà nulla — non ci sono sequenze ASCII da estrarre. I primi 12 byte sono il nonce casuale; tutto il resto è testo cifrato AES-GCM con un tag di autenticazione di 16 byte in appendice.
Automatizzato: store::password::tests::test_nonce_changes_on_each_save
Salva l'archivio due volte di fila, legge i primi 12 byte del file ogni volta e verifica che siano diversi.
cargo test store::password::tests::test_nonce_changes_on_each_save
Ispezione manuale:
xxd .enject/store | head -1 # nota i primi 12 byte
enject set anotherkey # qualsiasi scrittura ruota il nonce
xxd .enject/store | head -1 # i primi 12 byte ora sono diversi
Automatizzato: store::password::tests::test_wrong_password_returns_err
Crea un archivio con una password, poi tenta di sbloccarlo con una password diversa e verifica che venga restituito Err.
cargo test store::password::tests::test_wrong_password_returns_err
Manuale:
enject list # inserisci la password sbagliata
# output: "Password principale errata o archivio corrotto."
# exit code: 1
AES-GCM produce un tag di autenticazione di 16 byte sul testo cifrato. Qualsiasi modifica — anche un singolo bit capovolto — causa il fallimento della verifica prima che la decrittografia proceda. Il testo in chiaro non viene mai esposto.
Automatizzato: store::password::tests::test_tampered_ciphertext_returns_err
Capovolge un byte nella regione del testo cifrato del file dell'archivio (oltre il nonce di 12 byte), poi tenta la decrittografia e verifica Err.
cargo test store::password::tests::test_tampered_ciphertext_returns_err
Manuale:
# Capovolge il byte 20 (all'interno del testo cifrato, oltre il 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
# output: "Password principale errata o archivio corrotto."
en:// non risoltoSe un riferimento in .env non ha una chiave corrispondente nell'archivio, enject run esce immediatamente con un codice diverso da zero. Il sottoprocesso non viene mai avviato.
Automatizzato: env_template::tests::test_unknown_ev_ref_returns_err
Chiama resolve() con un riferimento che non ha una voce corrispondente e verifica Err.
cargo test env_template::tests::test_unknown_ev_ref_returns_err
Manuale:
echo "DB=en://nonexistent_key" > .env
enject run -- env
# output: Il segreto 'nonexistent_key' non è stato trovato nell'archivio. Aggiungilo con: enject set nonexistent_key
# exit code: 1 (il sottoprocesso `env` non è mai stato eseguito)
Implementare un archivio opzionale/aggiuntivo a livello di sistema per una manutenzione più semplice dei segreti utilizzati in più progetti.
Ridurre la necessità di inserire manualmente la password dell'archivio ogni volta che si effettuano aggiornamenti.