25 out 2026 · linux, seguranca · 4 min
Como guardar segredo em variável de ambiente sem vazar?
Um segredo em código ou em log já é segredo perdido. O .env resolve o armazenamento, mas só se a permissão do arquivo e o .gitignore estiverem corretos. O script abaixo cria o arquivo com modo 600, gera o .gitignore e testa se o valor aparece em um log. Veja também cron do jeito certo para como o cron herda (ou não) variáveis de ambiente, e container sem root para rodar o serviço sem privilégio.
O que vaza e por quê
Três caminhos costumam vazar um segredo: o arquivo .env entra no git porque ninguém adicionou a regra de ignore; o log da aplicação imprime a variável inteira durante o debug; e o segredo fica em uma variável de shell exportada em um script que roda como root e é lido por outro usuário. O primeiro é o mais comum e o mais barato de evitar.
A regra do Twelve-Factor ajuda a pensar: configuração varia entre ambientes, código não. Segredo é configuração, então não pertence ao repositório. Um .env fora do git com permissão 600 cobre o caso de um único host. Para vários hosts ou rotação frequente, um gerenciador de segredos (Vault, SOPS, o cofre do provedor) passa a valer o custo.
O artefato
```bash
!/usr/bin/env bashset -euo pipefail
DEMO=$(mktemp -d) cd “$DEMO”
1. arquivo de segredos com permissão restritaumask 077 cat > .env <<’ENV’ APP_SECRET=sk_live_9f8e7d6c5b4a3210 DB_PASSWORD=hunter2-nao-use-isso ENV chmod 600 .env
echo “=== permissão do .env ===” stat -c ‘%a %U:%G %n’ .env
2. .gitignore precisa cobrir .envif [ ! -f .gitignore ] || ! grep -qxF ‘.env’ .gitignore; then printf ‘%s\n’ ‘.env’ ‘.env.*’ ‘!.env.example’ >> .gitignore fi echo “=== .gitignore ===” cat .gitignore
3. leitura mascarada: nunca imprime o segredo inteiromask() { local v=”$1” if [ “${#v}” -le 4 ]; then printf ‘****’ else printf ‘%s…%s’ “${v:0:2}” “${v: -2}” fi }
set -a
shellcheck disable=SC1091. ./.env set +a
echo “=== variáveis carregadas (mascaradas) ===” printf ‘APP_SECRET=%s\n’ “$(mask “$APP_SECRET”)” printf ‘DB_PASSWORD=%s\n’ “$(mask “$DB_PASSWORD”)”
4. teste de vazamento em log: o log nunca pode conter o valor brutoLOG=”$DEMO/run.log” { printf ‘inicio %s\n’ “$(date -Is)” printf ‘APP_SECRET=%s\n’ “$(mask “$APP_SECRET”)” printf ‘fim\n’ } > “$LOG”
echo “=== teste de vazamento ===” if grep -Fq “$APP_SECRET” “$LOG” || grep -Fq “$DB_PASSWORD” “$LOG”; then echo “FALHA: segredo encontrado no log” exit 1 else echo “ok: nenhum segredo bruto no log” fi
5. exemplo de unit systemd (não executa aqui; é o que o leitor cola)echo “=== unit de exemplo (EnvironmentFile) ===” cat <<’UNIT’ [Service] EnvironmentFile=/etc/meu-app/.env ExecStart=/usr/local/bin/meu-app UNIT
echo “=== pronto ===”```
O script usa umask 077 antes de criar o .env, então o arquivo nasce com modo 600 mesmo se o umask do shell estiver relaxado. A leitura é mascarada: a função mask mostra apenas os dois primeiros e os dois últimos caracteres. O teste final procura o valor bruto no log e falha se encontrar.
Como verificar
Rode bash env.sh. A saída esperada inclui a linha 600 no stat e ok: nenhum segredo bruto no log. Em um repositório real, confirme também com git status --porcelain --ignored, que deve listar !! .env.
Se der errado
.envaparece emgit status: a regra de ignore foi adicionada depois que o arquivo já estava rastreado. Usegit rm --cached .enve commit; a regra só vale para arquivos ainda não rastreados.Permission deniedao ler o .env: o processo roda como outro usuário. Mude o dono do arquivo comchownpara o usuário do serviço, mantendo o modo 600, ou use o cofre do systemd comLoadCredential.- Segredo no log mesmo com mask: algum outro ponto imprime
os.environinteiro ou o framework registra a requisição com headers. Procure porprint(env)e por middlewares de log.
.env, shell ou cofre
| Abordagem | Quando usar | Limite |
|---|---|---|
| Arquivo .env com modo 600 | Um host, um serviço, segredo estável | Rotação manual, sem auditoria |
| Variável no shell ou no crontab | Comando único e efêmero | Fica no histórico e no ps |
| Gerenciador de segredos | Vários hosts ou rotação automática | Mais uma dependência para operar |
FAQ
Posso commitar um .env.example? Sim, desde que contenha apenas nomes de variáveis e valores de exemplo sem segredo. O script adiciona !.env.example para isso.
systemd lê .env diretamente? Sim, com EnvironmentFile=/etc/meu-app/.env. A linha de exemplo no script mostra a unit. O systemd ignora linhas que começam com # e exige KEY=VALUE sem export.
chmod 600 basta? Para um host com um único usuário de serviço, sim. Se vários usuários precisam ler, use grupo dedicado com modo 640 e um grupo de serviço, não 644.
Como rotacionar sem parar o serviço? Atualize o .env e recarregue o serviço (systemctl reload ou o mecanismo do app). Se o app lê o arquivo só no boot, será preciso reiniciar.
Vale a pena criptografar o .env no git? Para um time pequeno, não. Se precisar versionar segredo, use SOPS ou git-crypt, não o arquivo puro.
Fontes
- systemd.exec (EnvironmentFile), consultado em 25 out 2026
- gitignore, consultado em 25 out 2026
- Twelve-Factor App, config, consultado em 25 out 2026
- OWASP Secrets Management, consultado em 25 out 2026