sealkeep

Permita que agentes de IA usem segredos sem vê-los: injete um segredo em um comando e remova-o da saída. Keyring do SO, Vault, AWS, Google, Azure, 1Password, Bitwarden.

Documentação

sealkeep: your agent uses the key, it never sees it

npm MCP registry CI Smithery MIT

Install in Cursor Install in VS Code

sealkeep

O sealkeep permite que um agente de IA use seus segredos sem vê-los.

Você guarda cada segredo uma única vez, sob um nome como shared/stripe/test/SECRET_KEY. O agente encontra o nome com sealkeep list e executa um comando com ele. Ele nunca lê o valor e nunca precisa pedir um a você.

O agente solicita um segredo pelo nome. O sealkeep coloca o valor no ambiente de um comando, executa o comando e substitui o valor por [sealkeep:NAME] em toda a saída. O valor não vai para o contexto, para a transcrição nem para um arquivo.

agent ──► sealkeep run OPENROUTER_API_KEY -- sh -c 'curl -H "Authorization: Bearer $OPENROUTER_API_KEY" …'
              │
              ├─ reads the value from a store ──► OS keyring  (macOS Keychain, Secret Service, Windows Credential Manager)
              │                               └─► Vault KV v2 (a replica)
              ├─ starts the command with the value in its environment
              └─ redacts the value from stdout and stderr ──► agent sees [sealkeep:OPENROUTER_API_KEY]

Ele tem quatro partes:

  1. Uma CLI (sealkeep): set, list, run, import, scan, sync, aliases e os comandos de armazenamento.
  2. Um servidor MCP (sealkeep mcp): as ferramentas list_secrets, run_with_secrets e store_status. Nenhuma ferramenta retorna um valor.
  3. Um hook de proteção (sealkeep guard): ele recusa as chamadas de ferramenta que imprimiriam um segredo, por exemplo cat .env ou sealkeep get.
  4. Uma skill que diz ao agente quando e como usar a CLI.

Instalação

Apenas como servidor MCP (qualquer cliente), o comando é npx -y @munhq/sealkeep:

{ "mcpServers": { "sealkeep": { "command": "npx", "args": ["-y", "@munhq/sealkeep"] } } }

No Claude Code, o plugin adiciona o servidor MCP, a skill e o hook de proteção em uma única etapa:

/plugin marketplace add munhq/sealkeep
/plugin install sealkeep@sealkeep

A instalação completa abaixo faz o mesmo para Claude Code, Codex e Cursor.

Linux e macOS:

curl -fsSL https://raw.githubusercontent.com/munhq/sealkeep/main/install.sh | sh

O script baixa o binário da versão mais recente, verifica-o contra SHA256SUMS, copia-o para ~/.local/bin e executa sealkeep install.

Com Rust:

cargo install --locked --git https://github.com/munhq/sealkeep
sealkeep install

sealkeep install encontra os clientes de IA na máquina. Ele adiciona a skill, o servidor MCP e o hook de proteção a cada cliente:

ClienteSkillServidor MCPHook de proteção
Claude Code (cada pasta de configuração)<config>/skills/sealkeepclaude mcp add --scope user<config>/settings.json, PreToolUse em Bash, Read, Grep
Codex$CODEX_HOME/skills/sealkeepcodex mcp add$CODEX_HOME/hooks.json, PreToolUse (aprove uma vez com /hooks)
Cursor~/.cursor/skills/sealkeep~/.cursor/mcp.json~/.cursor/hooks.json, beforeShellExecution

Opções: --client claude,codex instala apenas nos clientes nomeados. --dry-run mostra o plano. --no-skills, --no-mcp e --no-hooks deixam de fora uma parte. Antes de o sealkeep alterar um arquivo JSON, ele grava um backup (<file>.bak-<unix time>). sealkeep uninstall remove o que install adicionou.

Execute sealkeep doctor para verificar os armazenamentos e os clientes.

Nomes

Um nome é <scope>/<project>/<env>/<KEY>:

ParteValoresExemplo
escopoQuem é o dono do segredo: personal, o nome de um empregador ou cliente, ou shared para uma conta que vários escopos usampersonal, acme, shared
projetoO repositório ou o serviçoexample-app, stripe, ovh
envprod, dev, test, local. Deixe de fora quando o segredo não tiver ambiente (a chave de API de uma conta)prod
KEYA variável de ambiente que o programa lêDATABASE_URL

As pastas ficam em minúsculas (a-z, 0-9, ., _, -). A chave fica em maiúsculas (A-Z, 0-9, _) e é a variável que run define.

