O arquivo .env exposto entrega tudo — e é uma URL de distância

O .env é o arquivo mais sensível de um projeto. Ele existe justamente para tirar segredo de dentro do código: senha do banco, chave secreta do gateway de pagamento, token de envio de e-mail, chave de API dos serviços que você usa.

Ele também é um arquivo de texto na pasta do projeto. E é aí que a coisa acontece.

O teste que leva dez segundos

curl -s https://seusite.com.br/.env

Se voltar texto com = no meio das linhas, ele está sendo servido. Qualquer pessoa no mundo pode fazer essa mesma requisição — não precisa de acesso, de login, de nada. É uma URL.

E não pare no primeiro. Estes também aparecem servidos com frequência:

for a in .env .env.local .env.production .env.bak .env.old .env~ .env.save; do
  printf "%-18s " "$a"
  curl -s -o /dev/null -w "%{http_code}\n" "https://seusite.com.br/$a"
done

200 em qualquer um deles é o mesmo problema. O .env~ e o .env.save são sobras de editor — ninguém os cria de propósito, e ninguém lembra de apagá-los.

Como um arquivo desses vai parar na web

Ele não é enviado de propósito. Ele fica no caminho.

A pasta do projeto é a pasta pública. Em hospedagem compartilhada e em muita configuração de PHP, o servidor serve tudo que está na raiz do site. O .env está na raiz do projeto. Se as duas raízes forem a mesma pasta, ele é servido como qualquer outro arquivo.

O deploy copiou a pasta inteira. Um rsync ou um scp do diretório do projeto leva junto o .env local — que costuma ser o de produção.

O build embutiu o valor. Este é o caso mais comum em app moderno, e não depende de arquivo nenhum. Em Next.js, tudo que começa com NEXT_PUBLIC_ vai para o JavaScript do navegador. Em Vite, é VITE_. O prefixo é a forma de dizer "isto é público" — e é fácil colar uma chave secreta ali sem perceber que o prefixo tem esse significado.

# a chave secreta virou parte do bundle
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_...

O .gitignore foi criado depois. O arquivo já tinha sido commitado. Ele some do diretório de trabalho e continua no histórico, acessível a qualquer um que clone o repositório.

Se voltou 200, a ordem importa

O instinto é apagar o arquivo. Apagar é o segundo passo, não o primeiro.

Enquanto o arquivo esteve no ar, ele foi lido — por rastreadores automáticos que varrem a internet inteira procurando exatamente isso, e que rodam o dia todo. Assuma que todo segredo que estava lá é conhecido. Remover o arquivo não desfaz a cópia que alguém já tem.

1. Rotacione tudo o que estava dentro. Senha do banco, chave do gateway, token de e-mail, chave de API de cada serviço. Cada uma no painel do seu provedor. Chave antiga invalidada, chave nova em uso.

2. Aí sim tire o arquivo do caminho. Fora da pasta pública, e bloqueado no servidor por garantia:

# nginx
location ~ /\.env { deny all; return 404; }
# Apache
<FilesMatch "^\.env">
  Require all denied
</FilesMatch>

3. Se ele estiver no Git, limpe o históricogit filter-repo ou o BFG. E rotacione de novo depois, porque enquanto o histórico existiu, ele foi clonado.

4. Olhe os logs de acesso. Procure por requisições a /.env que voltaram 200. Elas dizem se alguém pegou, e quando.

O caso do NEXT_PUBLIC_

Se o problema foi prefixo, não adianta mexer no servidor: o valor está dentro do JavaScript que você entrega ao navegador. Tirar do .env sem refazer o build não muda nada — o bundle antigo continua no ar.

O conserto é mover a chamada para o servidor. Em Next.js, um Route Handler; em Vite com backend próprio, um endpoint seu. O navegador chama o seu servidor, e o seu servidor chama o serviço com a chave secreta — que nunca sai da máquina.

Como saber que ficou resolvido

curl -s -o /dev/null -w "%{http_code}\n" https://seusite.com.br/.env   # 404
curl -s https://seusite.com.br/_next/static/chunks/*.js | grep -o "sk_live_[a-zA-Z0-9]*"

O segundo comando é o que a maioria esquece: um .env bloqueado no servidor não diz nada sobre o que o build já embutiu no JavaScript.

← Todos os posts