Founding Member: $499/mes de por vida
Seguridad

Llavero cifrado para tus llaves de API

Guarda tus llaves de API cifradas con sops y age, deja que tus programas las usen sin imprimirlas, y frena a tu asistente antes de que lea una llave en claro (un .env, el bloque env de un settings, sops a la pantalla).

ScriptPremiumSeguridad

🌱 Empieza aquí · Llavero cifrado para tus llaves de API

Esta semilla le da a tu asistente un llavero cifrado: un archivo donde tus llaves de API (las contraseñas que usan tus programas para hablar con otros servicios) viven cifradas, y unas reglas para usarlas sin verlas nunca. Tus programas reciben la llave cuando la necesitan; tu asistente solo ve los nombres. Y si tu asistente intenta leer una llave en claro con los comandos de siempre (abrir un .env, listar las variables, descifrar el llavero a la pantalla), un guardián lo frena antes.

Para instalarla

Abre esta carpeta con Claude Code y dile a tu asistente:

"Siembra esta semilla 🌱"

Tu asistente hace lo técnico. En un momento te va a pedir que pegues una llave en tu propia terminal (no en el chat): es a propósito, para que la llave nunca pase por la conversación. Vas a ver palabras en inglés en la pantalla; ni caso, él te guía.

¿Otras formas de pedírselo? "instala esta semilla", "ponme el llavero cifrado", "haz el setup del llavero de llaves". Te entiende igual.


Para el asistente que siembra 🤖

Este componente es un conjunto de scripts (programas pequeños de terminal) más un hook (gancho de automatización) que Claude Code corre antes de cada comando o lectura. El kernel trae seis archivos:

| Archivo | Qué es | Dónde va | |---|---|---| | kernel/llaves.sh | El llavero: nombres, run y agregar | ~/.claude/scripts/ | | kernel/guardar-secreto.py | Guarda una llave tecleada a ciegas y la comprueba por huella | ~/.claude/scripts/ | | kernel/buscar-secreto.sh | Busca una credencial en 7 capas de la máquina, con salida enmascarada | ~/.claude/scripts/ | | kernel/candado-lectura-llaves.py | Hook PreToolUse que frena lecturas que vuelcan llaves | ~/.claude/hooks/ | | kernel/_secretos.py | Los formatos de llave que el candado reconoce por su valor | ~/.claude/hooks/ (junto al candado) | | kernel/candado-secretos-cifrados.sh | Guardián de git: nada en claro entra a la carpeta secretos/ | ~/.claude/hooks/ |

Regla de oro durante toda la siembra: nunca imprimas, leas ni muestres el valor de una llave ni de la identidad age. No corras cat sobre identidad.age, ni sops decrypt a la pantalla, ni env sin filtro.

Cada bloque de comandos de esta guía es autocontenido: define sus propias variables y se puede correr solo, en una terminal nueva.

0. Requisitos: sops, age y Python 3

Revisa si ya están:

command -v sops age age-keygen python3
sops --version

Tienen que salir cuatro rutas y una versión de sops 3.10 o más nueva (las anteriores no traen --value-stdin ni SOPS_AGE_KEY_CMD, que usan los scripts). Si falta alguno o sops es más viejo:

  • macOS: brew install sops age
  • Linux: sudo apt install age (o el gestor de paquetes de la distribución) y el binario de sops desde sus versiones publicadas: https://github.com/getsops/sops/releases
  • Windows: winget install Mozilla.SOPS y winget install FiloSottile.age, más Git for Windows (trae Git Bash, la terminal que usan los scripts) y Python 3. Si llaves.sh no encuentra sops, agrega por su cuenta la carpeta de winget al PATH.

1. Copia los archivos del kernel

macOS / Linux / Windows con Git Bash (desde esta carpeta):

mkdir -p "$HOME/.claude/scripts" "$HOME/.claude/hooks"
cp kernel/llaves.sh kernel/guardar-secreto.py kernel/buscar-secreto.sh "$HOME/.claude/scripts/"
cp kernel/candado-lectura-llaves.py kernel/_secretos.py kernel/candado-secretos-cifrados.sh "$HOME/.claude/hooks/"
chmod +x "$HOME/.claude/scripts/llaves.sh" "$HOME/.claude/scripts/buscar-secreto.sh" "$HOME/.claude/hooks/candado-secretos-cifrados.sh"

