Cómo dar un secreto a un agente de código sin ponerlo nunca en el prompt

Un agente necesita una clave de Stripe o una contraseña de base de datos para lanzar su comando. La pegas en el chat y acaba en la transcripción de tu disco, en el proveedor del modelo y a veces en un commit. La regla a la que llegamos después de tres errores propios: un secreto llega a un agente por una variable de entorno y por nada más.

La situación es siempre la misma. Un agente está a mitad de una tarea y necesita llamar a tu API de pruebas de Stripe, lanzar una migración contra la base de datos de staging o publicar en un registro privado. Pide la clave. Tienes la clave en tu gestor de contraseñas, el agente espera, y lo más rápido del mundo es pegarla en el chat.

Ese pegado es el momento en que la clave sale de tu control. No porque el modelo sea malicioso, sino porque un prompt es un objeto almacenado y reenviado: va a un archivo de transcripción en tu disco, va al proveedor del modelo, y lo copia cada reintento y cada subagente que hereda la conversación.

Desarrollamos AgentsRoom, una app de escritorio que ejecuta agentes de código, así que hemos tenido que resolverlo para nosotros mismos. Nos equivocamos tres veces en septiembre de 2026. Esta es la regla a la que llegamos, los tres errores que la produjeron y cómo aplicarla uses o no nuestra app.

La respuesta corta

  • Un secreto llega a un agente por una variable de entorno, y por nada más. Ni un mensaje, ni un ticket, ni un skill, ni una opción de línea de comandos.
  • El agente usa $NAME, nunca el valor. El shell lo expande cuando el comando se ejecuta.
  • Guarda el valor una sola vez, fuera del repositorio, cifrado por el llavero de tu sistema, y referéncialo por su nombre en todos los demás sitios.
  • Un secreto que ha pasado por una conversación está quemado. Rótalo y después haz limpieza.
  • El enmascaramiento es una red de seguridad, no un plan. Atrapa lo que parece una clave, no una contraseña de base de datos que se lee como una palabra.

Por qué ese pegado es peor de lo que parece

Pasan tres cosas a la vez, y solo la primera es evidente.

Va al proveedor del modelo. Esa es la parte que todo el mundo ve. Tu clave está ahora en el cuerpo de una petición, en la infraestructura de otra persona, dentro de una conversación que no puedes editar.

Va a tu disco, en texto plano. Claude Code, Codex, Antigravity CLI y los demás guardan una transcripción de la conversación para que puedas retomarla. Ese archivo está en tu carpeta personal, sin cifrar, mientras el CLI conserve su historial. Busca una vez la cadena sk- en tus transcripciones y mira lo que sale.

Se propaga de lado. La conversación es la memoria del agente, así que todo lo que copia la conversación copia la clave: una sesión retomada, un agente forkeado, un subagente lanzado con el contexto del padre, un resumen escrito en un archivo del proyecto. Hemos visto una clave viajar de un mensaje de chat a la descripción de un ticket y de ahí al primer mensaje de un segundo agente, sin que nadie hiciera nada mal.

Y la red de seguridad habitual no aguanta. El enmascaramiento de la salida funciona por patrón: sk-…, glpat-…, la forma de un JWT. No puede saber que thursday-harbour-71 es la contraseña de tu base de datos de staging. En nuestra propia app enmascaramos los valores reconocidos en la salida de los terminales y aun así lo consideramos una red bajo el trapecio, no el trapecio.

La regla de la puerta única

Aquí está, en una línea: una credencial llega a un agente por una variable de entorno del proceso en el que corre, y por ningún otro canal.

Todo lo demás es texto literal. Un mensaje que escribes, la descripción de un ticket, un archivo de skill, un prompt de sistema, una opción de línea de comandos: todo eso lo lee un humano o un modelo, así que un secreto escrito ahí es un secreto publicado.

La línea de comandos merece su propia frase, porque parece más segura que un mensaje de chat y no lo es. Un valor en --api-key=… es visible para todos los procesos de la máquina a través de ps, aterriza en el historial de tu shell, lo registra cualquier cosa que guarde el comando lanzado, y reaparece en las herramientas de diagnóstico que te vuelven a mostrar la línea del proceso. Una variable de entorno de un solo proceso la pueden leer ese proceso y root. Superficie más pequeña, la misma comodidad.

