Especificação¶
Versão 0.4, 2026-10-14. Itens ainda não verificados estão marcados [a verificar].
1. Objetivo¶
Acabar com a cópia manual de senhas, tokens, chaves e URLs para chats, repositórios, arquivos .env e comandos escritos por um modelo. O agente vê nomes e metadados, nunca valores. A resolução só acontece na hora da execução (late binding), e o acesso expira.
O MCP é a interface de descoberta e chamada. Quem protege e injeta os segredos é o cofre e o runner.
2. Limites honestos¶
- Não protege contra um agente deliberadamente hostil rodando com o seu usuário do sistema.
- "Não exposto ao modelo" não quer dizer "nenhum processo toca o segredo": o processo executado recebe o valor. Cada modo de execução declara a sua garantia (Modelo de segurança).
- O mascaramento da saída é uma defesa, não uma garantia matemática: um valor codificado escapa.
- Fora do escopo: gerenciar IAM da AWS ou do GCP, uso em equipe, monetização.
3. Decisão de base¶
Construir sobre o OpenBao em vez de escrever cofre e daemon do zero. Cada motivo abaixo está verificado na documentação oficial do OpenBao:
- Tokens com TTL, máximo rígido (
-explicit-max-ttl) e limite de usos (-use-limit) (token create). - Response wrapping: token de uso único com TTL próprio (response wrapping).
- Leases com revogação automática; revogar um token revoga os leases criados com ele (lease).
- Login MFA com TOTP embutido (gratuito), além de Duo, Okta e PingID (serviços de terceiros) (login MFA).
- AWS e GCP existem como plugins oficiais externos (openbao-plugins), instalados por um bloco
pluginfixado por digest; não vêm embutidos (plugins). - Binários para Windows, macOS e Linux; imagens de contêiner; interface web (instalação).
Alternativa considerada: KeePassXC com kdbx (MIT). Sem servidor, mas sem expiração de acesso documentada.
4. Requisitos¶
| # | Requisito | Situação |
|---|---|---|
| R1 | Usar um cofre open source existente | Feito (OpenBao) |
| R2 | Servidor MCP local que entrega credenciais a agentes de IA locais | Feito |
| R3 | Variáveis de ambiente simples (AWS, GCP, chaves de LLM) | Feito: valores estáticos, e credenciais dinâmicas de AWS e GCP via credentials |
| R4 | Valores nunca aparecem no chat | Feito |
| R5 | Interface gráfica no servidor | Feito (interface web do OpenBao) |
| R6 | Windows, Linux e macOS | Feito (CI nos três) |
| R7 | Servidor acessível a partir do WSL | Documentado (Windows e WSL) |
| R8 | Acesso temporário: token com expiração e segundo fator gratuito | Feito (token embrulhado + TTL; TOTP no OpenBao) |
| R9 | Referências vault://caminho/CHAVE e placeholders {{...}} em comandos |
Feito |
| R10 | Workspace, projeto e ambiente como caminhos e políticas do OpenBao | Feito (convenção de caminhos + escopo) |
| R11 | Sessão por projeto e cliente, renovável (24 h ou 72 h) | Em parte: TTL por sessão; renovar é emitir um novo token embrulhado |
| R12 | Substituição segura de argumentos, sem concatenar em shell | Feito |
| R13 | Auditoria sem valores | Feito |
| R14 | Contribuir com um projeto upstream | Planejado |
5. Superfície MCP¶
Nomes e ações, nunca valores. Nenhuma tool devolve um valor em claro.
| Tool | Faz | Devolve | Situação |
|---|---|---|---|
keyholder_status |
Estado do cofre e da sessão | Selado ou não, TTL restante, políticas | Implementada |
keyholder_list |
Lista as referências no escopo do projeto | Nomes vault:// e versões |
Implementada |
keyholder_run |
Roda um comando permitido, resolvendo {{vault://...}} em args e env na hora da execução |
Saída mascarada, código de saída, referências usadas | Implementada |
keyholder_request_access |
Pede acesso temporário ao humano | Pendente, aprovado ou negado; nunca o token | Planejada |
Referência campo a campo: Configuração.
6. Modos de execução¶
- Modo A, runner (preferido): o agente envia um comando com referências; outro processo resolve e executa. O valor não passa pela conversa.
- Modo B, ferramentas nativas: AWS CLI, gcloud ou ssh-agent usam os próprios mecanismos. O valor pode ficar no processo da ferramenta e aparecer em saída de depuração; não se promete isolamento absoluto. Verificado: o
gcloudlê um token de acesso do arquivo indicado emCLOUDSDK_AUTH_ACCESS_TOKEN_FILE, que o runner escreve para a execução e apaga depois (tutorial de nuvem). - Modo C, navegador: um componente local preenche um campo. O sigilo depende de o agente conseguir ler a página depois. Fora do MVP.
Uma integração sem late binding seguro precisa declarar o limite em vez de fingir sigilo.
7. Fluxo de acesso temporário¶
- Você entra no OpenBao com TOTP.
- Você emite um token filho com política mínima, TTL, limite de usos opcional e
wrap-ttl. - O token embrulhado chega ao servidor pelo ambiente do cliente MCP, fora do chat.
- O servidor o desembrulha e guarda o token só em memória.
- O
keyholder_runlê o segredo e o injeta no comando. - O token expira; as leituras devolvem 403 e as tools dizem ao agente para pedir renovação. A expiração revoga os leases criados com o token, como a revogação explícita: verificado no OpenBao 2.7.1 e gravado no tutorial de acesso temporário: o lease de PKI de 1 hora de um token de 5 segundos some quando o token expira.
8. Prova de conceito, verificada¶
Contra o bao server -dev 2.7.1, gravada e reconferida pelos tutoriais:
- A API de destino recebeu o valor certo de
Authorization: Bearer, enquanto o agente leu[hidden:vault://my-project/dev/API_TOKEN]. - Nenhum valor guardado e nenhum token do cofre apareceram em resposta MCP alguma.
- Uma referência fora do
KEYHOLDER_SCOPEe um comando fora doKEYHOLDER_ALLOWforam recusados, mesmo com política ampla no cofre. - Depois do TTL, o
keyholder_statusinformou a expiração e okeyholder_runfalhou com acesso negado. - Reusar um token embrulhado falhou no desembrulho.
BAO_*,VAULT_*eKEYHOLDER_*nunca chegam ao processo filho.-
O CI compila e testa em Linux, Windows e macOS.
-
Uma credencial dinâmica criada pelo plugin real de AWS do OpenBao rodou a AWS CLI contra um emulador local da AWS (moto) e foi apagada quando o comando terminou.
-
Clientes MCP reais, com o binário da release v0.2.0: Claude Code (
claude -p) e Codex (codex exec) chamaramkeyholder_status,keyholder_listekeyholder_run; a API recebeu o token guardado, o agente o leu mascarado, o valor não apareceu em nenhuma das transcrições, ecatfoi recusado por não estar permitido. Executado à mão em 2026-10-14; os clientes precisam das próprias credenciais de modelo, então isso não roda no CI. -
Um servidor persistente a partir de
deploy/server.hcl: armazenamento Raft, TLS, init, unseal Shamir, token root revogado depois da configuração, Keyholder por TLS comBAO_CACERT, selado de novo depois de um reinício sem consumir o token embrulhado, backup por snapshot (tutorial, no CI).
Ainda não testado: o plugin do GCP e contas AWS reais.
9. Decisões em aberto¶
- ~~Como fazer o unseal depois de reiniciar: Shamir manual ou auto unseal.~~ Decidido: Shamir manual, 2 de 3 chaves, para o cofre de uma pessoa que é o alvo do Keyholder; o auto unseal fica documentado como opção, sem teste (cofre persistente).
- Segundo fator: TOTP embutido ou serviço externo.
- Credencial do GCP: chave JSON de service account ou token OAuth dinâmico.
- Escrita do agente no cofre: um caminho dedicado, ou nenhuma.
- Entrega do token: hoje
BAO_WRAPPING_TOKENno ambiente do cliente, sem valor depois de desembrulhado. Pode vir um prompt local ou um socket.
Decidido: OpenBao como cofre; Go como linguagem; licença MIT; OpenBao dentro do WSL ou de um contêiner no Windows.
10. Critérios de aceite¶
| # | Critério | Situação |
|---|---|---|
| AC-01 | Um token guardado não aparece no chat, na descrição de uma tool nem em resposta MCP | Atendido |
| AC-02 | Chamada real a uma API com referência: o servidor recebe o token certo, o agente só o resultado | Atendido |
| AC-03 | aws sts get-caller-identity e gcloud projects list funcionam sem o agente escrever chaves |
Atendido para AWS contra um emulador, no CI; tratamento do token do gcloud atendido; plugin do GCP não executado |
| AC-04 | Um projeto vê só as referências do seu escopo | Atendido |
| AC-05 | Dentro da sessão, operações repetidas não pedem nova aprovação; depois da expiração, renovação fora do chat | Atendido |
| AC-06 | Logs, mensagens MCP e artefatos do produto não contêm valores | Atendido |
| AC-07 | Uma integração sem late binding seguro declara o seu limite | Atendido (documentado) |
| AC-08 | Funciona com pelo menos dois clientes MCP sem mudar o armazenamento | Atendido com Claude Code e Codex, à mão (fora do CI) |
11. Roteiro¶
- ~~Prova de conceito e MCP mínimo~~: feito, publicado como v0.1.0.
- ~~Credenciais dinâmicas: plugins de AWS e GCP do OpenBao mapeados para variáveis de ambiente~~: feito na v0.2.0. Credenciais AWS
iam_usersão revogadas depois da execução; STS e tokens do GCP expiram sozinhos. keyholder_request_access: o agente pede, o humano aprova com TOTP, um novo token embrulhado chega ao servidor.- ~~Guia de servidor persistente: armazenamento, TLS e unseal~~: feito na v0.3.0 (cofre persistente).
- Adaptadores de SSH e navegador.
- ~~Upstream: propor "executar com variáveis injetadas"~~: entregue como
keyholder-mcp exec(configuração); acesso temporário a um projeto existente continua.