Windows con PowerShell (desde esta carpeta; chmod no hace falta porque los scripts se invocan con bash y python):

New-Item -ItemType Directory -Force "$HOME\.claude\scripts", "$HOME\.claude\hooks" | Out-Null
Copy-Item kernel\llaves.sh, kernel\guardar-secreto.py, kernel\buscar-secreto.sh "$HOME\.claude\scripts\"
Copy-Item kernel\candado-lectura-llaves.py, kernel\_secretos.py, kernel\candado-secretos-cifrados.sh "$HOME\.claude\hooks\"

_secretos.py tiene que quedar en la misma carpeta que candado-lectura-llaves.py. Si falta, el candado sigue funcionando pero solo reconoce llaves por su nombre, y lo avisa en cada rechazo. Si prefieres guardarlo en otro lado, define LLAVES_SECRETOS_PY con su ruta.

2. Crea la identidad age y el llavero (solo si la persona no tiene uno)

La identidad age es la llave maestra que abre el llavero. Si se pierde, el llavero ya no se puede abrir: díselo a la persona. Si ya tiene un llavero o una identidad, salta al final de este paso.

a) Genera la identidad, las reglas de cifrado y el llavero (macOS, Linux y Git Bash):

IDENT="$HOME/.config/llavero/identidad.age"
DIR="$HOME/.claude/secretos"
mkdir -p "$(dirname "$IDENT")" "$DIR"
if [ -e "$IDENT" ] || [ -e "$DIR/llavero.sops.env" ]; then
  echo "ya existe una identidad o un llavero: no toco nada"
else
  age-keygen -o "$IDENT" 2>/dev/null && chmod 600 "$IDENT"
  PUB=$(age-keygen -y "$IDENT")
  printf 'creation_rules:\n  - age: %s\n' "$PUB" > "$DIR/.sops.yaml"
  printf 'LLAVERO_CREADO=si\n' > "$DIR/llavero.sops.env"
  sops encrypt --in-place --age "$PUB" "$DIR/llavero.sops.env" && echo "llavero creado y cifrado"
fi

La parte pública de la identidad (la que queda en .sops.yaml) no es secreta. La entrada LLAVERO_CREADO=si es de control: sirve para la verificación del paso 6.

b) Solo en macOS: muda la identidad al Llavero de la Mac (ahí la busca llaves.sh por omisión, con el servicio sops-age). No imprime nada; la identidad pasa un instante como argumento del comando security en la propia máquina y el archivo se borra al final:

IDENT="$HOME/.config/llavero/identidad.age"
security add-generic-password -s sops-age -a "$USER" -w "$(grep '^AGE-SECRET-KEY' "$IDENT")" \
  && rm "$IDENT" && echo "identidad mudada al Llavero de la Mac"

En Linux y Windows la identidad se queda en ~/.config/llavero/identidad.age, protegida por los permisos de la cuenta (solo-dueño en Linux).

Si la persona ya tiene otro llavero o identidad, no crees nada: apunta a lo suyo con las variables LLAVES_ARCHIVO (ruta del llavero), LLAVES_IDENTIDAD (archivo de identidad en Linux y Windows), LLAVES_SERVICIO_MAC (servicio del Llavero de macOS) o LLAVES_CONFIG (su .sops.yaml). SOPS_AGE_KEY_FILE, SOPS_AGE_KEY o SOPS_AGE_KEY_CMD en el ambiente mandan sobre todo.

3. Registra el hook en settings.json

Abre ~/.claude/settings.json y agrega este bloque dentro de hooks. Si ya existe PreToolUse, añade el objeto al arreglo existente, no lo dupliques.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Read",
        "hooks": [
          { "type": "command", "command": "python3 ~/.claude/hooks/candado-lectura-llaves.py" }
        ]
      }
    ]
  }
}

En Windows, si python3 no existe, usa python. El hook se activa en la siguiente sesión.

4. Guardián de git (opcional, si la carpeta de secretos vive en un repositorio)

Si la persona versiona su carpeta secretos/ con git, instala el guardián como pre-commit en ese repositorio. Cambia <TU-REPO> por la carpeta del repositorio:

REPO="<TU-REPO>"
printf '#!/bin/sh\nexec bash "$HOME/.claude/hooks/candado-secretos-cifrados.sh" --staged\n' > "$REPO/.git/hooks/pre-commit"
chmod +x "$REPO/.git/hooks/pre-commit"

Si la carpeta vigilada se llama distinto, define CANDADO_CARPETA con su nombre. También sirve para revisar en disco: bash ~/.claude/hooks/candado-secretos-cifrados.sh <carpeta>.

5. Muda las llaves que ya existen (con la persona)

Por cada llave que la persona quiera guardar, pídele que corra en su propia terminal (no a través de ti), cambiando NOMBRE_DE_LA_LLAVE por el nombre real. Explícale dónde está esa terminal: en macOS, la app Terminal; en Linux, la terminal de su escritorio; en Windows, Git Bash (con python en lugar de python3 si ese no existe):

python3 ~/.claude/scripts/guardar-secreto.py NOMBRE_DE_LA_LLAVE

El script abre el llavero antes de pedir nada, pide el valor sin mostrarlo, rechaza valores vacíos, de menos de 12 caracteres o con espacios, lo cifra, lo relee y compara su huella. Si esa llave seguía en el bloque env de settings.local.json, el script lo avisa: hay que quitarla de ahí a mano, porque ese valor viejo es el que verían las sesiones. El script no borra la llave de ningún otro lado: si también vivía en un .env de un proyecto, esa copia en claro sigue ahí hasta que la persona la borre (y el programa pase a tomarla con llaves.sh run).

En macOS y Windows también sirve bash ~/.claude/scripts/llaves.sh agregar NOMBRE con el valor copiado en el portapapeles (el script limpia el portapapeles al terminar).

6. Verificación post-siembra (obligatoria)

a) Los archivos quedaron en su lugar:

ls -la "$HOME/.claude/scripts/llaves.sh" "$HOME/.claude/scripts/guardar-secreto.py" "$HOME/.claude/scripts/buscar-secreto.sh" \
  "$HOME/.claude/hooks/candado-lectura-llaves.py" "$HOME/.claude/hooks/_secretos.py" "$HOME/.claude/hooks/candado-secretos-cifrados.sh"; echo "exit=$?"
python3 ~/.claude/hooks/candado-lectura-llaves.py --estado; echo "estado=$?"

Salida esperada: seis líneas, una por archivo, y exit=0; luego candado de llaves: detección por nombre y por valor activas (_secretos.py encontrado) y estado=0. Si sale estado=1, _secretos.py no quedó junto al candado: repite el paso 1.

b) Prueba de comportamiento: el llavero abre y entrega llaves sin mostrarlas.

bash ~/.claude/scripts/llaves.sh nombres
bash ~/.claude/scripts/llaves.sh run -- sh -c '[ -n "$LLAVERO_CREADO" ] && echo "el llavero abrió"'

Salida esperada: la lista de nombres (al menos LLAVERO_CREADO) y luego el llavero abrió. Si run falla, la identidad no se encontró: revisa el paso 2.

c) Prueba de comportamiento: el candado deja pasar lo seguro. Con archivos falsos en una carpeta temporal:

T=$(mktemp -d)
printf 'DB_PASSWORD=falso\n' > "$T/.env"
python3 -c 'import json; print(json.dumps({"env": {"CONFIG_VALOR": "hola-mundo"}}))' > "$T/settings.json"
H="$HOME/.claude/hooks/candado-lectura-llaves.py"
printf '{"tool_name":"Bash","tool_input":{"command":"grep -c DB_PASSWORD %s/.env"}}' "$T" | python3 "$H"; echo "conteo=$?"
printf '{"tool_name":"Bash","tool_input":{"command":"cat %s/settings.json"}}' "$T" | python3 "$H"; echo "settings_sin_llaves=$?"
rm -rf "$T"

Salida esperada: sin mensajes, conteo=0 y settings_sin_llaves=0.

d) Prueba señuelo: el candado frena cuando debe. Un .env falso y un settings.json con un valor que tiene forma de token de GitHub (falso, generado aquí) bajo un nombre que no delata nada. Si cualquiera de las dos pasa, el candado no está protegiendo:

T=$(mktemp -d)
printf 'DB_PASSWORD=falso\n' > "$T/.env"
python3 -c 'import json; print(json.dumps({"env": {"CONFIG_VALOR": "ghp_" + "x" * 36}}))' > "$T/settings.json"
H="$HOME/.claude/hooks/candado-lectura-llaves.py"
printf '{"tool_name":"Bash","tool_input":{"command":"cat %s/.env"}}' "$T" | python3 "$H" 2>"$T/msg"; echo "env=$?"; head -1 "$T/msg"
printf '{"tool_name":"Bash","tool_input":{"command":"cat %s/settings.json"}}' "$T" | python3 "$H" 2>"$T/msg"; echo "valor=$?"; head -1 "$T/msg"
rm -rf "$T"

Salida esperada: env=2 seguido de un mensaje que empieza con candado de llaves: frené esto porque cat vuelca «.env» completo, y valor=2 seguido de ... cat vuelca «settings.json» completo. El segundo prueba la detección por valor: el nombre CONFIG_VALOR no parece llave, pero el valor sí.

Cuando las cuatro partes pasen, dile a la persona en una línea qué cambió: las llaves que guardó viven cifradas en el llavero y el candado frena los comandos comunes que las volcarían en claro.

Límites que debes conocer (y contarle a la persona si aplica)

  • agregar no funciona en Linux: usa el portapapeles de macOS o de Windows. En Linux se guarda con guardar-secreto.py.
  • Windows: la identidad queda en un archivo protegido solo por los permisos de tu usuario; esta semilla no la cifra con la protección de datos de Windows.
  • El candado reconoce las llaves por su nombre (TOKEN, SECRET, PASSWORD, API_KEY, ..._KEY, PRIVATE, CREDENTIAL, AUTH...) y por su valor cuando tiene un formato conocido (los de _secretos.py: OpenAI, AWS, GitHub, Google, Slack, Supabase, PEM y otros). La detección por valor solo aplica a los bloques env y headers de un settings*.json o .claude.json; un .env se frena siempre. Una llave con nombre neutro y formato desconocido no la reconoce. Tampoco reconoce el archivo de identidad age: mantenlo fuera de las carpetas de proyectos.
  • El candado es un filtro de lectura, no una bóveda: frena los comandos comunes que vuelcan llaves, pero no puede prever cualquier programa que alguien escriba para leerlas.
  • buscar-secreto.sh muestra los primeros 6 caracteres de lo que encuentra en el ambiente y en la configuración de la terminal, para que se reconozca sin verse entera. Sus capas de repositorios y carpetas extra se configuran con BUSCAR_RAIZ_REPOS y BUSCAR_CARPETAS_EXTRA.
  • run retiene las llaves de la API de Anthropic (ANTHROPIC_API_KEY y ANTHROPIC_AUTH_TOKEN): esa llave cobra por uso al crédito de API y no al plan, y un programa que la encuentra en su ambiente la usa sin avisar. Para entregarlas a un comando, antepón LLAVES_SOLTAR_RETENIDAS=1; para cambiar la lista, define LLAVES_RETENIDAS.
  • Qué se probó y dónde: todo se verificó en Linux. El camino de macOS (identidad en el Llavero de la Mac) se probó con un doble del comando security, no en una Mac real, y el de Windows no se probó en una máquina con Windows.
  • Codex: los scripts funcionan desde su terminal y el candado sabe contestar en el formato de decisión de Codex, pero esta semilla no trae ni verificó el registro del hook en Codex.

✅ ¿Cómo sé que funcionó?

Las llaves que guardes viven cifradas en un solo llavero (las copias viejas que tengas en un .env siguen ahí hasta que las borres). Cuando un programa necesita una, tu asistente lo corre con llaves.sh run y el programa la recibe sin que nadie la vea. Si tu asistente intenta abrir un .env, listar todas las variables o descifrar el llavero a la pantalla, vas a ver un aviso de "candado de llaves: frené esto porque..." con la forma segura de hacer lo mismo. Y si necesitas encontrar una llave perdida, buscar-secreto.sh barre hasta siete capas de tu máquina (algunas solo existen en macOS) y, si no la encuentra, te dice "no la encontré en estas capas", nunca "no existe".

¿Quieres implementarla con acompañamiento?

Agenda una asesoría directa con JP y aterrízala en tu caso · desde $600 MXN.

Agendar una asesoría