El agente, por su parte, nunca necesita el valor. Necesita escribir esto:

curl -H "Authorization: Bearer $STRIPE_SECRET_KEY" https://api.stripe.com/v1/charges

El shell expande $STRIPE_SECRET_KEY en la ejecución. El agente escribió diecisiete caracteres y un signo de dólar. Su transcripción contiene diecisiete caracteres y un signo de dólar.

Cómo funciona en nuestra app

El mecanismo importa menos que la forma, pero la forma es más fácil de copiar cuando se ve una implementación.

Una bóveda, un archivo. AgentsRoom guarda los secretos en un único archivo de tu carpeta personal, en modo 0600, cifrado a través del llavero del sistema operativo, reafirmado en cada escritura. Nada se sincroniza con un servidor y nada pasa a otro ordenador: un secreto pertenece a la máquina que lo guarda. Si el llavero no está disponible, el guardado falla en lugar de caer a texto plano.

Guardas un valor una sola vez. Comandos y conexiones > Secretos, en cualquier proyecto. Un nombre (letras, dígitos, punto, guion, guion bajo, 64 caracteres, para que sirva tal cual como nombre de variable de entorno) y un valor, escrito en un campo de contraseña. La fila que aparece muestra el nombre y su referencia, {{secret:STRIPE_SECRET_KEY}}, y un botón que copia la referencia. No hay botón para revelar ni para copiar el valor, porque no existe ningún camino de código que devuelva un valor a la interfaz.

La referencias donde se construye el entorno. Editar agente > Avanzado > Variables de entorno, o los ajustes del proyecto, o los del proveedor, o una línea de comandos de desarrollo:

STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}}

En el arranque, el proceso principal sustituye el valor en el entorno del terminal que está a punto de crear. La interfaz no lo ve nunca, el agente no lo ve nunca, el teléfono no lo ve nunca. La página Gestor de secretos recorre las pantallas.

El agente descubre nombres, no valores. Un agente conectado a nuestras herramientas MCP puede llamar a secrets_list y recibir la lista de nombres presentes en esa máquina. Con eso le basta para ser útil: puede decir «tienes un STRIPE_SECRET_KEY guardado, añade STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}} al entorno de este agente y usaré $STRIPE_SECRET_KEY». No puede leer el valor, ni siquiera mientras ejecuta un comando que lo usa.

Tres errores que publicamos

La regla de arriba parece obvia una vez escrita. Es la cicatriz de tres bugs, todos en septiembre de 2026, todos reportados por usuarios.

Una referencia citada en un ticket salía resuelta

19 de septiembre. Robert documentó un mapeo en la descripción de un ticket, el perfectamente razonable STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}}, para que quien cogiera el ticket supiera qué configurar. Releído por la API, el ticket devolvía la referencia intacta. Pero el agente lanzado desde ese ticket recibía el valor en su primer mensaje.

La causa era un detalle de fontanería con una consecuencia real. El brief de un ticket se convierte en el primer prompt del agente, que viajaba dentro de una variable de entorno del comando de lanzamiento. Nuestro resolutor sustituía todos los valores de entorno, así que también sustituía el prompt. La clave acabó en el disco y en el proveedor del modelo. Rotación obligatoria, sin escapatoria.

La corrección consistió en hacer literal el comando de lanzamiento de un agente: cuando el entorno lleva un identificador de agente, la variable que contiene el primer prompt se deja intacta. Un comando de desarrollo que usa la misma variable no tiene identificador de agente y se resuelve como antes. Aceptamos un efecto secundario a propósito: una referencia escrita en las opciones de CLI de un agente tampoco se resuelve, lo cual es correcto, porque una línea de comandos acaba de todos modos en ps y en los registros.

La lección: la puerta tiene que ser estrecha a propósito. «Sustituir en todas partes donde pudiera servir» es exactamente cómo un secreto acaba donde nunca debería estar.

Una referencia pegada en un mensaje, que nunca se resuelve

22 de septiembre. Cs040618 reportó el problema contrario: «pego la referencia en la conversación y el agente no consigue entender qué es esta clave».