personal/example-app/prod/DATABASE_URL
personal/example-app/dev/DATABASE_URL
personal/example-app/dev/STRIPE_SECRET_KEY   -> shared/stripe/test/SECRET_KEY   (alias)
acme/ovh/ovh-eu/APPLICATION_KEY
acme/cloudflare/API_TOKEN
shared/stripe/test/SECRET_KEY
shared/openrouter/API_KEY

Um valor, um nome. Quando dois projetos usam a mesma conta, guarde o valor uma única vez sob shared/… e crie um alias em cada pasta de projeto. A pasta do projeto então tem todas as variáveis de que o projeto precisa, e uma rotação altera um único valor.

Guardar um segredo

sealkeep set shared/openrouter/API_KEY -d "OpenRouter, the main account"   # writes to every store

O sealkeep pede o valor duas vezes, sem eco. Para enviar um valor por pipe, use --stdin:

some-password-tool show openrouter | sealkeep set shared/openrouter/API_KEY --stdin

Para encontrar os segredos em uma máquina, execute scan. Ele imprime o caminho de cada arquivo .env, os nomes das chaves, uma classe para cada chave (secret, config ou empty) e um número de grupo para cada valor que está em mais de um lugar. Ele nunca imprime um valor. Ele também lista outros arquivos que costumam conter uma credencial, apenas pelo caminho:

sealkeep scan ~/code --max-depth 4

Para copiar um arquivo para uma pasta:

sealkeep import ../example-app/.env --to personal/example-app/dev \
  --map STRIPE_SECRET_KEY=shared/stripe/test/SECRET_KEY
sealkeep import ~/.ovh.conf --to acme/ovh            # INI: one folder for each section
sealkeep set personal/github/PAT --from-file ~/.github-token

import lê arquivos dotenv e arquivos INI (.ini, .conf, AWS credentials). Ele imprime apenas os nomes. --map KEY=NAME guarda o valor em NAME e torna a chave do projeto um alias dele. Se NAME já tiver um valor diferente, import para e avisa. Após a importação, exclua o arquivo de origem ou mantenha-o fora do alcance do agente.

Deixe o agente usar

Diga ao agente o que fazer. A skill diz a ele para executar sealkeep list para os nomes e sealkeep run para o comando:

sealkeep run shared/openrouter/API_KEY -- sh -c 'curl -sS -H "Authorization: Bearer $API_KEY" https://openrouter.ai/api/v1/key'
sealkeep run --all personal/example-app/dev -- npm run dev
sealkeep run -e GITHUB_TOKEN=personal/github/PAT -- gh api user

Um nome define a variável nomeada pela sua chave. --all FOLDER define uma variável para cada segredo na pasta, o mesmo que um arquivo .env. --recursive também aceita as subpastas; dois segredos com a mesma chave são então um erro. -e VAR=NAME define uma variável com um nome diferente. STORE:NAME lê de apenas um armazenamento.

Para uma ferramenta que lê seus segredos de um arquivo, --dotenv grava um arquivo temporário que só você pode ler. O arquivo fica em $XDG_RUNTIME_DIR/sealkeep quando existe. O sealkeep coloca o caminho onde o comando tem {dotenv} e remove o arquivo quando o comando termina. Este exemplo dá ao Playwright MCP o login de uma conta de teste. O agente digita o nome ADMIN_PASSWORD e o Playwright coloca o valor:

sealkeep run --dotenv personal/example-app/dev/ADMIN_EMAIL --dotenv personal/example-app/dev/ADMIN_PASSWORD \
  -- npx @playwright/mcp@latest --secrets {dotenv}

--secrets <path> é uma opção de @playwright/mcp (veja o README dele).

Ver um valor você mesmo

sealkeep get shared/openrouter/API_KEY

get funciona apenas quando stdin e stdout são um terminal. Um agente executa comandos sem terminal, então get recusa, e o hook de proteção também recusa.

Armazenamentos

O sealkeep procura um nome simples nos armazenamentos na ordem do arquivo de configuração. O primeiro armazenamento que tiver o nome fornece o valor.

O arquivo de configuração é ~/.config/sealkeep/config.toml no Linux e ~/Library/Application Support/sealkeep/config.toml no macOS. $SEALKEEP_CONFIG define outro caminho. Sem arquivo, o sealkeep tem um armazenamento de keyring chamado local.

[[store]]
name = "local"
kind = "keyring"

[[store]]
name = "vault"
kind = "vault"
address = "http://127.0.0.1:{port}"
mount = "agent"
auth = "kubernetes"
role = "sealkeep"
jwt_command = ["kubectl", "-n", "vault", "create", "token", "sealkeep", "--duration", "10m"]
port_forward = ["kubectl", "-n", "vault", "port-forward", "svc/vault", "{port}:8200"]

