Janela de terminal escura com o comando CLAUDE_CODE_OAUTH_TOKEN=$(pass anthropic/oauth-token) claude destacado, sobre um fundo com textura de fingerprint GPG e um cadeado; wordmark 'pass + gpg'.
Janela de terminal escura com o comando CLAUDE_CODE_OAUTH_TOKEN=$(pass anthropic/oauth-token) claude destacado, sobre um fundo com textura de fingerprint GPG e um cadeado; wordmark 'pass + gpg'.

pass + GPG: injetando API keys no terminal sem export nem histórico vazado

Parei de dar export das minhas API keys e de ver sk-ant-... no histórico do shell. Uso pass + GPG e injeto o segredo direto no comando com $(pass anthropic/oauth-token). Este é o guia direto de como faço.

Iago Frota
produtividade

Há uns meses abri o ~/.zshrc para ajustar uma coisa boba e dei de cara com umas quatro export ANTHROPIC_API_KEY=sk-ant-... esquecidas ali, em texto puro. Rodei um history | grep sk- e, cara, o histórico do shell estava lotado de segredo. Chave de API, token de OAuth, senha de banco de dev. Tudo legível.

É engraçado, mas é sério: eu passo o dia falando de qualidade e de risco, e meus segredos estavam mais expostos que post no LinkedIn.

Resolvi arrumar aquilo com pass e GPG. Desde então virou o jeito padrão que uso para rodar o Claude Code e outros CLIs que pedem chave. Vou te contar como funciona e — sendo honesto — o que esse método não resolve.

Bora lá.

O problema: seu segredo mora em texto puro em três lugares

Quando você faz export ANTHROPIC_API_KEY=sk-ant-..., o valor literal da chave vaza para pelo menos três lugares:

  1. O histórico do shell — a linha inteira, com a chave, vai para o ~/.zsh_history ou ~/.bash_history. Fica lá. Em texto puro.
  2. O ambiente da sessão inteira — a variável exportada é herdada por todo processo filho que você abrir naquele terminal, do começo ao fim da sessão.
  3. Seus dotfiles — se você colocou o export no .zshrc/.bashrc para “não digitar de novo”, agora a chave está num arquivo que, com um descuido, sobe para o Git.

Quero destacar que não é paranoia. É o famigerado exemplo do backup que ninguém revisa: o dia que você compartilha a tela, cola um .env no chat errado ou publica um dotfile, a chave vaza junto. É melhor prevenir do que remediar.

Entra o pass (e o GPG por baixo)

O pass se autodenomina “the standard unix password manager”. A ideia é simples ao ponto de ser bonita: cada segredo é um arquivo criptografado com GPG, guardado numa arvorezinha de pastas em ~/.password-store/.

  • Nada de banco de dados, nada de app pesado.
  • Cada entrada é um .gpg. Sem sua chave privada, é só ruído.
  • A hierarquia é livre: anthropic/oauth-token, bancos/dev/postgres, github/token-ci.

O GPG é o carinha que faz o trabalho pesado de cripto. O pass é uma casca fina de shell script por cima dele. Gosto justamente disso: é explícito, sem mágica. Dá para ler o script e entender tudo.

Setup em 4 comandos

Assumindo que você já tem uma chave GPG (se não tiver, gpg --full-generate-key e siga o passo a passo — escolhe RSA 4096 e uma boa passphrase).

Primeiro, descubra o ID da sua chave:

Terminal window
gpg --list-secret-keys --keyid-format=long

Agora inicialize o pass apontando para ela (pode usar o e-mail associado à chave no lugar do ID):

Terminal window
pass init "[email protected]"

Guarde seu primeiro segredo. O insert abre um prompt que não ecoa o que você digita — nada de segredo aparecendo na tela:

Terminal window
pass insert anthropic/oauth-token

E recupere quando precisar:

Terminal window
pass anthropic/oauth-token

Esse último comando imprime a primeira linha da entrada no stdout. É exatamente esse comportamento que vamos usar para resolver o problema do export.

O pulo do gato: $(pass ...) em vez de export

Aqui está a mudança que faz diferença no dia a dia. Em vez de exportar a chave e deixá-la solta na sessão, você injeta o valor só naquele comando, na hora:

Terminal window
CLAUDE_CODE_OAUTH_TOKEN=$(pass anthropic/oauth-token) claude

Um parêntese que importa: aqui eu guardo um OAuth token (aquele que você gera com claude setup-token), então a variável certa é CLAUDE_CODE_OAUTH_TOKEN. Se o seu segredo é uma API key de verdade (sk-ant-...), aí sim o nome é ANTHROPIC_API_KEY. São coisas diferentes — misturar as duas é justamente o tipo de detalhe que passa batido. Guarde cada uma com um nome honesto no pass, tipo anthropic/oauth-token e anthropic/api-key.

Repara no que muda:

  • $(pass anthropic/oauth-token) é resolvido em tempo de execução. O GPG descriptografa, entrega o valor, e ele vive só pelo tempo daquele processo claude.
  • A variável existe apenas para esse comando. Acabou o comando, acabou a variável. Nada fica exportado para sessão.
  • E o mais importante: o que vai para o histórico do shell é a linha literal CLAUDE_CODE_OAUTH_TOKEN=$(pass anthropic/oauth-token) claudenão o valor descriptografado. Seu history | grep -i token volta limpo.

Se você usa o mesmo segredo o tempo todo, joga uma função no .zshrc — e olha que beleza, agora o dotfile guarda a referência, nunca a chave:

Terminal window
# no ~/.zshrc — nenhum segredo em texto puro aqui
claude() {
CLAUDE_CODE_OAUTH_TOKEN=$(pass anthropic/oauth-token) command claude "$@"
}

Esse padrão vale para qualquer CLI faminto por chave: AWS_SECRET_ACCESS_KEY, OPENAI_API_KEY, GITHUB_TOKEN, token de deploy, o que for.

O que isso NÃO resolve — sendo honesto

Skin in the game: não vou te vender isso como bala de prata, porque não é. Uso e gosto, mas conheço os limites.

Quando você faz VAR=$(pass ...) comando, o valor descriptografado vira parte do ambiente daquele processo enquanto ele roda. Ou seja:

  • Um processo do seu próprio usuário (ou o root) consegue ler /proc/<pid>/environ e ver a chave enquanto o comando está de pé. Isso não é blindagem contra máquina comprometida — é redução de superfície, não cofre inviolável.
  • Se você tiver set -x (xtrace) ligado no shell, o comando é expandido no log e a chave aparece. Cuidado com isso em scripts.
  • Ferramentas que logam o próprio ambiente vão logar a chave. A responsabilidade de não fazer besteira continua sua.

O que o pass + $(...) resolve, e resolve bem, é o segredo em texto puro parado em disco e o vazamento para o histórico. Que, na prática, são os dois furos por onde a maioria das chaves vaza no dia a dia. Aceite isso: você está tapando os buracos reais, não construindo Fort Knox.

Bônus: as duas coisas que uso toda semana

Alguns comandos que viraram mão na roda:

Terminal window
# copia para o clipboard e limpa sozinho depois de 45s
pass -c anthropic/oauth-token
# segredo multilinha (chave privada, JSON de service account, etc.)
pass insert -m gcp/service-account

Versionar suas chaves no Git (sim, dá — e faz sentido)

Aqui é onde muita gente trava: “versionar segredo no Git?! Cara, está maluco?”. Calma, respira. O pass foi feito pensando nisso, e a coisa é mais tranquila do que parece.

Como cada entrada já é um arquivo .gpg criptografado, o pass trata o ~/.password-store/ inteiro como um repositório Git normal:

Terminal window
# transforma o cofre num repo Git
pass git init
# aponta para um remoto privado seu
pass git remote add origin [email protected]:voce/store.git
pass git push -u origin master

A partir daí, todo pass insert, pass edit ou pass rm já gera um commit automático. Você só empurra quando quiser:

Terminal window
pass git push

Por que isso é bom no dia a dia:

  • Sincroniza suas máquinas. Notebook do trabalho, desktop de casa, VPS — pass git pull e você tem os mesmos segredos em todo lugar, sem mandar chave por WhatsApp igual sofredor.
  • Histórico de verdade. Trocou um token? O commit registra. Dá para ver quando cada segredo mudou e voltar atrás se precisar.
  • Backup honesto. Se o disco morrer, seu cofre está no remoto. E continua criptografado lá.

