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órico — git 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.