[aliases]
"personal/example-app/dev/STRIPE_SECRET_KEY" = "shared/stripe/test/SECRET_KEY"

[guard]
block_dotenv = true
deny = ["vault kv get", "ansible-vault view"]

O keyring do sistema operacional

SOArmazenamento
macOSo Keychain de login
Linux, BSDo Secret Service (GNOME Keyring, KWallet) no D-Bus da sessão
Windowso Credential Manager

Cada segredo é uma entrada com o serviço sealkeep e o nome do segredo como conta. Uma entrada de índice (__index__) mantém os nomes e as descrições.

No Linux, o keyring deve estar desbloqueado. Uma sessão de desktop o desbloqueia quando você faz login. Uma sessão sem desktop (SSH, um login sem senha de keyring) tem um keyring bloqueado, e nenhum prompt de desbloqueio pode aparecer. Execute isto uma vez após cada inicialização:

sealkeep unlock

Ele pede a senha do keyring de login e a entrega a gnome-keyring-daemon --unlock. Cada chamada de keyring espera no máximo 30 segundos (SEALKEEP_KEYRING_TIMEOUT), então um agente recebe um erro e a chamada não trava.

Vault como réplica

Um mount KV v2 do Vault (ou OpenBao) pode conter uma cópia do keyring, para uma segunda máquina ou um backup. Cada pasta é um segredo KV, e cada chave é um campo dele: shared/stripe/test/SECRET_KEY é o campo SECRET_KEY de agent/shared/stripe/test. O custom_metadata de cada segredo lista suas chaves e descrições, então list não lê nenhum valor.

Para um Vault dentro do Kubernetes, o sealkeep pode alcançá-lo pela API do Kubernetes com um port forward e fazer login com um token de ServiceAccount de curta duração. A máquina então não mantém nenhum token do Vault:

sealkeep store add-vault vault \
  --address 'http://127.0.0.1:{port}' --mount agent --auth kubernetes --role sealkeep \
  --jwt-command 'kubectl -n vault create token sealkeep --duration 10m' \
  --port-forward 'kubectl -n vault port-forward svc/vault {port}:8200'
sealkeep sync --from local --to vault   # one time, for the secrets that are already in the keyring

