sealkeep

Permite que los agentes de IA usen secretos sin verlos: inyecta un secreto en un comando y elimínalo de la salida. Llavero del sistema operativo, Vault, AWS, Google, Azure, 1Password, Bitwarden.

Documentación

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

npm MCP registry CI Smithery MIT

Install in Cursor Install in VS Code

sealkeep

sealkeep permite que un agente de IA use tus secretos sin verlos.

Guardas cada secreto una sola vez, bajo un nombre como shared/stripe/test/SECRET_KEY. El agente encuentra el nombre con sealkeep list y ejecuta un comando con él. Nunca lee el valor y nunca tiene que pedírtelo.

El agente solicita un secreto por su nombre. sealkeep coloca el valor en el entorno de un comando, ejecuta el comando y reemplaza el valor con [sealkeep:NAME] en toda la salida. El valor no entra en el contexto, la transcripción ni un archivo.

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]

Tiene cuatro partes:

  1. Una CLI (sealkeep): set, list, run, import, scan, sync, alias y los comandos de almacenamiento.
  2. Un servidor MCP (sealkeep mcp): las herramientas list_secrets, run_with_secrets y store_status. Ninguna herramienta devuelve un valor.
  3. Un hook de protección (sealkeep guard): rechaza las llamadas a herramientas que imprimirían un secreto, por ejemplo cat .env o sealkeep get.
  4. Una habilidad que le indica al agente cuándo y cómo usar la CLI.

Instalación

Como servidor MCP únicamente (cualquier cliente), el comando es npx -y @munhq/sealkeep:

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

En Claude Code, el plugin añade el servidor MCP, la habilidad y el hook de protección en un solo paso:

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

La instalación completa a continuación hace lo mismo para Claude Code, Codex y Cursor.

Linux y macOS:

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

El script descarga el binario de la versión más reciente, lo verifica contra SHA256SUMS, lo copia a ~/.local/bin y ejecuta sealkeep install.

Con Rust:

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

sealkeep install encuentra los clientes de IA en la máquina. Añade la habilidad, el servidor MCP y el hook de protección a cada cliente:

ClienteHabilidadServidor MCPHook de protección
Claude Code (cada carpeta de configuración)<config>/skills/sealkeepclaude mcp add --scope user<config>/settings.json, PreToolUse en Bash, Read, Grep
Codex$CODEX_HOME/skills/sealkeepcodex mcp add$CODEX_HOME/hooks.json, PreToolUse (apruébalo una vez con /hooks)
Cursor~/.cursor/skills/sealkeep~/.cursor/mcp.json~/.cursor/hooks.json, beforeShellExecution

Opciones: --client claude,codex instala solo en los clientes nombrados. --dry-run muestra el plan. --no-skills, --no-mcp y --no-hooks omiten una parte. Antes de que sealkeep modifique un archivo JSON, escribe una copia de seguridad (<file>.bak-<unix time>). sealkeep uninstall elimina lo que install añadió.

Ejecuta sealkeep doctor para verificar los almacenes y los clientes.

Nombres

Un nombre es <scope>/<project>/<env>/<KEY>:

ParteValoresEjemplo
alcanceQuién es el dueño del secreto: personal, el nombre de un empleador o cliente, o shared para una cuenta que varios alcances usanpersonal, acme, shared
proyectoEl repositorio o el servicioexample-app, stripe, ovh
entornoprod, dev, test, local. Omítelo cuando el secreto no tenga entorno (la clave API de una cuenta)prod
KEYLa variable de entorno que el programa leeDATABASE_URL

Las carpetas están en minúsculas (a-z, 0-9, ., _, -). La clave está en mayúsculas (A-Z, 0-9, _) y es la variable que run establece.

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

Un valor, un nombre. Cuando dos proyectos usan la misma cuenta, guarda el valor una sola vez bajo shared/… y crea un alias en cada carpeta de proyecto. La carpeta del proyecto tendrá entonces todas las variables que el proyecto necesita, y una rotación cambia un solo valor.

Guardar un secreto

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

sealkeep pide el valor dos veces, sin eco. Para pasar un valor por tubería, usa --stdin:

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

Para encontrar los secretos en una máquina, ejecuta scan. Imprime la ruta de cada archivo .env, sus nombres de clave, una clase para cada clave (secret, config o empty) y un número de grupo para cada valor que esté en más de un lugar. Nunca imprime un valor. También lista otros archivos que suelen contener credenciales, solo por ruta:

sealkeep scan ~/code --max-depth 4

