wardenv
Escrevi este guarda depois de constatar o óbvio tarde demais: o .gitignore impede o commit do .env, não a leitura. Seu agente de codificação nunca vê o valor, e ainda escreve o código certo.
npm install -g wardenv
wardenv installReinicie o agente. O instalador faz backup do seu settings.json antes de tocar em qualquer coisa, é idempotente, e não mexe nos outros hooks. Zero dependências, MIT, 101 testes.
As quatro portas
A maioria dos setups tranca a porta da frente e deixa três abertas.
Leitura direta
Read(.env)
É a porta que todo mundo tranca, e a única que permissions.deny cobre bem.
Shell
cat .env, Get-Content .env, git show HEAD:.env
O alvo ainda está no comando, mas há tantas formas de escrever a mesma coisa que a regra de permissão vira uma lista de padrões mantida para sempre.
Ricochete
printenv, docker compose config, vercel env pull, kubectl get secret
O comando é inocente e não cita arquivo nenhum: o vazamento está na saída. Nenhuma regra de permissão pega, porque não há nada suspeito em que casar.
Exfiltração
a chave viva escrita num config.ts, ou curl -F f=@.env
Corre no sentido inverso. Uma vez que o segredo está em contexto, o agente inline o valor tentando ajudar, ou manda o arquivo inteiro pela rede.
Bloqueia o valor, não o conhecimento
Um guarda que só responde “não” faz o agente adivinhar, contornar e, em uma semana, ser desinstalado. Então o bloqueio devolve a estrutura:
🔒 wardenv: ".env" is a secret file — read blocked.
File structure (names only, values withheld):
DATABASE_URL=<set, 48 chars>
STRIPE_SECRET=<set, 31 chars>O agente aprende que a chave existe, aprende o formato, e escreve process.env.DATABASE_URL corretamente sem nunca ter visto a senha.
Por que um hook, e não permissions.deny
permissions.deny é ignorado sob --dangerously-skip-permissions. Um hook PreToolUse é enforcement de policy, não um prompt de permissão: continua rodando.
Os hooks disparam dentro de subagentes, onde o raio de dano é maior e onde quase nenhum guardrail costuma ser ligado.
O que nunca é bloqueado
Falso positivo é tratado como falha de segurança de primeira classe, com suíte de testes própria. Um guarda que grita lobo é desligado, e um guarda desligado protege zero.
- →
.env.example,.env.samplee.env.templatesão documentação, nunca bloqueados. - →Valores com menos de 12 caracteres nunca são redigidos, senão
NODE_ENV=productionapaga a palavra “production” de todo log que você lê. - →Escrever no
.envé legítimo. O bloqueio é o inverso: segredo saindo para arquivo que não é cofre. - →Falha aberta. Payload malformado, arquivo ilegível ou bug na própria ferramenta deixam a chamada passar: uma ferramenta de segurança que trava sua sessão é desinstalada.
Limites, honestamente
A redação não é hermética: pega os valores conhecidos dos seus arquivos .env e as formas conhecidas de segredo (prefixo de chave da Anthropic e da OpenAI, token do GitHub, access key da AWS, JWT, bloco PEM, connection string com senha). Um segredo em formato exótico que nunca passou por um .env pode escapar. Isso reduz a superfície drasticamente, não zera.
Há adapters para Claude Code, Codex CLI, Gemini CLI, Cursor e GitHub Copilot CLI, mas só o Claude Code é verificado ponta a ponta numa sessão real. O Codex chegou perto: uma sessão viva bloqueou todos os cenários testados, mas o fluxo de unlock e os subagentes ainda não foram exercitados. Gemini, Cursor e Copilot foram conferidos contra o código e a documentação de cada um, não contra uma sessão viva, e o instalador avisa isso quando roda. No Cursor não existe hook de saída, então a porta 3 não fecha lá. O parsing de shell é heurístico: o modelo de ameaça é um agente prestativo tomando o caminho óbvio, não um adversário deliberado.
diferente das outras ferramentas daqui, esta não roda no navegador: é um pacote npm que instala hooks no seu agente, e tudo acontece na sua máquina. o ponto dela é justamente impedir que o segredo saia dali.