Los secretos se filtran: por un env file que alguien subió al repo, por un bundle público, por una línea de log demasiado parlanchina. La pregunta nunca es si alguno se va a escapar, sino cuánto daño puede hacer una sola clave filtrada. Esta es la disciplina con la que trabajo: dónde van de verdad los secretos, cómo acotar una clave para que no te vacíe una cuenta, por qué conviene centralizar en vez de regar copias, y cómo convertir la rotación en un hábito de un solo paso. Terminas sabiendo diseñar pensando en la fuga, en lugar de fingir que nunca va a pasar.

En resumen
- Diseña para controlar el radio del daño, no para el secretismo. La meta no es 'que nadie vea jamás esta clave', sino 'que una clave filtrada no termine siendo una brecha completa.'
- Acota cada clave a una sola tarea para un solo servicio. Si se filtra una clave de analítica de solo lectura, es una molestia. Si se filtra una clave raíz, se va la empresa entera.
- Nunca en el repo, nunca en el bundle del cliente. Todo lo que llega al navegador es público, así que los secretos de servidor se quedan en el servidor, y punto.
- Centraliza en una sola fuente de verdad. Tener el mismo secreto en cinco env files son cinco cosas que actualizar al rotar y cinco lugares donde se te puede olvidar.
- Convierte la rotación en una operación de un solo paso. Si rotar duele, no lo vas a hacer, y eso, no la fuga en sí, es la verdadera vulnerabilidad.
Los secretos se filtran. Se filtran por un env file que alguien subió al repo sin querer, por una clave metida directo en un bundle del cliente que cualquiera puede leer con DevTools, por un stack trace que imprimió una connection string, por una integración de terceros que ya ni recordabas que tenía acceso. El error no es creer que nunca va a pasar. Es diseñar como si no fuera a pasar, de modo que el día que una clave se escapa, queda como lo único entre un atacante y todo lo demás. Esta es la disciplina con la que trabajo de verdad: cómo guardar, acotar, centralizar y rotar credenciales para que un secreto expuesto sea un incidente contenido y no una brecha. Terminas pudiendo diseñar pensando en la fuga, en vez de cruzar los dedos.
Trabajo así todos los días en Ilustrari (que opera como NexoString): sistemas multiagente y de soporte a decisiones sobre Anthropic Claude, TypeScript, Next.js y Supabase, detrás de Traefik en un solo VPS. Es también el mismo razonamiento que le dio forma a Infuse, el gestor de secretos que estoy construyendo. Ahí la meta nunca fue solo esconder claves. Fue controlar el radio del daño, que una fuga te cueste una credencial acotada y una rotación de dos minutos, no tu cuenta.
Importante
Requisitos previos: una app donde tú controlas cómo llegan los secretos al runtime (variables de entorno, un archivo montado o un gestor de secretos), la capacidad de emitir y revocar claves en los proveedores de los que dependes (Supabase, Stripe, Anthropic, una base de datos), y tener clara en la cabeza la diferencia entre lo que es un secreto y lo que es simplemente config. Si todavía no puedes responder "¿qué podría hacer esta clave en concreto si se filtrara ahora mismo?" para cada credencial que tienes, eso es lo primero que esta guía te ayuda a resolver. Todo lo demás se apoya en ahí.
01 · El modelo mental: radio del daño, no secretismo
El enfoque equivocado con los secretos es "mantenlos escondidos." Esconder es necesario, pero por sí solo es una apuesta perdida, porque las formas en que un secreto se puede escapar no paran de crecer: más dependencias, más logs, más colaboradores, más copias. Si toda tu defensa es "nadie va a ver esto nunca," apostaste el negocio a una moneda que se sigue tirando una y otra vez.
El enfoque correcto es el radio del daño. Asume que cada secreto se va a filtrar tarde o temprano, y pregúntate: cuando pase, ¿qué es lo peor que puede hacer quien tenga esa clave? Tu trabajo es achicar ese peor caso. Si se filtra una clave de analítica de solo lectura, alguien podrá leer unos números, lo cual es molesto pero se sobrevive. Si se filtra una credencial raíz de base de datos, alguien puede leer, modificar y borrar todo, y eso no es un incidente, es el fin de la empresa. El mismo tipo de evento, un costo radicalmente distinto, y la diferencia depende por completo de cómo diseñaste la clave.
Este replanteo cambia todas las decisiones que vienen después. Dejas de optimizar para "que la vea menos gente" y empiezas a optimizar para "si la ven, que haga lo menos posible y la podamos matar rápido." Por eso acotar, centralizar y rotar pesan más que cualquier truco ingenioso para esconder.
Consejo
Haz este ejercicio una vez con cada credencial que tengas: escribe una sola frase que describa el daño máximo si esa clave exacta se publicara ahora mismo. Las claves cuya frase te da miedo son las que hay que acotar, aislar o meter detrás de un gestor primero.
02 · Guarda los secretos donde no se puedan subir al repo ni al cliente
Las dos fugas más comunes no tienen nada de sofisticadas. Son un secreto en el repo de git y un secreto en el bundle del cliente. Cierra esas dos antes que nada.
Nunca en el repo. Ni en el código, ni en config versionada, ni en un env file subido al repo. Un secreto en git queda en el historial de git, así que borrar la línea en un commit posterior no sirve de nada. El valor sigue siendo recuperable desde cualquier clon. El setup defensivo es aburrido pero efectivo: mantén los valores reales en un env file ignorado por gitignore, sube al repo solo un .env.example con las claves y valores de mentira, y corre un scanner de secretos en pre-commit para que una clave ni siquiera llegue al historial.
Nunca en el bundle del cliente. Esta es la que les pega justo a los equipos de Next.js. Cualquier variable de entorno con prefijo NEXT_PUBLIC_ queda incrustada en el JavaScript que se envía al navegador. Es, por diseño, pública. La clave anon de Supabase sí va ahí. La clave service_role de Supabase, tu clave de Anthropic, tu clave secreta de Stripe y la URL de tu base de datos no van ahí de ninguna manera. La regla es simple: si un valor se lee en un Server Component, un Route Handler o un server action, puede seguir siendo secreto. Si se lee en código del cliente, asume que ya lo tiene todo el mundo.
// En una app Next.js: el prefijo es toda la frontera de seguridad.
// SEGURO — pensado para ser público; RLS protege los datos detrás.
const url = process.env.NEXT_PUBLIC_SUPABASE_URL
const anon = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
// SECRETO — se lee solo en código de servidor, nunca con NEXT_PUBLIC_.
// Si esto termina en código del cliente, es una brecha publicada.
const serviceRole = process.env.SUPABASE_SERVICE_ROLE_KEY
Atención
Un secreto que alguna vez se subió a git, aunque haya sido una sola vez, aunque después se haya revertido, hay que tratarlo como ya comprometido. Rótalo de inmediato. No creas que lo "arreglas" editando el historial y esperando que nadie haya clonado el repo mientras tanto. Lo mismo vale para cualquier clave que alguna vez se imprimió en un log o se pegó en un chat. Asume que quedó expuesta, y rota.
03 · Acota cada clave a una sola tarea
Acotar es donde de verdad se gana o se pierde el radio del daño, y es el paso que más se salta, porque la clave que sirve para todo "funciona y ya." Esa comodidad es justamente la vulnerabilidad.
Una credencial bien acotada hace una sola tarea para un solo servicio con el mínimo privilegio que esa tarea necesita. Piensa qué puede tocar cada clave y achícalo a propósito:
- Mínimo privilegio. Un servicio que solo lee recibe una clave de solo lectura. Un worker que solo escribe en una tabla recibe una clave que solo puede escribir en esa tabla. Nadie recibe "admin" por defecto.
- Un propósito por clave. No reuses la misma clave entre tu app web, un cron job y un script de analítica. Tener claves separadas hace que una fuga en un camino no comprometa los demás, y puedes revocar una sin romper el resto.
- Entornos separados. Producción y staging nunca comparten credenciales. Una clave de staging filtrada no debería ni poder tocar datos de producción.
En el caso de Supabase, esto encaja al pelo. La clave anon es pública y deliberadamente débil: solo puede hacer lo que Row-Level Security permita. La clave service_role se salta RLS por completo y es prácticamente root, así que se queda estrictamente del lado del servidor y se usa solo donde de verdad necesitas actuar por fuera de los permisos de un usuario. El verdadero trabajo de acotar está en las políticas de RLS en sí. Son las que hacen que sea seguro publicar la clave pública, porque aunque un atacante la tenga en mano, solo va a leer las filas que tus políticas permiten.
-- RLS es lo que hace seguro shipear la clave anon pública.
-- Sin política, la tabla está cerrada; con esta,
-- un usuario llega solo a sus propias filas — clave anon o no.
alter table profiles enable row level security;
create policy "solo el perfil propio"
on profiles for select
using ( auth.uid() = user_id );
La prueba que demuestra que una clave está acotada
Agarra cualquier clave y pregúntate: "Si publicara esto, ¿cuál sería el radio del daño?" Para una clave anon de Supabase bien acotada y con buen RLS detrás, la respuesta honesta es "pueden leer lo que cualquier visitante sin login ya podía leer," y eso está bien. Para una clave service_role, la respuesta es "se vuelven dueños de la base de datos," y por eso nunca sale del servidor. Si no puedes dar una respuesta corta y específica para una clave, es que todavía no está acotada.
04 · Centraliza en una sola fuente de verdad, y luego inyecta en runtime
Una vez que las claves están acotadas, el modo de falla pasa de "demasiado amplia" a "demasiado regada." Copias del mismo secreto terminan en un env file local, en la config de CI, en el host de producción, en la máquina de un compañero y en un servidor de staging olvidado. Ahora rotar significa salir a cazar cinco copias, y se te va a escapar una: la que mantiene viva una clave revocada o, peor, la que nadie recuerda que existe.
Centraliza. Una sola fuente de verdad, un almacén gestionado de secretos o un gestor como Infuse, guarda el valor real, y cada entorno lo busca al arrancar en vez de quedarse con su propia copia en disco. Este es el único cambio que hace que rotar sea de verdad un solo paso: actualizas el valor en un lugar y el próximo deploy o restart lo toma en todas partes. Además te da algo que un env file jamás te va a dar, un registro de auditoría de qué servicio leyó qué secreto y cuándo, que es exactamente lo que necesitas el día que estés investigando una fuga.
El patrón de inyección en runtime es el mismo sin importar dónde viva la fuente de verdad: la app arranca, le pide al almacén los secretos que tiene permitido leer, los mantiene en memoria mientras dure el proceso, y nunca los escribe a disco. La imagen del contenedor se mantiene limpia, el repo se mantiene limpio, y el único lugar donde el secreto existe en texto plano es la memoria del proceso en ejecución.
clave del proveedor -> acotada, de un solo propósito -> guardada una vez en el vault
-> buscada al boot -> mantenida en memoria -> nunca escrita a disco
Nota
No necesitas un gestor pesado desde el día uno. Para un solo VPS, un env file ignorado por gitignore, con permisos cerrados e inyectado en runtime es un punto de partida perfectamente honesto. Cumple con "ni en el repo, ni en el bundle, inyectado en runtime." Da el salto a un gestor de secretos de verdad cuando tengas varios servicios, varios entornos, o cualquier secreto cuya fuga de verdad te dolería. Ajusta la herramienta al radio del daño, no a la moda.
05 · Convierte la rotación en un hábito, y asume lo peor ante una fuga
Un secreto que no puedes rotar fácil es un secreto que no vas a rotar, y una credencial que nunca se rota es una que estuvo válida durante cada fuga que no notaste. Rotar tiene que ser lo bastante barato como para volverse rutina, que es toda la razón de centralizar en el paso 04: cuando hay un solo lugar para cambiar el valor, rotar deja de ser un proyecto.
Rota por calendario y ante sospecha. El calendario limita cuánto tiempo es válido cualquier secreto, así que hasta una fuga no detectada tiene la vida corta. La rotación ante sospecha es la que importa en un incidente real, y el único orden seguro es: emite la clave nueva, despliégala y verifícala, y luego revoca la vieja. La mayoría de los proveedores deja que varias claves sean válidas a la vez, y esa ventana de solape es tu red de seguridad. Si revocas primero, de golpe cada servicio en ejecución se queda con una clave muerta.
Cuando una fuga sí ocurre, la postura correcta es "asume que se usó." Que los logs se vean limpios significa que no viste el abuso, no que no lo hubo. Los logs de uso van con retraso, muestrean y se les escapan cosas, y un atacante cuidadoso se mimetiza. Así que rotas la clave, y luego auditas todo lo que pudo haber alcanzado y revisas el uso y el gasto en el proveedor. La razón de que esto sea sobrevivible y no catastrófico es todo lo que hiciste antes, río arriba: una clave acotada tiene poco que auditar, una clave centralizada se rota rápido, y una clave que nunca estuvo en el repo ni en el bundle probablemente ni siquiera se filtró por el camino fácil.
Diseñar pensando en la fuga no es pesimismo. Es la diferencia entre una rotación tranquila de dos minutos y un fin de semana entero averiguando qué le hizo a tu base de datos una clave raíz sin acotar. Arma el scoping, la fuente de verdad central y la rotación de un solo paso antes de necesitarlos, y el día que un secreto se escapa pasa a ser un evento de rutina en vez de una emergencia. Eso, y no el secretismo perfecto, es para lo que de verdad sirve la gestión de secretos.
Puntos clave
- Diseña pensando en el radio del daño, no en el secretismo: asume que cada clave se filtra tarde o temprano y achica el peor caso.
- Mantén los secretos fuera del historial de git y del bundle del cliente. En Next.js, NEXT_PUBLIC_ es una frontera pública, así que la service_role y las claves de API se quedan en el servidor.
- Acota cada clave a una sola tarea con mínimo privilegio. En Supabase, RLS es lo que hace seguro publicar la clave anon pública.
- Centraliza en una sola fuente de verdad e inyecta en runtime, para que rotar sea un solo paso y tengas registro de auditoría.
- Haz que rotar sea barato y de rutina, rota en el orden seguro (emite, despliega, verifica, revoca), y ante cualquier fuga parte de 'asume que se usó.'
Preguntas frecuentes
¿La clave anon de Supabase no es un secreto que debería esconder?
No. Está diseñada para ser pública y enviarse al navegador, y tratar de esconderla solo te enreda el modelo de amenazas. Lo que de verdad protege tus datos es Row-Level Security: la clave anon solo puede hacer lo que tus políticas de RLS permitan, así que con buenas políticas un atacante que la tenga no va a leer más de lo que un visitante sin login ya podía. Las claves que sí debes esconder son las que se saltan RLS, sobre todo la service_role, que es prácticamente root y nunca debe salir del servidor.
Saqué el secreto del código en un commit nuevo. ¿Ya estoy a salvo?
No. Borrar la línea en un commit posterior deja el valor en el historial de git, recuperable por cualquiera que clone el repo o se ponga a navegar los commits viejos, y si el repo alguna vez fue público o se compartió, asume que ya lo copiaron. El único arreglo real es rotar la clave filtrada para que el valor expuesto deje de funcionar. Reescribir el historial puede limpiar el repo después, pero la rotación es el paso que de verdad cierra el hueco. Trata cualquier secreto subido al repo como comprometido desde el instante en que entró.
¿De verdad necesito un gestor de secretos, o con un env file me arreglo?
Para un solo VPS con una sola app, un env file ignorado por gitignore, con permisos cerrados e inyectado en runtime es un punto de partida honesto. Cumple con 'ni en el repo, ni en el bundle, inyectado en runtime.' Das el salto a un gestor de secretos de verdad cuando tienes varios servicios o entornos buscando los mismos secretos, cuando necesitas un registro de auditoría de quién leyó qué, o cuando la fuga de cierto secreto de verdad te dolería. Ajusta la herramienta al radio del daño. Sacar un gestor pesado en un proyecto de hobby es tanto error como regar claves en diez archivos en uno serio.
¿Cómo sé si es seguro poner un valor en NEXT_PUBLIC_?
Hazte una sola pregunta: ¿estarías tranquilo imprimiendo este valor en la home para que lo lea cualquiera? Todo lo que lleva el prefijo NEXT_PUBLIC_ queda incrustado en el bundle del navegador y es funcionalmente público, así que la prueba es si la seguridad del valor depende de que se quede escondido. La URL de Supabase y la clave anon pasan la prueba, porque su seguridad viene de RLS, no del secretismo. Una clave service_role, una de Anthropic, una secreta de Stripe o una connection string de base de datos la reprueban sin discusión. Si ves alguna con NEXT_PUBLIC_, trátala como una fuga y rota.
¿Qué significa de verdad 'acotar una clave' si mi proveedor solo me da una?
Si el proveedor solo emite una clave que sirve para todo, el acotamiento tiene que ocurrir de tu lado. Envuelves esa clave detrás de un proxy ligero del lado del servidor que expone únicamente la operación puntual que cada cliente necesita, y repartes tus propios tokens acotados en vez de la clave del proveedor. De eso se trata buena parte de Infuse. Donde el proveedor sí ofrece acotar (claves de solo lectura, claves de API restringidas, RLS, roles de IAM), úsalo directo. El principio es el mismo en ambos casos: nada río abajo debería cargar una credencial más amplia de lo que su tarea exige.
¿Cada cuánto debería rotar los secretos si no se filtró nada?
Rota con una frecuencia acorde al radio del daño del secreto. Una clave de amplio alcance y alto impacto se gana un ciclo más ajustado que una estrecha de solo lectura, y rota siempre ante sospecha, sin importar el calendario. La razón honesta para hacerlo por calendario aun sin fuga conocida es que 'sin fuga conocida' solo significa que no detectaste ninguna. La rotación programada le pone tope a cuánto tiempo sigue siendo útil una fuga que no notaste. El requisito real es que rotar sea de un solo paso, porque una cadencia trimestral que en la práctica no puedes ejecutar es peor que una realista que sí vas a mantener.
¿Prefieres que lo hagamos por ti?
Esto mismo lo construimos para negocios como el tuyo. La primera conversación es gratis y sin compromiso.
Escríbenos por WhatsAppPrimera conversación gratis. Te responde el fundador.
Recursos relacionados