Para copiar un archivo a una carpeta:

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 lee archivos dotenv y archivos INI (.ini, .conf, AWS credentials). Imprime solo los nombres. --map KEY=NAME guarda el valor en NAME y hace que la clave del proyecto sea un alias de él. Si NAME ya tiene un valor diferente, import se detiene y lo indica. Después de la importación, elimina el archivo fuente o mantenlo fuera del alcance del agente.

Deja que el agente lo use

Dile al agente qué hacer. La habilidad le indica que ejecute sealkeep list para los nombres y sealkeep run para el 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

Un nombre establece la variable nombrada por su clave. --all FOLDER establece una variable para cada secreto en la carpeta, igual que un archivo .env. --recursive también toma las subcarpetas; dos secretos con la misma clave son entonces un error. -e VAR=NAME establece una variable con un nombre diferente. STORE:NAME lee de un solo almacén.

Para una herramienta que lee sus secretos de un archivo, --dotenv escribe un archivo temporal que solo tú puedes leer. El archivo está en $XDG_RUNTIME_DIR/sealkeep cuando existe. sealkeep coloca la ruta donde el comando tiene {dotenv} y elimina el archivo cuando el comando termina. Este ejemplo le da a Playwright MCP el inicio de sesión de una cuenta de prueba. El agente escribe el nombre ADMIN_PASSWORD y Playwright coloca el 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> es una opción de @playwright/mcp (consulta su README).

Ver un valor tú mismo

sealkeep get shared/openrouter/API_KEY

get funciona solo cuando stdin y stdout son una terminal. Un agente ejecuta comandos sin terminal, por lo que get se niega, y el hook de protección también lo rechaza.

Almacenes

sealkeep busca un nombre simple en los almacenes en el orden del archivo de configuración. El primer almacén que tenga el nombre da el valor.

El archivo de configuración es ~/.config/sealkeep/config.toml en Linux y ~/Library/Application Support/sealkeep/config.toml en macOS. $SEALKEEP_CONFIG establece otra ruta. Sin archivo, sealkeep tiene un almacén de llavero llamado 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"]

El llavero del sistema operativo

SOAlmacén
macOSel llavero de inicio de sesión
Linux, BSDel Secret Service (GNOME Keyring, KWallet) en el D-Bus de la sesión
Windowsel Credential Manager

Cada secreto es una entrada con el servicio sealkeep y el nombre del secreto como cuenta. Una entrada de índice (__index__) mantiene los nombres y las descripciones.

En Linux, el llavero debe estar desbloqueado. Una sesión de escritorio lo desbloquea al iniciar sesión. Una sesión sin escritorio (SSH, un inicio de sesión sin contraseña de llavero) tiene un llavero bloqueado y no se puede mostrar ningún aviso de desbloqueo. Ejecuta esto una vez después de cada arranque:

sealkeep unlock

Pide la contraseña del llavero de inicio de sesión y se la da a gnome-keyring-daemon --unlock. Cada llamada al llavero espera como máximo 30 segundos (SEALKEEP_KEYRING_TIMEOUT), por lo que un agente recibe un error y la llamada no se cuelga.

Vault como réplica

Un montaje KV v2 de Vault (o OpenBao) puede contener una copia del llavero, para una segunda máquina o una copia de seguridad. Cada carpeta es un secreto KV y cada clave es un campo de él: shared/stripe/test/SECRET_KEY es el campo SECRET_KEY de agent/shared/stripe/test. El custom_metadata de cada secreto lista sus claves y sus descripciones, por lo que list no lee ningún valor.

