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
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:
- Una CLI (
sealkeep):set,list,run,import,scan,sync, alias y los comandos de almacenamiento. - Un servidor MCP (
sealkeep mcp): las herramientaslist_secrets,run_with_secretsystore_status. Ninguna herramienta devuelve un valor. - Un hook de protección (
sealkeep guard): rechaza las llamadas a herramientas que imprimirían un secreto, por ejemplocat .envosealkeep get. - 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:
| Cliente | Habilidad | Servidor MCP | Hook de protección |
|---|---|---|---|
| Claude Code (cada carpeta de configuración) | <config>/skills/sealkeep | claude mcp add --scope user | <config>/settings.json, PreToolUse en Bash, Read, Grep |
| Codex | $CODEX_HOME/skills/sealkeep | codex 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>:
| Parte | Valores | Ejemplo |
|---|---|---|
| alcance | Quién es el dueño del secreto: personal, el nombre de un empleador o cliente, o shared para una cuenta que varios alcances usan | personal, acme, shared |
| proyecto | El repositorio o el servicio | example-app, stripe, ovh |
| entorno | prod, dev, test, local. Omítelo cuando el secreto no tenga entorno (la clave API de una cuenta) | prod |
| KEY | La variable de entorno que el programa lee | DATABASE_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
| SO | Almacén |
|---|---|
| macOS | el llavero de inicio de sesión |
| Linux, BSD | el Secret Service (GNOME Keyring, KWallet) en el D-Bus de la sesión |
| Windows | el 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én | Añadirlo | Diseño | El valor entra a través de |
|---|---|---|---|
| AWS Secrets Manager | sealkeep store add-aws aws --region eu-west-1 | Un secreto JSON para cada carpeta, sealkeep/<folder> | --secret-string file://… |
| Google Secret Manager | sealkeep store add-gcp gcp --project acme-prod | Un secreto para cada nombre; las anotaciones contienen el nombre | --data-file=- (stdin) |
| Azure Key Vault | sealkeep store add-azure azure --vault acme-kv | Un secreto para cada nombre; las etiquetas contienen el nombre | --file … |
| 1Password (CLI 2.23+) | sealkeep store add-1password op --vault Engineering | Una Secure Note para cada carpeta, un campo oculto para cada clave | el JSON del elemento en stdin |
| Bitwarden / Vaultwarden | sealkeep store add-bitwarden bw | Una Secure Note sealkeep:<folder>, un campo oculto para cada clave | el JSON del elemento en stdin |
| OpenBao | sealkeep store add-vault … | como Vault | como 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:
| Herramienta | Qué hace |
|---|---|
list_secrets | Los nombres, almacenes y descripciones, para todos los nombres o uno folder. |
run_with_secrets | Ejecuta 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_status | Si 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:
sealkeep get, también dentro de$(…), comillas invertidas ybash -c.- Una lectura de las entradas del llavero de sealkeep:
secret-tool lookup service sealkeep …,security find-generic-password -s sealkeep … -w,keyring get sealkeep …. - Una lectura de un archivo
.envmediante un comando de shell o mediante las herramientas Read y Grep, cuandoblock_dotenvestá activado..env.example,.env.sample,.env.templatey.env.distpermanecen legibles. - 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