set, import e mv gravam em todos os armazenamentos (um armazenamento Vault aceita apenas nomes com pasta), então os armazenamentos permanecem iguais. --store NAME grava em um armazenamento. O lado do Vault precisa de um mount KV v2, uma política com create, read e update em agent/data/* e read, list e update em agent/metadata/*, e um papel de autenticação Kubernetes vinculado ao ServiceAccount. Com --auth token, o sealkeep lê $VAULT_TOKEN, ou o token que sealkeep store token vault mantém no keyring.

Outros gerenciadores de segredos

Cada um destes é um tipo de armazenamento. Ele executa a CLI oficial do fornecedor, então o login do próprio fornecedor (SSO, conta de serviço, biometria) se aplica, e o sealkeep não mantém nenhum token do fornecedor. Um valor vai para a CLI via stdin ou por um arquivo que só você pode ler, nunca como argumento de comando, que ps mostraria.

ArmazenamentoAdicioná-loLayoutO valor entra por
AWS Secrets Managersealkeep store add-aws aws --region eu-west-1Um segredo JSON para cada pasta, sealkeep/<folder>--secret-string file://…
Google Secret Managersealkeep store add-gcp gcp --project acme-prodUm segredo para cada nome; anotações guardam o nome--data-file=- (stdin)
Azure Key Vaultsealkeep store add-azure azure --vault acme-kvUm segredo para cada nome; tags guardam o nome--file …
1Password (CLI 2.23+)sealkeep store add-1password op --vault EngineeringUma Secure Note para cada pasta, um campo oculto para cada chaveo JSON do item no stdin
Bitwarden / Vaultwardensealkeep store add-bitwarden bwUma Secure Note sealkeep:<folder>, um campo oculto para cada chaveo JSON do item no stdin
OpenBaosealkeep store add-vault …como Vaultcomo Vault

O Google e o Azure permitem apenas alguns caracteres em um ID de segredo, então o ID é o nome com / como -- e um hash curto, e o nome real está em uma anotação ou tag. O Azure mantém um segredo removido no estado de soft-delete, e um set posterior do mesmo nome o recupera. Para Bitwarden, desbloqueie primeiro: export BW_SESSION=$(bw unlock --raw). A CLI do Bitwarden Secrets Manager (bws) aceita um valor apenas como argumento de comando, então o sealkeep usa a CLI do gerenciador de senhas (bw).

Chaves SSH

Um agente SSH mantém uma chave com senha apenas na memória. Após uma reinicialização, ele fica vazio, e um git push de um agente falha com Permission denied (publickey). O sealkeep mantém a senha e carrega a chave:

sealkeep set personal/ssh/ID_ED25519_PASSPHRASE
sealkeep ssh-add ~/.ssh/id_ed25519 --passphrase personal/ssh/ID_ED25519_PASSPHRASE
sealkeep ssh-load      # unlock runs it too

ssh-load executa ssh-add com o sealkeep como o programa SSH_ASKPASS. A etapa askpass responde apenas quando seu processo pai é ssh-add e um token de uso único corresponde, e ela lê a senha do armazenamento, então a senha nunca vai para um arquivo ou para a saída. --agent escolhe o socket do agente; o padrão é $SSH_AUTH_SOCK.

MCP

sealkeep mcp serve estas ferramentas no stdio:

FerramentaO que ela faz
list_secretsOs nomes, armazenamentos e descrições, para todos os nomes ou um folder.
run_with_secretsExecuta command (uma lista de argumentos, sem shell) com folders e secrets no ambiente e dotenv em um arquivo temporário. Retorna o código de saída, stdout e stderr, com redação. O limite de tempo é 120 s por padrão e 900 s no máximo. Cada fluxo é cortado em 100 KiB.
store_statusSe cada armazenamento pode ser usado agora.

O hook de proteção

O hook recusa estas chamadas de ferramenta e diz ao agente para usar sealkeep run:

  1. sealkeep get, também dentro de $(…), crases e bash -c.
  2. Uma leitura das entradas do chaveiro sealkeep: secret-tool lookup service sealkeep …, security find-generic-password -s sealkeep … -w, keyring get sealkeep ….
  3. Uma leitura de um arquivo .env por um comando de shell ou pelas ferramentas Read e Grep, quando block_dotenv está ativo. .env.example, .env.sample, .env.template e .env.dist permanecem legíveis.
  4. Cada prefixo de comando em guard.deny.

Mais duas camadas funcionam para o Claude Code:

  1. A proteção de saída (sealkeep guard --after, um hook PostToolUse para Bash, Read e Grep). Após a chamada da ferramenta, ele compara o resultado com os valores dos segredos armazenados, nas mesmas formas da redação de run. Quando um valor está no resultado, o hook o substitui por [sealkeep:NAME] antes que o agente leia o resultado, e nomeia o segredo em uma nota. Isso também cobre env > out.txt seguido por uma leitura de out.txt, e um glob ou uma variável no caminho. Os armazenamentos carregam em paralelo com um limite de 4 segundos, e um armazenamento que não responde não adiciona valores. O Claude Code registra a saída original em sua própria telemetria.
  2. Regras de negação (permissions.deny em settings.json). sealkeep install adiciona Read(**/.env), Read(**/.env.local), Read(**/*.env) e as regras para os outros nomes de .env.*. O Claude Code verifica essas regras no caminho, então a ferramenta Read e os comandos de arquivo que ele reconhece não podem alcançar o arquivo por um truque de shell. A instalação as adiciona quando block_dotenv está ativo, e sealkeep uninstall as remove.

O que o sealkeep protege, e o que ele não protege

O sealkeep mantém segredos fora do contexto do agente, fora da transcrição e fora dos arquivos do projeto. Cada uso vai para o log de auditoria (~/.local/share/sealkeep/audit.jsonl no Linux, ou $SEALKEEP_AUDIT_LOG), com os nomes e o comando e nunca um valor.

A redação corresponde ao valor, à sua forma escapada em JSON, à sua forma codificada em percentual e às suas formas em base64. Um valor é redigido quando sua chave parece um segredo (KEY, TOKEN, SECRET, PASS, AUTH e palavras semelhantes), quando é uma URL com senha, ou quando tem 16 caracteres ou mais. Um valor de configuração como PORT=3000 permanece legível. Um valor com menos de 4 caracteres não é redigido. Um comando que transforma um valor de outra forma (por exemplo, um hash ou uma parte do valor) pode imprimi-lo em uma forma que o sealkeep não corresponde.

Um agente que pode executar qualquer comando de shell como seu usuário pode ler o que seu usuário pode ler. O sealkeep não muda isso. Ele torna o caminho seguro o fácil, recusa os caminhos comuns para um valor e registra cada uso. Dê ao agente chaves com escopo pequeno: uma chave Stripe restrita, uma chave OpenRouter com limite de gasto, uma conta de teste para um login web.

Build e teste

cargo build --release
cargo test --features test-store

O recurso test-store substitui o chaveiro do sistema operacional por um arquivo, apenas para os testes. Os binários de lançamento são construídos sem ele.

Licença

MIT