No es un bug. Un mensaje es texto literal por diseño, que es exactamente la corrección de tres días antes. Pero nada en la app lo decía en el momento del gesto, y el agente, al recibir {{secret:…}} en un mensaje, no tenía ni idea de qué responder.

Tres correcciones, todas para decir dónde está la puerta: un aviso que salta cuando pegas una referencia en el campo de redacción y apunta a las variables de entorno, un texto de ayuda encima de la lista de secretos que nombra los tres campos donde se sustituye una referencia y dice «nunca en un mensaje», y una línea en las propias instrucciones del agente que le dice qué responder cuando le llega una referencia en un mensaje.

«Ajustes > Secretos» no existía

28 de septiembre. El agente de Mohammad presentó un reporte: el usuario dice que añadió OTPIQ_API_KEY en Ajustes > Secretos, que lo guardó varias veces a lo largo de treinta minutos, y secrets_list sigue devolviendo los mismos tres secretos antiguos.

El camino de guardado estaba bien. El secreto nunca había llegado a la bóveda, porque no hay ninguna página Secretos en los ajustes. Seis textos de ayuda de la app de escritorio, una página de este sitio y las propias instrucciones del agente enviaban a la gente a una pantalla que no existía. Lo que sí existe en los ajustes es el bloque de variables de entorno, así que ahí fue donde escribió el token, entre llaves, lo cual no es una referencia: nunca sustituido, enviado al agente tal cual, y sincronizado con el proyecto, así que el token salió de la máquina.

Corregimos los textos para que nombren el camino real, hicimos que las herramientas dirigidas a los agentes digan dónde se añade un secreto, y añadimos una comprobación en el bloque de entorno que señala una credencial escrita en claro, un nombre entre llaves que no es una referencia, y una referencia a un secreto que no existe en esta máquina.

La lección aquí no va de secretos en absoluto: un camino equivocado en un texto de ayuda es un bug de seguridad. Lleva al usuario al campo más cercano que parece el correcto.

Si no usas AgentsRoom

La regla viaja. Coge la de estas opciones que encaje con tu instalación; todas terminan en el mismo sitio, una variable de entorno en el proceso en el que corre el agente.

  • El CLI de un gestor de contraseñas. op run -- claude con 1Password, o el equivalente en Bitwarden y Doppler: la herramienta lee referencias de una plantilla, inyecta los valores reales en el entorno del proceso hijo, y no se escribe nada en el disco.
  • direnv con un .envrc en el gitignore. Simple, local, por carpeta. Añade .envrc y .env* a .gitignore antes del primer valor, no después.
  • Un archivo .env que el agente nunca lee. Mantenlo fuera del repositorio y cárgalo en el shell que lanza al agente. Si el archivo está dentro del repositorio, el agente acabará abriéndolo, citándolo en un mensaje, y volverás al punto de partida.
  • Credenciales de nube: que sean de vida corta. GitHub OIDC en CI en lugar de claves AWS de larga duración, y una identidad de carga de trabajo en lugar de un archivo de clave de cuenta de servicio.

Elijas lo que elijas, pon la regla en las instrucciones del agente para que deje de preguntar. Un párrafo en CLAUDE.md o AGENTS.md basta: nunca pedir el valor de una credencial, nunca escribir una en un archivo, usar $NAME y decirle al usuario qué variable configurar. Publicamos un pequeño skill que dice exactamente eso, y ocupa cinco líneas.

Si una clave ya ha pasado por una conversación

En este orden, y el primer paso no es opcional.

  1. Rótala. Un prompt no se puede retirar. Da el valor por comprometido y emite uno nuevo.
  2. Encuentra las transcripciones. Los CLI guardan los archivos de conversación en tu carpeta personal. Busca en ellos el valor antiguo y borra o trunca lo que coincida.
  3. Revisa los sitios que copian texto. El historial del gestor de portapapeles, el ticket o la issue donde se briefeó al agente, cualquier resumen que el agente escribiera en el repositorio, y git log -p si el agente commiteó mientras la tenía.
  4. Y solo entonces, monta la puerta. Nuevo valor detrás de una variable de entorno, referencia por su nombre, e instrucciones del agente actualizadas para que el siguiente no pregunte.

