VOLVER

Saca tus secretos del .zshrc con 1Password CLI

5 min de lectura

Mantengo mi configuración de shell en un repo de dotfiles para poder rastrear cambios en git. Pero algunas API keys necesitaban estar disponibles al inicio de la shell, y hardcodearlas en .zshrc no era una opción. Así que las cargué desde un archivo .env separado — uno que se mantenía fuera del repo.

Los secretos seguían en texto plano, solo que en un archivo diferente. Un git add equivocado y estarían en el historial. Entonces pensé: 1Password podría encargarse de esto.

Moví las claves allí y ahora las cargo al inicio de la shell con op inject — una llamada, desbloqueo biométrico, sin archivos en texto plano por ningún sitio.

El problema con los secretos en .env y .zshrc

La mayoría de desarrolladores almacenan secretos de una de estas dos formas: un archivo .env o sentencias export dispersas por las configuraciones de la shell. De cualquier manera, los valores están en texto plano y terminan en copias de seguridad, repos de dotfiles o en el historial de la shell.

Escribí sobre almacenamiento seguro de credenciales para Node.js con cross-keychain. Las variables de entorno de la shell son un problema diferente — se cargan antes de que se ejecute cualquier aplicación, y cada proceso en tu terminal necesita acceso a ellas.

1Password CLI (op) resuelve esto a nivel de shell.

Crea una bóveda y habilita el desbloqueo biométrico

Instala primero la 1Password CLI, luego configura dos cosas antes de tocar tu configuración de shell.

Una bóveda para los secretos. Creé una bóveda llamada development con múltiples entradas para cada servicio. Cada secreto es el campo de credenciales para ese servicio. Esto te da URIs limpias tipo op://development/<NOMBRE_SERVICIO>/credentials. Puedes organizarlo como quieras, pero recomiendo una estructura consistente para facilitar la referencia.

Desbloqueo biométrico. Sin esto, op te pide tu contraseña maestra cada vez que abres una terminal. Habilítalo en la aplicación de escritorio de 1Password bajo Ajustes → Desarrollador → "Integrar con 1Password CLI". Después de eso, op autentica vía Touch ID o el keychain de tu sistema.

Divide tu shell en dos archivos

Con la bóveda lista, reemplaza tu archivo .env con dos archivos de shell. Uno para configuración no secreta que zsh carga (source) automáticamente. Otro para secretos que op inject procesa antes de cargar.

.zshenv — configuración plana, sin secretos

export EDITOR="nvim"
export LANG="en_US.UTF-8"
export PATH="$HOME/.local/bin:$PATH"
export NODE_ENV="development"

Zsh carga este archivo automáticamente en cada invocación de la shell — interactiva, no interactiva, scripts, comandos SSH. Aquí no hay secretos, nunca.

.zshsecrets — referencias a secretos, no valores

export GITHUB_TOKEN="{{ op://development/github/credentials }}"
export ANTHROPIC_API_KEY="{{ op://development/anthropic/credentials }}"
export NPM_TOKEN="{{ op://development/npm/credentials }}"

Es seguro hacer commit a tu repo de dotfiles. Cualquiera que lo lea ve URIs op://, no credenciales.

.zshrc — conéctalo todo

if command -v op &>/dev/null; then
eval "$(op inject -i ~/.zshsecrets 2>/dev/null)" || echo "⚠ op: secrets not loaded"
fi

op inject lee la plantilla, resuelve cada referencia {{ op://... }} contra tu bóveda de 1Password, y saca el resultado con valores reales. eval ejecuta los exports. Si op no está autenticado o no está disponible, la shell arranca igual — simplemente no tendrás los secretos cargados hasta que ejecutes op signin y vuelvas a cargar (re-source).

Por qué op inject en lugar de op read

Dos formas de obtener secretos de op. Lecturas por variable:

export GITHUB_TOKEN="$(op read 'op://development/github/credentials' 2>/dev/null)"

O inyección de plantillas, que es lo que uso yo. La diferencia es el rendimiento. Cada op read genera un subproceso y hace una llamada a la API. Con más de 5 secretos, el inicio de la shell crece 1–2 segundos. op inject resuelve todo en una sola llamada — alrededor de 200–400ms con desbloqueo biométrico habilitado.

La limpieza que no puedes saltarte

Después de migrar, hice tres cosas:

Eliminé cada secreto hardcodeado de mis archivos de shell. Un grep rápido encuentra los rezagados:

grep -rn 'export.*KEY\|export.*TOKEN\|export.*SECRET' ~/.zsh*

Limpié el historial de git de mis dotfiles. Si alguna vez hiciste commit de secretos — incluso si los borraste luego — todavía están en el historial. git filter-repo o BFG Repo-Cleaner pueden purgarlos.

Roté cada token que había estado en texto plano. Incluso si tus secretos nunca tocaron un repo git, pueden haber vivido en copias de Time Machine o en el historial de la shell. Si no estás seguro de tu exposición total, rótalos. No es opcional.

Compromisos

Latencia de inicio. La llamada a op inject añade 200–400ms al inicio de la shell. Sin desbloqueo biométrico, op pide tu contraseña maestra — doloroso si abres terminales frecuentemente. Incluso habilitado, la petición de Touch ID interrumpe el flujo al abrir terminales en sucesión rápida.

Brechas de autenticación. Si op no está autenticado, tus secretos no cargarán. Te darás cuenta cuando un comando falle, entonces ejecuta op signin y source ~/.zshrc. Es una fricción menor y un intercambio razonable por no tener secretos en texto plano.

Sin soporte remoto. Las sesiones SSH a máquinas remotas no tendrán op. Si necesitas secretos allí, necesitarás un mecanismo diferente — cuentas de servicio de op, o reenviar variables específicas a través de la configuración SSH.

Un repo de dotfiles que puedes hacer público

Mi repo de dotfiles es ahora seguro para el público. Cada secreto vive en 1Password, un archivo de plantilla con URIs op:// los carga al inicio, y Touch ID maneja el resto.

Si necesitas secretos en máquinas remotas, la documentación de 1Password CLI cubre cuentas de servicio y reenvío de agente SSH.