Uma extensão para o password-store (pass) que obscurece a árvore de diretórios e nomes de serviços mantendo a estrutura original do pass.
Diferente do pass-tomb (que necessita de volumes criptografados via Loopback e privilégios de superusuário), o pass-secrets utiliza mapeamentos criptografados (.secrets.gpg e .mask.gpg) baseados na chave GPG de cada diretório. Os serviços e pastas usam codinomes aleatórios, e a associação real é guardada no mapa por identidade.
Versão atual: 2.6.0
No pass tradicional, os nomes de pastas e arquivos são visíveis no sistema de arquivos. O pass-secrets permite que você renomeie subdiretórios e entradas reais para codinomes aleatórios (ex: Zovar/Kelip.gpg) e mantenha um mapa criptografado associando o codinome ao serviço real.
Uma identidade é qualquer diretório na árvore do pass que possua seu próprio arquivo .gpg-id, independente da profundidade. Nomes de identidade devem ser únicos em toda a árvore. Dois diretórios com .gpg-id e o mesmo nome tornam o comando ambíguo e são recusados.
- Fronteira de Confiança: Uma identidade aninhada dentro de outra NÃO herda as chaves da identidade pai.
- Isolamento Total: Comprometer a chave da identidade pai não expõe o conteúdo da filha.
- Proteção contra cruzamento de fronteira:
generateenamegenrecusam qualquer bloco/caminho que atravesse o diretório de outra identidade aninhada — sem essa checagem, uma senha poderia ser cifrada com a chave da identidade errada.
Se PASSWORD_STORE_SIGNING_KEY estiver configurado (mesma variável usada pelo pass nativo), o pass-secrets exige um .gpg-id.sig válido antes de aceitar os destinatários de uma identidade — bloqueando substituição ou injeção de chave no .gpg-id. Sem a variável configurada, o comportamento é idêntico ao pass puro (sem verificação).
# 1. Crie o diretório de extensões do pass (caso não exista)
mkdir -p "${PASSWORD_STORE_EXTENSIONS_DIR:-$HOME/.password-store/.extensions}"
# 2. Copie o script para a pasta de extensões
cp secrets.bash "${PASSWORD_STORE_EXTENSIONS_DIR:-$HOME/.password-store/.extensions}/secrets.bash"
# 3. Torne o script executável
chmod +x "${PASSWORD_STORE_EXTENSIONS_DIR:-$HOME/.password-store/.extensions}/secrets.bash"
# 4. Habilite extensões no seu shell (.bashrc, .zshrc, etc.)
export PASSWORD_STORE_ENABLE_EXTENSIONS=trueTodos os comandos seguem a sintaxe: pass secrets <identidade> <subcomando> [argumentos].
| Comando | Descrição |
|---|---|
pass secrets <id> dir <bloco> |
Lista entradas cujo caminho começa com o bloco informado. |
pass secrets <id> word <termo> [contexto] |
Busca um termo no mapa visualizando linhas de contexto (grep -C). |
pass secrets <id> count <bloco> |
Retorna a contagem de entradas sob o bloco especificado. |
pass secrets <id> struct |
Exibe a estrutura real de codinomes em disco via scan (sem decifrar). |
pass secrets <id> version |
Exibe a versão instalada da extensão. |
| Comando | Descrição |
|---|---|
pass secrets <id> add <caminho> |
Associa manualmente um codinome já existente. O nome real é pedido via prompt, nunca por argumento, para evitar exposição no histórico do shell. |
pass secrets <id> edit |
Edita o .secrets.gpg decifrando para um arquivo temporário na memória (via /dev/shm), abre com o seu $EDITOR e recifra. Não depende de ferramentas de terceiros (o suporte via vim-gnupg foi removido). |
pass secrets <id> check |
Audita o mapa contra a árvore real de arquivos, como somente leitura (equivale a rebuild --dry-run). Além de novas/órfãs, reporta colisão global de nomes de identidade, entradas de mask apontando pra diretório inexistente, e nomes reais duplicados entre codinomes diferentes. |
pass secrets <id> rebuild [--yes] [--prune] |
Varre a árvore real e reconcilia o mapa. |
- Flags do
rebuild:--yes: Não pergunta o nome real para novas entradas (insere como(pendente)).--prune: Remove entradas órfãs do mapa.
| Comando | Descrição |
|---|---|
pass secrets <id> namegen [bloco] [-n tamanho] [-u quantidade] |
Sugere codinomes livres de um tamanho especificado, sem criar arquivos. Checa colisões apenas dentro da mesma identidade. Blocos que atravessam identidades aninhadas são recusados, mesma proteção do generate abaixo. |
pass secrets <id> generate [bloco] [tamanho] [flags] |
Gera um codinome livre e já cria a entrada real via comando nativo pass generate (repassando as [flags]). Não registra a associação de nome, exigindo o uso de add posteriormente. Blocos que atravessam identidades aninhadas são recusados para evitar cifrar com a chave GPG errada. |
O mapa .mask.gpg permite associar aliases (ex: e-mails descartáveis) a diretórios em uma relação muitos-para-muitos (many-to-many).
| Comando | Descrição |
|---|---|
pass secrets <id> mask add <dir> |
Associa um alias de e-mail a um diretório. O alias é pedido via prompt interativo em vez de argumento. |
pass secrets <id> mask dir <dir> |
Lista os aliases associados a um diretório. |
pass secrets <id> mask word <termo> [ctx] |
Busca um alias ou diretório no .mask.gpg. |
pass secrets <id> mask list |
Lista todo o conteúdo do .mask.gpg. |
pass secrets <id> mask edit |
Edita o .mask.gpg utilizando o mesmo mecanismo seguro do comando edit principal. |
O nome real (add) e o alias (mask add) nunca são aceitos como argumento — só via prompt, para não ficarem em ~/.bash_history ou visíveis via ps aux. Isso tem uma consequência direta em automação: onde o script precisaria de uma confirmação humana real, ele recusa prosseguir em vez de assumir uma resposta padrão silenciosamente.
pass secrets <id> add <caminho>sobre um caminho já associado: exige confirmação interativa ([y/N]). Fora de um terminal, é recusado — nunca sobrescreve silenciosamente.pass secrets <id> rebuildsem--yes: pergunta o nome real de cada entrada nova. Se a entrada padrão terminar (EOF) antes de responder, o comando morre com erro em vez de gravar(pendente)silenciosamente — evita confundir "ninguém respondeu" com "usuário aceitou o padrão". Use--yesexplicitamente para automação.pass secrets <id> edit/mask edit: se a cifragem falhar (ex.:.gpg-idcorrompido ou apontando pra chave inexistente) e a entrada não for interativa, o comando morre imediatamente em vez de ficar tentando de novo indefinidamente.
Em todos os três casos, o comportamento interativo normal (perguntar e esperar sua resposta num terminal de verdade) não muda em nada.
Os arquivos .secrets.gpg e .mask.gpg são mantidos criptografados em disco usando a chave GPG definida no .gpg-id local. O formato em texto plano, antes da cifra, segue:
-
Formato de
.secrets.gpg(associação 1:1 por caminho):<caminho-do-codinome-relativo-a-identidade> = <nome real / descrição>(Exemplo:
a1/b2 = Servidor Produção - SSH) -
Formato de
.mask.gpg(associação N:N por alias/diretório):<alias> = <caminho-dir>(Exemplo:
alias1@domain.com = servicos/financeiro)
- O script audita o sistema de arquivos e exige permissão exatamente
600nos arquivos de mapa criptografados (não aceita640nem qualquer outro valor). - O ciclo de vida dos arquivos temporários nas edições (
editemask edit) é inteiramente gerenciado pela extensão, limpando o rastro com segurança (viashred/rmatrelado a umtrapdo shell) sem depender de plugins como ovim-gnupg. - O script recusa caminhos e blocos de geração de senhas que atravessam diretórios de identidades aninhadas, garantindo que arquivos nunca sejam cifrados pela chave de uma identidade indesejada.
- Integração nativa com o Git do
pass: todas as modificações nos mapas via script geram commits automáticos no repositório. - Todas as entradas do usuário passam por checagens de validação de path traversal (
check_sneaky_paths) e sanitização de tokens.
Todas as mensagens do pass-secrets ficam em arquivos-fonte simples, um por idioma, na pasta i18n/ (pt.json, en.json, es.json, ru.json). São arquivos JSON comuns — não precisa saber bash nem entender o script pra traduzir.
Para corrigir uma tradução existente:
- Abra o
.jsondo idioma em qualquer editor de texto. - Edite o texto entre aspas da chave que quiser corrigir. Mantenha a mesma quantidade de
%sque o original — são placeholders preenchidos em runtime (nome de identidade, caminho, etc.). - Rode
python3 tools/gen-i18n.py secrets.bashpra aplicar a mudança dentro do script. - Rode
bash -n secrets.bashpra conferir que a sintaxe continua válida. - Abra um Pull Request.
Para adicionar um idioma novo que ainda não existe:
- Copie
i18n/en.jsonparai18n/<código-do-idioma>.json(ex:i18n/fr.jsonpara francês) como ponto de partida. - Traduza os valores — não precisa ser tudo de uma vez. Qualquer chave que você deixar sem traduzir cai automaticamente para inglês (e depois para português) até alguém completar.
- Não esqueça da chave especial
_usage_full— é o texto completo do--helpnaquele idioma. Use{PROG}no lugar de "pass secrets" (isso é substituído automaticamente em runtime). - Rode
python3 tools/gen-i18n.py secrets.bash— ele detecta o arquivo novo sozinho, não precisa editar mais nada. - Teste com
PASS_SECRETS_LANG=<código> pass secrets <identidade> ...e confira que as chaves traduzidas saem certas e as faltantes caem no fallback. - Abra um Pull Request com o
.jsonnovo e osecrets.bashregenerado.
Nenhuma dessas etapas exige conhecimento de bash. tools/gen-i18n.py só precisa de Python 3 (nenhuma dependência externa) e é a única ferramenta necessária pra ir do .json traduzido até o secrets.bash final.
Você também pode usar o weblate para ajudar a traduzir.
- Relatar bugs e comportamentos inesperados — principalmente em contexto não-interativo (scripts, cron), fronteiras entre identidades aninhadas, e qualquer coisa que pareça um caso extremo não coberto.
- Revisar segurança — todo o modelo de ameaça do projeto (isolamento por identidade, verificação de assinatura do
.gpg-id, validação de path traversal) está aberto a escrutínio; um PR com prova prática de uma falha (não só teoria) é sempre bem-vindo. - Revisar traduções existentes — mesmo sem ser tradutor "oficial" de um idioma, sinalizar uma tradução estranha ou incorreta via issue já ajuda.
Este projeto é disponibilizado sob a mesma licença do projeto password-store (GPLv2+).