El paso 4 el último, porque un mecanismo limpio alrededor de una clave filtrada es solo una fuga mejor ordenada.

Las preguntas que la gente hace de verdad

¿Por qué una clave de API nunca debe ir en un prompt dirigido a un agente de IA?

Porque un prompt se almacena y se reenvía. Claude Code, Codex y los demás CLI escriben la conversación en un archivo de transcripción en tu disco, y el mensaje en sí va al proveedor del modelo. De ahí, la clave está además en el historial de tu portapapeles, en cualquier registro que repita el mensaje, y en los reintentos y los subagentes que copian la conversación. El enmascaramiento por patrón atrapa cosas con forma de sk- o glpat-, no una contraseña de base de datos que parece una palabra corriente.

¿Cómo usa un agente de código un secreto que no puede leer?

Por una variable de entorno. Pones el valor en una variable de entorno del proceso en el que corre el agente, y el agente escribe $STRIPE_SECRET_KEY en el comando en lugar del valor. El shell lo expande cuando el comando se ejecuta. El agente nunca ve los caracteres, así que nunca entran en su conversación ni en su transcripción.

¿Puedo pasar el secreto como argumento de línea de comandos en su lugar?

Puedes, y no deberías. Una línea de comandos es visible para todos los procesos de la máquina a través de ps, entra en el historial del shell, en los registros de procesos, y en cualquier herramienta de diagnóstico que relea el comando que se lanzó. Una variable de entorno de un solo proceso la pueden leer ese proceso y root, que es una superficie mucho más pequeña.

¿Dónde guarda AgentsRoom los secretos que usan los agentes?

En un único archivo de tu carpeta personal, en modo 0600, cifrado a través del llavero de tu sistema operativo, solo en esa máquina. Nada se sincroniza con un servidor y no hay ningún botón para volver a mostrar un valor. La app muestra nombres, el agente ve nombres, y el valor solo se descifra en el proceso principal, en el momento en que arranca un terminal o un comando.

¿Qué pasa si pego una referencia de secreto en un mensaje al agente?

No se sustituye nada, y es a propósito. Un mensaje, la descripción de un ticket, un skill y un prompt de sistema son texto literal, así que una referencia escrita ahí le llega al agente como texto. Solo se sustituye el valor de una variable de entorno. Lo aprendimos por las malas: una referencia citada en la descripción de un ticket salía resuelta en el primer mensaje del agente lanzado desde ese ticket.

Una clave ya pasó por una conversación. ¿Qué hago?

Rótala primero, antes que cualquier otra cosa, porque un prompt no se puede retirar: está del lado del proveedor y en el archivo de transcripción de tu disco. Después borra o trunca las transcripciones que la contienen, revisa tu gestor de portapapeles, y busca en el repositorio por si el agente la escribió en un archivo. Y solo entonces pon el nuevo valor detrás de una variable de entorno.

En resumen

Un agente no necesita leer tu clave. Necesita un proceso cuyo entorno ya la contenga.

Guarda el valor una sola vez, fuera del repositorio, cifrado por el sistema. Referéncialo por su nombre allí donde se construye el entorno. Deja que el agente escriba $NAME. Mantén literales todos los demás canales, incluidos aquellos en los que la sustitución habría sido cómoda, porque los canales cómodos son precisamente los que se almacenan y se reenvían.

Y cuando te equivoques, que te equivocarás, rota la clave primero y ordena después.

Descargar AgentsRoom

Ejecuta todos tus agentes de IA, en todos tus proyectos, desde una sola ventana.

GratisDescargar AgentsRoom

App complementaria: supervisa tus agentes en movimiento

Usa Claude, Codex, Antigravity CLI u otro proveedor de IA.

Instalar la extensión
Chrome Web Store

Envía bugs y peticiones directamente a tu backlog público.

Multi-proyectos
Multi-proveedor
Multi-agentes
Estado en vivo
Diff y commit
App móvil
Vista previa
Equipos de agentes
Pruebas en navegador
Dev guiada por backlog
Biblioteca de prompts
Biblioteca de skills
Ver todas las funcionalidades

Seguir leyendo