O ponto que faz isso ser seguro: o que sobe são só os .gpg, já cifrados. Quem clonar o repositório sem a sua chave privada GPG está segurando um monte de lixo ininteligível. A segurança mora na chave, não na obscuridade do repositório.

Agora, skin in the game de novo — dois cuidados que não dá para ignorar:

  • Use remoto privado. Óbvio, mas preciso dizer: repositório público é não. Mesmo cifrado, você não expõe seu cofre inteiro para o mundo tentar quebrar offline no tempo dele.
  • Sua chave privada GPG é o cofre de verdade. Se você perder a chave, perde o acesso a tudo (por isso, backup dela em lugar seguro). Se alguém roubar a chave e o repositório, aí sim leva tudo. Proteja a privada com uma boa passphrase e trate ela como o item mais sensível de todos.

No fim, é o mesmo princípio de sempre: você move o problema de “proteger N segredos espalhados” para “proteger uma chave só, muito bem”. Bem mais gerenciável.

E onde você guarda a chave privada?

Se tudo passa a depender da sua chave privada GPG, a pergunta óbvia é: e se eu perder ela? Aí perde o acesso a tudo que está no pass. Então vale sentar cinco minutos e resolver isso direito.

Uma coisa a favor: a chave privada já é protegida pela passphrase que você definiu ao criá-la. O arquivo exportado não é texto puro — é cifrado por essa senha. Ainda assim, trate como o item mais sensível que você tem. A regra é simples: duas cópias ou mais, em lugares diferentes. Uma cópia só é um ponto único de falha esperando acontecer.

Primeiro, exporte o que importa:

Terminal window
# descubra o ID
gpg --list-secret-keys --keyid-format=long
# chave privada (protegida pela sua passphrase)
gpg --export-secret-keys --armor SEU_KEYID > gpg-private.asc
# chave pública (não é segredo, mas guarde junto)
gpg --export --armor SEU_KEYID > gpg-public.asc
# certificado de revogação — para "matar" a chave se ela vazar
gpg --gen-revoke --armor --output gpg-revoke.asc SEU_KEYID

Agora, onde colocar. Escolha dois ou mais destes, em locais diferentes:

  • Pendrive criptografado (LUKS ou VeraCrypt), guardado fisicamente — gaveta trancada, casa dos pais, um cofre. Dois pendrives em dois lugares é o padrão simples e que funciona.
  • Backup em papel com o paperkey. Imprime a chave e guarda o papel. Sobrevive a disco queimado e bit rot, que backup digital esquecido numa gaveta costuma sofrer.
Terminal window
sudo apt install paperkey
gpg --export-secret-keys SEU_KEYID | paperkey --output gpg-paper.txt
# imprime o gpg-paper.txt e guarda o papel num lugar seguro
  • Gerenciador de senhas com anexo (Bitwarden, 1Password): dá para anexar o .asc num item. É cômodo para sincronizar, mas aí você depende da segurança daquela conta.

E o que não fazer, para não sabotar todo o resto:

  • Não guarde no mesmo repositório Git do pass.
  • Não jogue em Drive, Dropbox ou qualquer cloud em texto puro.
  • Não deixe a chave sem passphrase — aí o export vira ouro puro para quem achar.

Um detalhe que muita gente esquece: a passphrase é a fechadura do backup, então não guarde ela colada na cópia da chave. Memorize, ou guarde separada num gerenciador de senhas. Chave num lugar, passphrase noutro — quem roubar um não abre o outro.

Se quiser subir de nível, existe o jeito paranoico: uma YubiKey (ou outro token de hardware). A chave privada mora dentro do chip e nunca sai dele. Toda operação GPG acontece no próprio hardware. Roubaram seu notebook? Não levaram a chave — precisariam do token físico e do PIN. Admito que aqui eu ainda estou migrando, mas é o caminho de quem leva a sério.

O elefante na sala: agentes de IA

Deixei o assunto mais importante para o fim, porque é o que mais me fez levar isso a sério. Hoje eu rodo o Claude Code e outros agentes que executam comandos no meu terminal sozinhos. Isso muda completamente o tamanho do estrago que um segredo mal guardado pode causar.