Rota un secreto filtrado con env y Docker, en el orden seguro y sin downtime
Que se filtre una API key es algo de rutina, no una catástrofe, siempre que hayas dejado la rotación lista antes de necesitarla. Este es el recorrido completo que sigo en cada contenedor: dónde van de verdad los secretos, el orden emitir-desplegar-verificar-revocar que nunca tumba el tráfico en producción, las trampas de Docker que dejan tus claves grabadas en la imagen para siempre, y un ejemplo paso a paso que puedes copiar. Terminas sabiendo rotar bajo presión sin tener que adivinar.

Secretos para flotas de agentes: credenciales de vida corta, mínimo privilegio y nunca en el prompt
Una sola API key estática compartida entre toda una flota de agentes es una brecha que tarde o temprano va a pasar. Estos son los patrones que usamos para acotar, rotar y auditar credenciales, de modo que cuando un secreto se filtre, el daño sea mínimo.

Skill de auditoría de seguridad: una revisión repetible de tu código antes de que se te complique
Un skill de auditoría de seguridad le da siempre la misma revisión aburrida y estructurada a tu código (secretos filtrados, authz que falta, inyección, cripto débil) y te devuelve una lista priorizada con archivo:línea y una etiqueta de confianza. Aquí te explico cómo armar uno que de verdad ayude en vez de ahogarte en hallazgos de «quizás deberías revisar esto».