
enject: Esconda segredos .env de olhos prAIng: os segredos ficam em armazenamentos locais criptografados (por projeto) e são injetados diretamente nos aplicativos em tempo de execução, nunca tocando no disco como texto simples.
Oculte segredos do .env dos olhos de IAs.
(Nota: este projeto era anteriormente chamado de enveil e foi renomeado para enject)
Ferramentas de codificação com IA como Claude Code, Copilot, Cursor e outras podem ler arquivos no diretório do seu projeto, o que significa que um arquivo .env em texto simples é um vazamento acidental de segredos à espera de acontecer. Isso não é teórico. É um problema conhecido que já me aconteceu várias vezes (mesmo depois de dizer explicitamente ao Claude para não espiar no arquivo settings.json do Claude Code). enject resolve isso garantindo que segredos em texto simples nunca existam em disco. Seu arquivo .env contém apenas referências simbólicas; os valores reais ficam em um armazenamento local criptografado e são injetados diretamente no seu subprocesso na inicialização.
Este projeto é inspirado na solução/postagem no blog de Filip Hric, que usa um conceito semelhante aproveitando o 1Password. Eu queria uma solução autocontida que não dependesse de serviços de terceiros, dando origem a esta solução. E sim, este projeto foi construído quase inteiramente com Claude Code, com muita verificação e testes manuais.
Este projeto foi projetado principalmente para mitigar o problema conhecido de ferramentas de IA/LLM lerem acidentalmente segredos do .env no seu projeto. Benefícios adicionais incluem prevenir o vazamento de segredos se um .env for acidentalmente commitado em um repositório, a capacidade de compartilhar arquivos .env contendo referências em vez de segredos em texto simples, e a opção de compartilhar o próprio armazenamento criptografado.
Este projeto não é uma bala de prata para impedir que um agente de IA obtenha seus segredos. Por exemplo, um agente ainda pode escrever código (acidentalmente ou por injeção de prompt) que exfiltra segredos para a saída do terminal ou para um arquivo em tempo de execução. Recomendamos fortemente não confiar apenas nesta ferramenta, ou em arquivos .env em geral, para armazenar segredos de produção.
Seu arquivo .env fica assim:
DATABASE_URL=en://database_url
STRIPE_KEY=en://stripe_key
PORT=3000
Tecnicamente é seguro commitar (talvez não faça isso, no entanto), e mais importante: seguro para qualquer ferramenta de IA que esteja bisbilhotando acidentalmente (ou talvez não tão acidentalmente).
Quando você executa enject run -- npm start, ele:
en:// contra o mapa descriptografadoO arquivo de armazenamento é um blob binário. Sem a senha mestra, é indistinguível de ruído aleatório. O nonce é gerado novamente a cada gravação, portanto, a reutilização de nonce do AES-GCM é impossível. Qualquer modificação no texto cifrado — mesmo um único bit invertido — faz com que a autenticação falhe e a descriptografia seja recusada.
Este lançamento ainda está em alpha, portanto, é necessário anexar a versão mais recente ao instalar ao chamar cargo install
cargo install enject --version 0.2.0-alpha
Requer Rust 1.70+.
git clone https://github.com/greatscott/enject
cd enject
cargo build --release
O binário compilado está em target/release/enject. Instale-o uma vez em um local no seu PATH para poder executá-lo a partir de qualquer projeto:
macOS / Linux (bash ou zsh)
# Opção A: ~/.local/bin (não requer sudo, comum no Linux)
mkdir -p ~/.local/bin
cp target/release/enject ~/.local/bin/
# Opção B: /usr/local/bin (requer sudo, disponível em todo o sistema)
sudo cp target/release/enject /usr/local/bin/
# Opção C: ~/.cargo/bin (já está no PATH se você usou rustup)
cp target/release/enject ~/.cargo/bin/
Se você usou a opção A e ~/.local/bin ainda não está no seu PATH, adicione isto à sua configuração do shell (~/.zshrc, ~/.bashrc ou ~/.bash_profile):
export PATH="$HOME/.local/bin:$PATH"
Em seguida, recarregue:
source ~/.zshrc # ou ~/.bashrc
Verifique se funcionou:
enject --version
O binário é instalado globalmente — você nunca o reinstala. Mas cada projeto recebe seu próprio armazenamento criptografado:
cd your-project
enject init
Isso cria .enject/ no diretório atual com a configuração do projeto e o armazenamento criptografado. Adicione-o ao .gitignore — nunca deve ser commitado.
Execute isso uma vez por projeto, na raiz do projeto:
enject init
Isso gera um salt aleatório de 32 bytes, escreve .enject/config.toml, cria um armazenamento criptografado vazio em .enject/store e solicita que você defina uma senha mestra. Adicione .enject/ ao seu .gitignore — o armazenamento nunca deve ser commitado.
enject set some_database_url
# solicita: Valor para 'database_url': (oculto)
enject set some_api_key
Os valores são sempre inseridos interativamente. Não há como passar um valor como argumento de linha de comando — isso evita que segredos apareçam no histórico do shell ou na saída do ps.
.envDATABASE_URL=en://some_database_url
MY_API_KEY=en://stripe_key
PORT=3000
Linhas KEY=VALUE simples passam inalteradas. Apenas referências en:// são resolvidas.
enject run -- npm start
enject run -- python manage.py runserver
enject run -- cargo run
Tudo após -- é passado literalmente para o SO. O subprocesso herda todo o seu ambiente de shell (então PATH, HOME, etc. estão presentes) com os valores do .env sobrepostos.
enject list # imprime os nomes das chaves armazenadas (nunca os valores)
enject delete <key> # remove um segredo
enject import <file> # criptografa todos os valores em um .env de texto simples, reescreve-o como modelo en://
enject rotate # re-criptografa o armazenamento com uma nova senha mestra
Não há get e nenhum export. Imprimir um valor secreto na saída padrão cria um vetor de vazamento legível por IA — o objetivo principal do enject é manter os valores fora do disco e de qualquer fluxo de saída legível.
Cada invariante de segurança possui um teste automatizado correspondente e um caminho de inspeção manual.
cargo test
31 testes, todos cobrindo as afirmações abaixo.
Automatizado: store::password::tests::test_encrypt_decrypt_roundtrip
Salva um segredo, persiste o armazenamento, recarrega do disco, descriptografa e verifica se o valor é preservado. Só passa se os bytes no disco forem texto cifrado válido — texto simples falharia na descriptografia.
cargo test store::password::tests::test_encrypt_decrypt_roundtrip
Inspeção manual:
enject init # senha: test123
enject set mykey # valor: my-super-secret
xxd .enject/store | head -5
strings .enject/store
xxd mostrará dados binários. strings não retornará nada — não há sequências ASCII para extrair. Os primeiros 12 bytes são o nonce aleatório; todo o resto é texto cifrado AES-GCM com uma tag de autenticação de 16 bytes anexada.
Automatizado: store::password::tests::test_nonce_changes_on_each_save
Salva o armazenamento duas vezes seguidas, lê os primeiros 12 bytes do arquivo cada vez e afirma que eles diferem.
cargo test store::password::tests::test_nonce_changes_on_each_save
Inspeção manual:
xxd .enject/store | head -1 # observe os primeiros 12 bytes
enject set anotherkey # qualquer gravação rotaciona o nonce
xxd .enject/store | head -1 # os primeiros 12 bytes agora são diferentes
Automatizado: store::password::tests::test_wrong_password_returns_err
Cria um armazenamento com uma senha, então tenta desbloqueá-lo com uma senha diferente e afirma que Err é retornado.
cargo test store::password::tests::test_wrong_password_returns_err
Manual:
enject list # digite a senha errada
# saída: "Senha mestra incorreta ou armazenamento corrompido."
# código de saída: 1
O AES-GCM produz uma tag de autenticação de 16 bytes sobre o texto cifrado. Qualquer modificação — mesmo um único bit invertido — faz com que a verificação falhe antes que a descriptografia prossiga. O texto simples nunca é exposto.
Automatizado: store::password::tests::test_tampered_ciphertext_returns_err
Inverte um byte na região do texto cifrado do arquivo de armazenamento (após o nonce de 12 bytes), então tenta a descriptografia e afirma Err.
cargo test store::password::tests::test_tampered_ciphertext_returns_err
Manual:
# Inverte o byte 20 (dentro do texto cifrado, após o 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
# saída: "Senha mestra incorreta ou armazenamento corrompido."
en:// não resolvidaSe uma referência no .env não tiver uma chave correspondente no armazenamento, enject run sai imediatamente com um código diferente de zero. O subprocesso nunca é iniciado.
Automatizado: env_template::tests::test_unknown_ev_ref_returns_err
Chama resolve() com uma referência que não possui entrada correspondente e afirma Err.
cargo test env_template::tests::test_unknown_ev_ref_returns_err
Manual:
echo "DB=en://nonexistent_key" > .env
enject run -- env
# saída: Segredo 'nonexistent_key' não encontrado no armazenamento. Adicione com: enject set nonexistent_key
# código de saída: 1 (o subprocesso `env` nunca foi executado)
Implementar armazenamento opcional/adicional em todo o sistema para facilitar a manutenção de segredos usados em vários projetos.
Reduzir a necessidade de digitar manualmente a senha do armazenamento ao fazer atualizações.