Pensa comigo. Um agente lê arquivos, roda comando, faz curl, às vezes em modo auto-aprovado, sem você confirmar cada passo. Se sua AWS_SECRET_ACCESS_KEY está exportada na sessão de onde você subiu o agente, ele herda essa variável — e todo subprocesso que ele abrir também. Some a isso o famigerado prompt injection: um README malicioso, uma resposta de API envenenada, um issue do GitHub com instruções escondidas. O agente lê aquilo como comando e pode ser induzido a ler /proc/self/environ e mandar suas chaves para fora. Complica, né?

E tem o vazamento silencioso: quase todo agente registra o que faz. Se um segredo aparecer num comando ecoado ou numa saída, ele vai parar no transcript, no log, e muitas vezes é enviado junto para a API do modelo. Sua chave dentro da janela de contexto de uma LLM é o tipo de coisa que você não quer nem imaginar.

Onde o pass ajuda de verdade aqui

O ganho concreto é este: não exporte nada na sessão do agente. Deixe o ambiente dele limpo e injete o segredo só no comando exato que precisa, com $(pass ...):

Terminal window
# ruim: a chave fica no ambiente do agente a sessão inteira
export AWS_SECRET_ACCESS_KEY=...
claude
# bom: o agente sobe sem segredo nenhum no ambiente;
# a chave só existe no comando que realmente usa ela
AWS_SECRET_ACCESS_KEY=$(pass aws/deploy-key) terraform apply

Assim, um agente fazendo tarefa genérica não fica com suas credenciais de deploy paradas no ambiente dele o tempo todo, à disposição de qualquer instrução injetada.

Agora, skin in the game, porque isso é importante e muita gente vende ilusão: se você faz CLAUDE_CODE_OAUTH_TOKEN=$(pass ...) claude, essa chave está no ambiente do próprio agente — afinal é ela que autentica ele. O pass não protege o agente dele mesmo. O que o pass faz bem é manter todos os outros segredos fora do alcance do ambiente dele.

Dicas que uso

  • Nunca dê export no shell de onde você lança o agente. Ele herda tudo. Ambiente limpo, sempre.
  • Encurte o cache do gpg-agent. Por padrão ele lembra sua passphrase por um tempo generoso — o que significa que um agente sequestrado poderia descriptografar segredos por horas sem te pedir nada. Aperte isso no ~/.gnupg/gpg-agent.conf:
default-cache-ttl 60
max-cache-ttl 120
Terminal window
gpg-connect-agent reloadagent /bye
  • Trate o prompt da passphrase como um ponto de controle. É chato digitar? É. Mas é justamente ali que você, humano, entra no meio: o agente não decifra nada sozinho sem você destravar o gpg-agent. Essa fricção é uma feature, não um bug.
  • Cuidado com o auto-aprovar. Se o agente roda qualquer comando sem confirmação, o pass vira sua última linha de defesa, não a única. Reveja o que você libera.
  • Rotacione o que o agente tocou. Desconfiou de um comportamento estranho? Troca a chave. É barato e dorme melhor.

A motivação, no fim, é essa: agente autônomo multiplica o alcance de qualquer segredo exposto. Antes, uma chave vazada era um problema; agora ela pode virar um problema automatizado, rápido, rodando enquanto você toma café. A disciplina de manter os segredos cifrados e injetá-los sob demanda encolhe drasticamente o que um agente comprometido consegue pegar. Não custa quase nada e muda tudo.

Por fim

No fim do dia, a troca é essa: em vez de deixar sk-ant-... em texto puro no .zshrc e no histórico, você deixa uma referência$(pass anthropic/oauth-token) — e o segredo de verdade fica criptografado, decifrado só na hora exata em que o comando precisa dele.

Não é perfeito, e eu fiz questão de mostrar onde ele não te cobre. Mas fecha os dois furos que mais aparecem na vida real, custa quatro comandos de setup, e o GPG faz o trabalho sujo por baixo sem você pensar nele.

Se você ainda tem chave exportada no dotfile, o teste é rápido: roda um history | grep -iE 'key|token|secret' aí no seu terminal. Se voltar alguma coisa legível, já sabe por onde começar.