cleancontext.blog
npm, roda localRead in English

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.

instalação
npm install -g wardenv
wardenv install

Reinicie 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.

01

Leitura direta

Read(.env)

É a porta que todo mundo tranca, e a única que permissions.deny cobre bem.

02

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.

03

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.

04

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.sample e .env.template são documentação, nunca bloqueados.
  • Valores com menos de 12 caracteres nunca são redigidos, senão NODE_ENV=production apaga 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.