Skip to content

Configuração

O keyholder-mcp não tem arquivo de configuração nem flags além de -version. Tudo vem de variáveis de ambiente definidas pelo cliente MCP que o inicia.

Variáveis de ambiente

Variável Padrão Significado
BAO_ADDR https://127.0.0.1:8200 (padrão do cliente OpenBao) Endereço do cofre
BAO_WRAPPING_TOKEN — Token embrulhado, de uso único. Preferido. Desembrulhado uma vez ao iniciar
BAO_TOKEN — Token comum. Aceito para testes locais; prefira o embrulhado
KEYHOLDER_KV_MOUNT secret Mount do KV v2 que guarda os segredos
KEYHOLDER_SCOPE vazio (tudo) Prefixo de caminho do projeto. Referências fora dele são recusadas e não aparecem na listagem. Vazio gera um aviso no log
KEYHOLDER_ALLOW vazio (nada) Comandos que o agente pode rodar, separados por vírgula, pelo nome exato: curl,aws,gcloud
KEYHOLDER_DYNAMIC vazio (nenhum) Caminhos do OpenBao de segredos dinâmicos que o agente pode usar, separados por vírgula, pelo caminho exato: aws/creds/deploy,gcp/roleset/viewer/token
KEYHOLDER_TIMEOUT 5m Tempo máximo por execução (duração do Go: 30s, 2m)

TLS e outras opções do cliente seguem as variáveis da CLI do OpenBao lidas pelo cliente oficial em Go (BAO_CACERT, BAO_CLIENT_CERT, BAO_CLIENT_KEY, BAO_NAMESPACE...). Esse cliente aceita os nomes VAULT_* como alternativa.

Ao iniciar, o servidor remove BAO_WRAPPING_TOKEN, BAO_TOKEN e VAULT_TOKEN do próprio ambiente, e todo processo filho recebe um ambiente sem BAO_*, VAULT_* e KEYHOLDER_*.

Nunca libere shells ou interpretadores

KEYHOLDER_ALLOW não pode ter sh, bash, zsh, cmd, powershell, python, python3, node, env, printenv nem nada que execute código recebido. Com eles o agente consegue imprimir ou codificar um segredo; veja Mascaramento e seu limite.

Referências

Uma referência aponta para um campo de um segredo no KV:

vault://my-project/dev/API_TOKEN
        └─ caminho ──┘ └─chave─┘
  • caminho: o caminho do segredo dentro do mount (secret/my-project/dev → my-project/dev).
  • chave: o campo dentro do segredo.
  • Os segmentos usam A-Z a-z 0-9 _ . -; segmento vazio, . e .. são recusados.
  • O valor precisa ser texto.

