Caja Fuerte de claves
Un agente que trabaja de verdad necesita credenciales: clonar un repositorio privado, conectar un MCP, entrar a un servidor y desplegar. Hasta ahora la única forma de dárselas era pegarlas en el chat, y eso significa que el token viaja al proveedor de IA y se queda en el historial.
La Caja Fuerte rompe ese trato. Los secretos se guardan cifrados en el proyecto y el agente no trabaja con el valor, sino con una referencia: «secreto:azure/pat/lord-denihol». La aplicación sustituye esa referencia por el valor real al lanzar el proceso, ya fuera del alcance del modelo.
La promesa
Sección titulada «La promesa»La clave nunca es texto del chat, ni de un prompt, ni de una salida de herramienta.
En la práctica son tres garantías:
- El agente pide permiso, no el valor. Cuando necesita una credencial te pregunta si autorizas usar
mantis/prod/contextcode; nunca recibe la cadena. - Nada de lo que vuelve de una herramienta la delata. Lo que imprime un comando, lo que devuelve un archivo leído o un servidor MCP pasa por un filtro antes de llegar al modelo: el valor se sustituye por su referencia. Es lo que tapa los clásicos
echo $TOKEN,env,cat .envygit remote -vcon el token en la URL. - El valor solo se expande donde puede hacer falta. Comandos, servidores MCP marcados de confianza y
ssh/scp. Nunca al escribir archivos.
Es una garantía de arquitectura, no de disciplina: el descifrado ocurre en el núcleo de la aplicación (Rust) y ni la interfaz ni el modelo ven el valor en ningún momento.
Crear la caja
Sección titulada «Crear la caja»El icono de Caja Fuerte está en la barra lateral, justo al lado de Skills y MCP. La caja es por proyecto: abre primero la carpeta con la que trabajas.
-
Pulsa el icono de la Caja Fuerte.
Si el proyecto no tiene caja todavía, aparece la pantalla Crear la Caja Fuerte del proyecto.
-
Elige la contraseña maestra.
Se teclea dos veces en un campo seguro (puntos negros, fuera del chat) y exige al menos 8 caracteres: esa clave cifra todos los secretos del proyecto.
-
Guárdala en tu gestor de contraseñas.
La maestra no se puede recuperar. La app te lo dice al crear la caja, y el único respaldo es la exportación de emergencia.
-
Pulsa Crear caja.
La caja queda creada y abierta. La cabecera del panel muestra en todo momento si está Abierta o Cerrada, de qué fuente sale y si la maestra está en memoria.
Dónde se guarda cada cosa
Sección titulada «Dónde se guarda cada cosa»| Qué | Dónde | ¿Va al repositorio? |
|---|---|---|
| La clave derivada de la maestra | Llavero del sistema (Credential Manager, Keychain, Secret Service), una entrada por repositorio | No |
| Los valores cifrados | .context/secretos/equipo.enc (AES-256-GCM) | Sí, se commitea |
| Los metadatos | .context/secretos/equipo.enc.meta (texto claro: id, tipo, descripción, servicio, usuario, fechas, fortaleza) | Sí |
El .meta nunca lleva valores. Sirve para dos cosas: listar el panel sin abrir la caja y que los diffs de Git sean legibles (se ve quién añadió o rotó un secreto, sin ver nada secreto).
La derivación de la maestra (Argon2id) se hace una vez por sesión y la clave queda en memoria; se borra al cerrar la app. Si el llavero del sistema no está disponible, la maestra vive solo en esa sesión y el panel lo avisa con «maestra en memoria».
Guardar un secreto
Sección titulada «Guardar un secreto»Con la caja abierta, la barra del panel ofrece Nuevo secreto, Nuevo servidor, Importar .env y Exportar.
En Nuevo secreto rellenas el id en tres partes —sistema (azure-devops, mantis…), entorno (prod, pat…) y usuario— y el panel te enseña el id resultante: azure-devops/pat/lord-denihol. Ese id es el nombre con el que el agente te pedirá autorización, así que conviene que se entienda.
Los tipos son contraseña, token, clave SSH, OAuth, variable de entorno y servidor.
El valor no se escribe en un campo normal: pulsas Elegir valor seguro… y se abre el campo seguro, un cuadro propio de la aplicación con el valor oculto, sin autocompletado y sin paso por el chat. Lo que teclees va directo al núcleo cifrado.
El badge de fortaleza
Sección titulada «El badge de fortaleza»Las contraseñas se evalúan al guardarlas (y la contraseña de un servidor, también). No es un contador de caracteres: mide lo adivinable que es la clave —diccionarios, nombres propios, palabras de tu empresa o proyecto, fechas, secuencias de teclado, sustituciones tipo @ por a— y el resultado sale en la lista:
| Badge | Qué significa |
|---|---|
| Muy débil (rojo) | Se rompe en minutos. Se puede guardar, pero el panel no lo disimula. |
| Débil (naranja) | Aguanta poco. Toca cambiarla cuando puedas. |
| Fuerte (verde) | Sin patrones reconocibles y con longitud suficiente. |
| Sin evaluar | Tokens, claves SSH y demás: ahí no se mide fortaleza (los genera el servicio, y lo que importa es el formato y la caducidad). |
El contexto cuenta: si el servicio es una URL pública o el usuario tiene permisos altos (sa, administrator), el mínimo aceptable sube y una clave mediana genera aviso de rotación.
Un ejemplo real: Ds910913@ parece decente —mayúscula, dígitos, símbolo— y sale naranja o roja. Son unas iniciales, una fecha y un símbolo al final: nueve caracteres con un molde que los diccionarios conocen de memoria. La app no te prohíbe guardarla; te dice la verdad sobre ella.
Entradas de tipo servidor
Sección titulada «Entradas de tipo servidor»Guardar la IP por un lado y la contraseña por otro no le sirve a nadie, así que Nuevo servidor agrupa todo lo necesario para entrar:
-
Datos de la conexión.
Host / IP, Puerto (22 por defecto) y Usuario. No son secretos: van en claro en el
.meta, porque el agente los necesita para construir el comando. -
Autenticación.
Contraseña, Clave SSH o Agente del SO. La contraseña, la clave privada y su passphrase entran por el campo seguro; la clave se pega en un cuadro que nunca la muestra entera.
-
Guardar servidor.
-
Probar conexión.
La aplicación ejecuta un
sshde verdad y te dice solo Conexión correcta o No se pudo conectar con el motivo (autenticación rechazada, host inalcanzable, tiempo agotado). La clave privada pasa por un archivo temporal con permisos restringidos que se borra al terminar.
Si ya tenías esa máquina en el panel de conexiones remotas, no hace falta teclear nada dos veces: desde el selector de conexiones, Guardar en la caja abre este formulario con host, puerto y usuario ya puestos. Solo falta el secreto, que entra por el campo seguro.
Cómo lo usa el agente
Sección titulada «Cómo lo usa el agente»El agente puede listar la caja (ids y metadatos, nunca valores) y pedir usar un secreto. Cuando lo hace, te llega una tarjeta de Aprobación requerida con el id y el propósito que él mismo ha escrito («para clonar el repositorio de la organización»), y tres salidas:
- Permitir — vale para este uso.
- Permitir siempre — crea una regla del proyecto. No autoriza la caja entera: se recuerda por patrón de sistema (
mantis/*a partir demantis/prod/contextcode), caduca a los 30 días y se revoca cuando quieras desde el panel. La propia tarjeta te dice qué vas a conceder antes de que pulses. - Denegar — el agente se queda sin la credencial y tiene que seguir sin ella.
Si autorizas, el valor se descifra dentro de la aplicación y se inyecta al proceso, no al texto. El agente escribe la referencia en el comando y la app la sustituye al lanzarlo:
git clone https://oauth2:«secreto:azure/pat/lord-denihol»@dev.azure.com/org/proy/_git/repossh «secreto:servidor/prod/mi-server» ./deploy.shEn el segundo caso la referencia se convierte en usuario@host y la contraseña o la clave se inyectan solas.
Dónde se expande y dónde no
Sección titulada «Dónde se expande y dónde no»| Sitio | ¿Se expande? |
|---|---|
| Argumentos y variables de entorno de un comando | Sí |
ssh, scp, sftp, rsync construidos por la app | Sí |
| Servidores MCP marcados de confianza en este proyecto | Sí, en su entorno |
| MCP de terceros no marcados | No |
| Escrituras de archivos (Write/Edit) | Nunca, tampoco en un .env |
Esa última fila es deliberada: si el agente pudiera «pasar» la clave a un archivo del repositorio, después podría leerla él mismo y la promesa se caería.
El tapado de salidas
Sección titulada «El tapado de salidas»La inyección por variable de entorno no basta, porque el valor vuelve por la salida. Todo lo que un agente recibe de una herramienta —salida y errores de la consola, contenido de archivos leídos, resultados de MCP— pasa por el filtro antes de llegar al modelo:
| El agente ejecuta | Lo que el modelo lee |
|---|---|
echo $TOKEN | «secreto:azure/pat/lord-denihol» |
env | la línea del token, con la referencia en su lugar |
cat .env | cada valor reconocido, sustituido |
git remote -v | la URL con la referencia donde iba el token |
Se tapan también las formas base64, URL-encoded y con escapes JSON del valor, porque los servidores las devuelven así (Authorization: Basic …, https://usuario:clave@…, {"token":"…"}).
Dos detalles importantes:
- El filtro solo cubre los secretos en uso en esta sesión (los que autorizaste en el turno o los que cubre una regla activa), no la caja entera en todo momento.
- Los valores de menos de 8 caracteres no se tapan. Al guardar uno, la app te advierte de que no se tapará.
Mientras hay secretos autorizados, el compositor muestra un indicador: «N secretos en uso este turno», que abre el panel de un clic.
Cuando el secreto aparece donde no debe
Sección titulada «Cuando el secreto aparece donde no debe»En el chat, al escribir
Sección titulada «En el chat, al escribir»El compositor analiza lo que teclas, antes de enviarlo. Reconoce tokens de Azure DevOps y GitHub, claves de OpenAI, Anthropic y Google, claves de AWS, JWT, claves privadas PEM y los clásicos password= / token:; y, además de los patrones, mide la entropía para pillar tokens que no encajan en ningún molde. Los hashes de Git, los UUID y los marcadores tipo Password=<password> no se marcan.
Si hay coincidencia sale una franja, que no bloquea el envío:
Esto parece una credencial. Se enviará al proveedor de IA y quedará en el historial.
- Guardar en caja y enviar referencia (la opción por defecto): la credencial entra en la caja y el texto que viaja al proveedor lleva la referencia en su lugar. En tu propio mensaje verás la versión censurada.
- Enviar igual: se manda tal cual, bajo tu responsabilidad.
La X solo descarta el aviso para ese texto.
En un archivo que abres o guardas
Sección titulada «En un archivo que abres o guardas»Al abrir un archivo, el editor analiza sus primeras líneas y marca las que parecen llevar secretos. Si hay, aparece una franja:
Este archivo contiene N posible(s) secreto(s).
Con Guardar en la caja pasan a la caja cifrada… y el archivo no se toca. La app te lo confirma: «El archivo NO se ha modificado: las referencias «secreto:id» nunca se escriben en archivos». No es un olvido; es la regla de más arriba.
El mismo aviso vuelve a ofrecerse al guardar una pestaña con secretos, una vez por archivo y sesión.
El panel, pestaña por pestaña
Sección titulada «El panel, pestaña por pestaña»Secretos
Sección titulada «Secretos»La lista trae id, tipo, fortaleza, expiración y último uso, con filtros por sistema y por entorno. Al entrar en uno:
- Mostrar — pide la maestra otra vez («Confirmar identidad») y enseña el valor, que se oculta solo a los 30 segundos.
- Copiar — copia sin que el valor pase por la interfaz y avisa: «Copiado. El portapapeles se borrará solo en 30 segundos».
- Validar — devuelve la fortaleza (0-4) con sus motivos, y si el secreto pasó por un chat en claro lo dice sin rodeos: se considera comprometido, rótalo cuanto antes.
- Eliminar — lo borra de la caja cifrada con confirmación; el agente deja de poder usarlo.
- Rotar — está en su sitio, desactivado, con la nota «Rotación guiada: disponible pronto».
Permisos
Sección titulada «Permisos»Cada Permitir siempre que hayas concedido, con su patrón, su ámbito y su caducidad; las caducadas se marcan como tal y ya no autorizan nada. Revocar deja las cosas como al principio: el agente volverá a pedir autorización cada vez.
Audit log
Sección titulada «Audit log»Una tabla local con fecha, secreto, quién autorizó y propósito de cada uso, más los borrados. Vive en la base de datos cifrada de la aplicación y nunca guarda valores: sirve para saber qué se usó y para qué, no para recuperar nada.
Importar un .env
Sección titulada «Importar un .env»Pegas el contenido del archivo, el asistente reconoce los pares CLAVE=valor, los lista y tú desmarcas los que no deban entrar. Es la vía rápida para un proyecto que ya llevaba sus claves en texto claro.
Exportación de emergencia
Sección titulada «Exportación de emergencia»Genera un archivo cifrado con la caja entera, protegido por una frase de paso propia (no es la maestra: puedes usar otra). Es el único respaldo si pierdes la maestra, así que guárdalo fuera de este equipo. Se escribe en la carpeta de datos de la aplicación, nunca dentro del repositorio.
En equipo
Sección titulada «En equipo»- Una maestra por repositorio, compartida por el gestor de contraseñas del equipo. Context Code no distribuye maestras ni las manda a ningún sitio.
- Al clonar un repositorio que ya trae
equipo.enc, el panel lo detecta y pide la maestra; una vez abierta, queda asociada a tu llavero y no vuelve a pedirla en cada sesión. - Cada persona usa su propio usuario en los servicios. El usuario de servicio (
ContextCode, o como lo llames) es para los agentes. Así el audit log dice algo. - El
.metaen claro hace que los cambios de la caja se revisen como cualquier otro diff: se ve que alguien añadiómantis/prod/contextcodeel martes, sin ver el valor.
Lo que viene
Sección titulada «Lo que viene»Tres piezas están diseñadas pero todavía no están en la aplicación:
- Rotación guiada: abrir la guía del servicio (Azure DevOps, GitHub, Google, Mantis), teclear la clave nueva en el campo seguro y sustituir la vieja en un paso. El botón Rotar ya está en el detalle, desactivado.
- Importador de la caja manual: traerse de golpe la hoja de claves que el equipo llevaba a mano.
- Asistente de salida de miembro: rotar la maestra y recorrer en serie los secretos que hay que cambiar.