Para un Vault dentro de Kubernetes, sealkeep puede alcanzarlo a través de la API de Kubernetes con un reenvío de puerto e iniciar sesión con un token de ServiceAccount de corta duración. La máquina no guarda entonces ningún token de 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 y mv escriben en cada almacén (un almacén de Vault solo acepta nombres con carpeta), por lo que los almacenes permanecen iguales. --store NAME escribe en un solo almacén. El lado de Vault necesita un montaje KV v2, una política con create, read y update en agent/data/* y read, list y update en agent/metadata/*, y un rol de autenticación de Kubernetes vinculado al ServiceAccount. Con --auth token, sealkeep lee $VAULT_TOKEN, o el token que sealkeep store token vault guarda en el llavero.

Otros gestores de secretos

Cada uno de estos es un tipo de almacén. Ejecuta la CLI oficial del proveedor, por lo que se aplica el inicio de sesión propio del proveedor (SSO, cuenta de servicio, biometría) y sealkeep no guarda ningún token del proveedor. Un valor va a la CLI a través de stdin o de un archivo que solo tú puedes leer, nunca como argumento de comando, que ps mostraría.

AlmacénAñadirloDiseñoEl valor entra a través de
AWS Secrets Managersealkeep store add-aws aws --region eu-west-1Un secreto JSON para cada carpeta, sealkeep/<folder>--secret-string file://…
Google Secret Managersealkeep store add-gcp gcp --project acme-prodUn secreto para cada nombre; las anotaciones contienen el nombre--data-file=- (stdin)
Azure Key Vaultsealkeep store add-azure azure --vault acme-kvUn secreto para cada nombre; las etiquetas contienen el nombre--file …
1Password (CLI 2.23+)sealkeep store add-1password op --vault EngineeringUna Secure Note para cada carpeta, un campo oculto para cada claveel JSON del elemento en stdin
Bitwarden / Vaultwardensealkeep store add-bitwarden bwUna Secure Note sealkeep:<folder>, un campo oculto para cada claveel JSON del elemento en stdin
OpenBaosealkeep store add-vault …como Vaultcomo Vault

Google y Azure permiten solo algunos caracteres en un ID de secreto, por lo que el ID es el nombre con / como -- y un hash corto, y el nombre real está en una anotación o una etiqueta. Azure mantiene un secreto eliminado en su estado de soft-delete, y un set posterior del mismo nombre lo recupera. Para Bitwarden, desbloquea primero: export BW_SESSION=$(bw unlock --raw). La CLI de Bitwarden Secrets Manager (bws) acepta un valor solo como argumento de comando, por lo que sealkeep usa la CLI del gestor de contraseñas (bw).

Claves SSH

Un agente SSH mantiene una clave con frase de contraseña solo en memoria. Después de un reinicio está vacío, y el git push de un agente falla con Permission denied (publickey). sealkeep guarda la frase de contraseña y carga la clave:

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 ejecuta ssh-add con sealkeep como programa SSH_ASKPASS. El paso askpass responde solo cuando su proceso padre es ssh-add y un token de un solo uso coincide, y lee la frase de contraseña del almacén, por lo que la frase nunca va a un archivo ni a la salida. --agent elige el socket del agente; el predeterminado es $SSH_AUTH_SOCK.

MCP

sealkeep mcp sirve estas herramientas en stdio:

HerramientaQué hace
list_secretsLos nombres, almacenes y descripciones, para todos los nombres o uno folder.
run_with_secretsEjecuta command (una lista de argumentos, sin shell) con folders y secrets en el entorno y dotenv en un archivo temporal. Devuelve el código de salida, stdout y stderr, redactados. El límite de tiempo es de 120 s por defecto y 900 s como máximo. Cada flujo se corta en 100 KiB.
store_statusSi cada almacén se puede usar ahora.

El hook de protección

El hook rechaza estas llamadas a herramientas y le indica al agente que use sealkeep run:

  1. sealkeep get, también dentro de $(…), comillas invertidas y bash -c.
  2. Una lectura de las entradas del llavero de sealkeep: secret-tool lookup service sealkeep …, security find-generic-password -s sealkeep … -w, keyring get sealkeep ….
  3. Una lectura de un archivo .env mediante un comando de shell o mediante las herramientas Read y Grep, cuando block_dotenv está activado. .env.example, .env.sample, .env.template y .env.dist permanecen legibles.
  4. Cada prefijo de comando en guard.deny.

Lo que sealkeep protege, y lo que no

sealkeep mantiene los secretos fuera del contexto del agente, fuera de la transcripción y fuera de los archivos del proyecto. Cada uso se registra en el registro de auditoría (~/.local/share/sealkeep/audit.jsonl en Linux, o $SEALKEEP_AUDIT_LOG), con los nombres y el comando, y nunca un valor.

La redacción coincide con el valor, su forma escapada en JSON, su forma codificada en porcentaje y sus formas en base64. Un valor se redacta cuando su clave parece un secreto (KEY, TOKEN, SECRET, PASS, AUTH y palabras similares), cuando es una URL con contraseña, o cuando tiene 16 caracteres o más. Un valor de configuración como PORT=3000 permanece legible. Un valor de menos de 4 caracteres no se redacta. Un comando que transforma un valor de otra manera (por ejemplo, un hash o una parte del valor) puede imprimirlo en una forma que sealkeep no coincide.

Un agente que puede ejecutar cualquier comando de shell como tu usuario puede leer lo que tu usuario puede leer. sealkeep no cambia eso. Hace que el camino seguro sea el fácil, rechaza los caminos comunes hacia un valor y registra cada uso. Entrega al agente claves con un alcance pequeño: una clave de Stripe restringida, una clave de OpenRouter con un límite de gasto, una cuenta de prueba para un inicio de sesión web.

Compilación y pruebas

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

La función test-store reemplaza el llavero del sistema operativo con un archivo, solo para las pruebas. Los binarios de lanzamiento se compilan sin ella.

Licencia

MIT