Fundamentos de seguridad
Cómo rotar una clave service_role de Supabase filtrada
Supabase dice que primero cierres la fuga. Otras guías dicen que rotes ya. Cuál es lo correcto depende de dónde se filtró tu clave service_role.

En resumen
- Si tu clave service_role de Supabase está en el JavaScript que descarga un navegador, rótala antes de arreglar nada. La copia que ya circula no caduca.
- Si solo llegó a un repositorio privado o a un registro, cierra primero la fuente, o tu clave nueva saldrá detrás de la vieja en el siguiente despliegue.
- Una clave service_role antigua no se puede rotar. Invalidarla significa crear las nuevas claves sb_secret_, mover tu app a ellas y desactivar el par antiguo con un interruptor que afecta a los dos.
Alguien te ha dicho que tu clave service_role de Supabase está en tu app,
donde cualquiera puede leerla. Antes de cambiar nada, asegúrate de que es esa la
clave que encontraron: nuestro escaneo gratuito lee tu sitio en
producción igual que lo leería un desconocido y te dice qué clave de Supabase ve
ahí de verdad. Sin cuenta y sin instalar nada.
El consejo que sigue a un hallazgo así es casi siempre una palabra: rota. Luego buscas cómo se hace y las fuentes se contradicen. La propia documentación de Supabase abre su guía de rotación diciéndote que arregles la causa de la fuga antes de empezar. Media docena de guías de terceros te dicen que rotes de inmediato y preguntes después.
Esta es la parte que ninguna de las dos escribe: las dos tienen razón en situaciones distintas, y la pregunta que las separa es adónde se filtró la clave.
Una cosa clara antes de todo. Borrar la clave de tu código quita tu propia copia del llavero. Rotarla cambia la cerradura. Solo lo segundo alcanza las copias que otros ya tienen.
¿Es de verdad la clave service_role?
En un proyecto nuevo lo responden los primeros caracteres. sb_secret_ al
principio es la secreta. sb_publishable_ es la clave que debe estar en tu app,
y encontrarla ahí no es ningún hallazgo.
Asegúrate antes de hacer nada drástico, porque la clave que aparece en una app hecha con vibe coding suele ser la que sí pertenece a ese sitio.
Los proyectos antiguos entregan otro par, anon y service_role, y las dos se
parecen muchísimo: mismo formato, misma longitud, ningún prefijo que leer. La
sección central de una de esas claves no está cifrada en absoluto. Se decodifica
en unas líneas de texto plano, y una de ellas indica el rol.
Qué claves de API son seguras en tu frontend
recorre las dos comprobaciones.
El motivo para asegurarte es que esto es más raro de lo que sugieren las
advertencias. Escaneamos 30.998 apps hechas con vibe coding en agosto de 2026 y
encontramos una clave service_role publicada en 3 de ellas. Una clave de API
de Google, que suele ser inofensiva y a menudo está limitada a un dominio,
apareció en 1.142.
El recuento completo está aquí.
¿Rotar primero o cerrar la fuga primero?
Depende de si la clave ya es pública, y la línea entre los dos casos es limpia.
Si la clave está en el JavaScript que descargan tus visitantes, ya es pública. Cualquiera que cargara tu sitio mientras estaba activa tiene una copia, y también la tiene cualquier rastreador automático que fuera buscando exactamente esa cadena. Nada de lo que cambies en tu código alcanza esas copias. Rota primero y después cierra la fuente.
Si solo llegó a un repositorio privado, un archivo de registro, una variable de CI o un chat, la exposición está acotada. Si aquí rotas primero, lo harás dos veces, porque el siguiente despliegue vuelve a sacar el valor viejo de aquello que lo produjo y tu clave nueva sale detrás de la vieja. Cierra la fuente y luego rota.
La guía de Supabase está escrita para el segundo caso. Las guías urgentes están escritas para el primero. Si estás leyendo esto porque un escáner encontró la clave en tu URL en producción, estás en el primer caso.
Cómo rotar una clave secreta de Supabase
Si tu proyecto tiene claves sb_secret_, esto es cosa del panel y sin caída de
servicio.
Un proyecto puede tener varias claves secretas a la vez, cada una con su nombre, y eso es lo que lo hace seguro: añades la nueva antes de quitar la vieja, así que entre medias no hay nada roto.
- Abre tu proyecto, ve a Settings → API Keys y crea una clave secreta nueva.
- Ponla en todos los sitios donde se usaba la vieja, que deberían estar todos en un servidor: edge functions, webhooks, tareas programadas, un backend propio. Dónde viven esos valores si nunca has abierto esa página.
- Despliega y luego recorre las partes de tu app que leen y escriben datos. Una clave secreta incorrecta falla de forma ruidosa e inmediata, que es el caso bueno.
- Solo entonces borra la clave comprometida.
El paso 4 es permanente. Supabase borra una clave secreta del todo: sin deshacer, sin reactivar, sin copia aparcada que puedas recuperar. Por eso va al final.
Borrar una clave secreta no cierra la sesión de tus usuarios. Una clave de API dice qué aplicación está llamando a tu base de datos, y el token de sesión de un visitante dice qué usuario es. Lo segundo se comprueba contra la clave de firma de tu proyecto, y ese es un valor aparte que no has tocado.
Por qué una clave service_role antigua de Supabase no se puede rotar
Porque no hay botón para ello. La nota de resolución de problemas de Supabase
dice que la rotación directa de los secretos antiguos anon, service_role y
JWT ya no está soportada, y te remite a las claves nuevas.
Así que invalidar una clave service_role antigua filtrada es una migración y
no una rotación:
- Crea las claves nuevas. Esto añade
sb_publishable_ysb_secret_junto al par antiguo. Los dos sistemas funcionan a la vez y no se rompe nada. - Mueve tu frontend a la clave publicable. En Lovable o Bolt esto suele ser un valor en los ajustes de tu proyecto y no una línea de código.
- Mueve todo uso del lado del servidor a la clave secreta.
- Desactiva las claves antiguas en los ajustes de tu proyecto.
El paso 4 es un solo interruptor y afecta a las dos claves antiguas. anon y
service_role son JWT firmados con el mismo secreto, así que el interruptor que
revoca una revoca la otra, y un frontend que todavía lleve la clave anon vieja
deja de funcionar en el momento en que lo accionas. Por eso el paso 2 va antes.
Desactivar es reversible, que es la única buena noticia de esta sección: si algo que olvidaste resulta que todavía usa una clave antigua, puedes volver a encenderlas mientras lo arreglas. Qué implica la migración lo cuenta con más detalle.
De dónde se filtró la clave, y cómo cerrarlo
Tres causas explican casi todo, y las tres están dentro de tu propio proyecto.
Un prefijo VITE_ o NEXT_PUBLIC_. No son un ajuste de seguridad que
alguien olvidó activar. Son una instrucción para la compilación: mete este valor
en el bundle. Una variable llamada VITE_SUPABASE_SERVICE_ROLE_KEY se compiló
en tu JavaScript a propósito, por una herramienta que hizo exactamente lo que se
le dijo.
Un valor pegado directamente en un componente. Sin prefijo de por medio y
sin archivo .env, solo la clave en una línea de código porque era la forma más
rápida de que una consulta devolviera algo.
Trabajo que pertenece a un servidor. Borrar una cuenta, escribir en una tabla en la que tus usuarios no pueden escribir, leer filas de todo el mundo. Se echó mano de la clave porque el navegador no podía hacer el trabajo, y la solución es mover el trabajo: una edge function, una ruta serverless, cualquier cosa que tus visitantes no descarguen.
¿Cómo sé si alguien la usó?
Normalmente no puedes estar seguro. Lo que sí puedes hacer es acotar la ventana y mirar dentro.
La ventana se abre con el despliegue que publicó la clave por primera vez y se cierra cuando la desactivaste. Tu proyecto de Supabase guarda registros de la API y de la base de datos, y ese es el rango al que hay que filtrarlos. Lo que buscas son lecturas y escrituras que no puedas justificar: peticiones a tablas que tu app nunca toca, tráfico a horas en las que nadie la estaba usando, borrados que no hizo nadie.
Después revisa los datos en sí. Recuentos de filas frente a lo que esperas, tus propios registros de cuenta, cualquier cosa con una marca de tiempo que se movió mientras nadie trabajaba. Un mensaje de soporte sobre datos que cambiaron solos es como se descubren de verdad la mayoría de estos casos, y llega semanas después.
Una clave service_role no alcanza a tu proveedor de pagos ni a tu envío de
correo. Aun así conviene saber si era la única clave de tu bundle, porque las
que gastan dinero se filtran por el mismo camino:
qué publicaron 30.998 apps.
Revisa tu app en producción antes de darlo por hecho
Carga tu sitio en una ventana privada, abre el JavaScript que descargó tu navegador y busca en él el valor viejo. Busca después el nuevo, que tampoco debería estar ahí.
Dos cosas hacen que valga la pena comprobarlo a mano. Una compilación puede servir un bundle cacheado un rato después de que despliegues, así que el archivo que recibe un visitante no siempre es el que acabas de construir. Y una clave puede estar en más de un sitio: un segundo punto de entrada, un service worker, una compilación vieja que todavía se sirve desde una ruta que nadie enlaza.
Nuestro escaneo gratuito hace esta parte desde fuera, sobre la URL que usan tus visitantes de verdad, y decodifica una clave de Supabase lo suficiente para leer el rol, así que una clave publicable vuelve con una marca de correcto. Tarda unos veinte segundos y no necesita cuenta: escanea tu app.
Qué hacer ahora
Qué hacer
- Confirma primero la clave.
sb_secret_al principio, o"role": "service_role"dentro de una más antigua. Una clave publicable en tu frontend no es un hallazgo. - Si está en el bundle que descargan tus visitantes, rótala antes de cambiar código. Editar tu app no alcanza las copias que ya circulan.
- Si solo se filtró a un repositorio, un registro o un chat, cierra primero la fuente, para que tu siguiente despliegue no vuelva a sacar la clave nueva.
- En un proyecto nuevo: crea una segunda clave secreta, mueve el código de tu servidor a ella, despliega y luego borra la vieja. Borrar es permanente.
- En un proyecto antiguo no hay botón de rotar. Crea las claves nuevas, mueve tu frontend y tu servidor a ellas, y después desactiva el par antiguo con el único interruptor que afecta a los dos.
- Busca luego en tu bundle en producción. Una compilación cacheada puede servir el valor viejo un rato después de que despliegues.
Una clave service_role lee y escribe cada fila de tu base de datos. La peor
versión de esta historia acaba con una tabla vacía, y que esas filas vuelvan
depende de
qué estabas respaldando antes
de que ocurriera todo esto.
Preguntas frecuentes
¿Rotar la clave rompe mi app en producción?
No si lo haces en orden. En un proyecto con las claves nuevas añades una segunda clave secreta, mueves el código de tu servidor a ella, despliegas y solo entonces borras la vieja, así que nunca hay un momento sin una clave que funcione. En un proyecto antiguo el riesgo es otro: desactivar el par antiguo también desactiva la clave anon que usa tu frontend, así que tu app se cae salvo que ya esté con la clave publicable cuando acciones ese interruptor.
¿Puedo rotar las claves antiguas anon y service_role?
No. La documentación de resolución de problemas de Supabase dice que la rotación directa de los secretos antiguos anon, service_role y JWT ya no está soportada. Invalidar una clave antigua filtrada significa crear las nuevas claves sb_publishable_ y sb_secret_, mover tu app y el código de tu servidor a ellas, y después desactivar el par antiguo en los ajustes de tu proyecto.
¿Borro la clave vieja o la desactivo?
No puedes elegir, porque los dos sistemas de claves se comportan distinto. Una clave sb_secret_ nueva se borra, de forma permanente, sin manera de recuperarla. El par antiguo se desactiva, y desactivar sí es reversible: si algo que olvidaste resulta que todavía usa una clave vieja, puedes volver a encenderlas mientras lo arreglas.
¿Cómo sé si alguien la usó?
Normalmente no puedes estar seguro. Redúcelo a una ventana de tiempo: la clave fue utilizable desde el despliegue que la publicó por primera vez hasta el momento en que la desactivaste. Filtra los registros de la API y de la base de datos de tu proyecto de Supabase a ese rango y busca lecturas y escrituras que no puedas justificar; después revisa tus datos en busca de recuentos de filas y marcas de tiempo que no cuadren con nada que hicieras.
¿Tengo que rotar también mi clave anon?
En un proyecto antiguo no puedes elegir: desactivar el par antiguo se lleva las dos claves a la vez, así que tu frontend necesita la clave publicable nueva antes de que acciones el interruptor. En un proyecto nuevo la clave publicable está pensada para ser pública y no hay motivo para tocarla. Lo que sí conviene revisar en ambos casos es Row Level Security, porque es lo único que se interpone entre una clave publicable y toda tu base de datos.