No keyholder_run, a referência vai dentro de um placeholder, {{vault://caminho/CHAVE}}, em qualquer ponto de um argumento, do valor de uma variável ou do valor de um arquivo. Cabem vários placeholders numa mesma string.

Uma referência dinâmica aponta para um campo de um segredo que o OpenBao cria quando ele é lido, como uma credencial da AWS:

bao://aws/creds/deploy#access_key
      └── caminho ──┘ └─ campo ─┘

O caminho é relativo à raiz do OpenBao, não ao mount do KV, e precisa estar em KEYHOLDER_DYNAMIC. Numa execução cada caminho é lido uma vez, então #access_key e #secret_key vêm da mesma credencial. Veja Credenciais dinâmicas.

Tools

keyholder_status

Sem entrada. Saída:

Campo Significado
addr Endereço do cofre
initialized, sealed, server_version De sys/health
kv_mount, scope A configuração em uso
token_ttl_seconds, token_expire_time Tempo de vida restante do token da sessão
token_renewable, token_policies Da consulta ao token
session_error Por que não há sessão utilizável: sem token, desembrulho recusado, expirado ou sem permissão para consultar a si mesmo

Nunca devolve o token nem o accessor.

keyholder_list

Sem entrada. Saída: scope, entries (cada uma com ref e version), dynamic (cada caminho dinâmico permitido com uma dica use, por exemplo credentials: ["aws:aws/creds/deploy"]; a listagem nunca o lê) e um note para o agente. Percorre o escopo (até 500 entradas), lendo cada segredo para saber os nomes dos campos e descartando os valores.

keyholder_run

Entrada:

Campo Tipo Significado
command texto Nome de um comando permitido. Sem caminho, sem placeholder
args lista de textos Argumentos; podem conter {{vault://...}} e {{bao://...}}
env mapa Variáveis extras para o filho; os valores podem conter placeholders. Nomes BAO_*, VAULT_*, KEYHOLDER_* são recusados
files mapa Variáveis que recebem o caminho de um arquivo temporário com o valor (modo 0600, apagado depois da execução), para ferramentas que leem a credencial de um arquivo
credentials lista de textos Credenciais de nuvem no formato que cada CLI espera: aws:<caminho> ou gcp:<caminho>. Veja Credenciais dinâmicas

Saída:

Campo Significado
exit_code Código de saída do filho
stdout, stderr Até 64 KiB cada, com todo valor resolvido trocado por [hidden:<referência>]
refs_used Referências resolvidas nesta execução
truncated A saída passou de 64 KiB
timed_out A execução atingiu KEYHOLDER_TIMEOUT
notes O que aconteceu depois da execução, por exemplo revoked 1 dynamic credential lease(s) after the run

O comando roda sem shell: exec com a lista de argumentos e stdin no dispositivo nulo. Pipes, redirecionamentos e $VAR não são interpretados, e caracteres especiais dentro de um segredo nunca viram código.

Erros (não permitido, fora do escopo, referência inexistente, acesso negado) voltam como erro da tool, com uma mensagem que o agente consegue usar, sem URLs nem corpo de resposta do cofre.

Credenciais dinâmicas

Com os plugins de segredos da AWS e do Google Cloud do OpenBao, um comando pode rodar com uma credencial de nuvem criada só para aquela execução.

Entrada em credentials Lê Define no filho
aws:<caminho>, por exemplo aws:aws/creds/deploy access_key, secret_key, session_token AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, e AWS_SESSION_TOKEN quando o tipo de credencial tem um
gcp:<caminho>, por exemplo gcp:gcp/roleset/viewer/token token CLOUDSDK_AUTH_ACCESS_TOKEN_FILE (um arquivo temporário para o gcloud) e GOOGLE_OAUTH_ACCESS_TOKEN

O preset também remove as variáveis herdadas daquela nuvem (AWS_*; CLOUDSDK_AUTH_*, GOOGLE_OAUTH_ACCESS_TOKEN, GOOGLE_APPLICATION_CREDENTIALS), para a credencial nova nunca se misturar com um perfil ou token do ambiente do servidor.

Quando o comando termina, o Keyholder revoga cada lease criado pela execução. O efeito depende da credencial:

Credencial Depois da execução
AWS iam_user Revogada: o OpenBao apaga o usuário IAM e a chave dele
AWS assumed_role, federation_token, session_token (STS) Não pode ser revogada; vale até o TTL do role (o mínimo da AWS é 15 minutos)
Token de acesso de roleset ou conta do GCP O plugin não cria lease; vale até expirar (até uma hora)

Prefira roles iam_user quando a credencial precisa morrer junto com o comando. Um lease que não pôde ser revogado aparece em notes. Passo a passo: tutorial de credenciais de nuvem.

Política

A política de exemplo concede exatamente o que as três tools precisam para um projeto:

Caminho Permissões Usado por
secret/data/my-project/* read keyholder_run (resolver), keyholder_list (nomes dos campos)
secret/metadata/my-project, .../* list, read keyholder_list
auth/token/lookup-self read keyholder_status (já está na política default do OpenBao)

Para credenciais dinâmicas, acrescente uma segunda política como a deploy/policy-cloud.hcl: read em cada caminho de KEYHOLDER_DYNAMIC e update em sys/leases/revoke, que o Keyholder usa para revogar depois da execução.

Sem lookup-self, o keyholder_status estima o TTL pela resposta do desembrulho e avisa isso em session_error.

Trilha de auditoria

Cada keyholder_run escreve uma linha no stderr do servidor, que os clientes MCP guardam nos logs do servidor:

2026/10/13 22:35:40 audit cmd="curl" refs=[vault://my-project/dev/API_TOKEN] exit=0 dur=8ms status=ok

Ela registra o nome do comando, as referências, o código de saída, a duração e o resultado. Nunca valores, nunca argumentos. Os audit devices do próprio OpenBao registram cada leitura do lado do cofre.

khcall

O khcall é um cliente MCP mínimo para depuração e para os tutoriais:

khcall [-server keyholder-mcp] -tools
khcall [-server keyholder-mcp] <tool> ['<argumentos json>'] [+wait=<duração>] [<tool> ...]

Todas as chamadas rodam numa sessão (um servidor, um desembrulho). +wait=7s pausa entre chamadas. Código de saída: 0 quando todas dão certo, 1 quando uma tool reporta erro, 2 em erro de uso ou de protocolo.

exec: um serviço com os segredos no ambiente

Para um processo de longa duração no host (um servidor de API, um servidor de modelo) que precisa de uma chave, o exec substitui o arquivo .env:

keyholder-mcp exec -e UPSTREAM_BEARER='{{vault://my-project/prod/API_KEY}}' -- ./server --port 8080

UPSTREAM_BEARER é a variável que o seu serviço lê. O template é resolvido no cofre quando o processo inicia e vai só para o ambiente dele. Nada é escrito em disco, nada passa por argumento ou shell, e BAO_*, VAULT_*, KEYHOLDER_* nunca chegam ao filho. Sinais são encaminhados; o código de saída é o do filho; leases bao:// são revogados quando ele termina. Valem os mesmos BAO_ADDR, BAO_WRAPPING_TOKEN (ou BAO_TOKEN), KEYHOLDER_SCOPE e KEYHOLDER_DYNAMIC.

Limites, ditos claramente: o valor vive na memória do processo e pode ser lido por qualquer processo do mesmo usuário do sistema (por exemplo via /proc). Repassá-lo a docker run -e o coloca no docker inspect; rode o serviço nativamente, ou dê ao container o mecanismo de arquivo de token.