FixVibe

// docs / baas security / supabase service role exposure

Clave de rol de servicio de Supabase expuesta en JavaScript: qué significa y cómo detectarla

La clave de rol de servicio de Supabase es la clave maestra de tu base de datos. Quien la posea se salta la seguridad a nivel de fila, puede leer cada columna de cada tabla y puede escribir o borrar lo que quiera. Está diseñada para vivir exclusivamente en código del lado del servidor — nunca en el navegador. Cuando una herramienta de codificación con IA la envía al bundle de JavaScript, tu base de datos queda, en efecto, pública. Este artículo explica la forma del JWT que identifica una clave filtrada, los tres patrones de herramientas de IA que producen la fuga, qué hacer en la primera hora tras la detección y cómo escanear automáticamente para detectarla antes que los usuarios.

Qué es la clave de rol de servicio

Supabase emite dos claves distintas para cada proyecto: la clave anon (también llamada clave publicable en proyectos más nuevos) y la clave service_role. Ambas son JSON Web Tokens firmados con el secreto JWT de tu proyecto. La diferencia está en el claim role incorporado en el payload del JWT — anon para la clave pública, service_role para la clave maestra. PostgREST, Supabase Storage y Supabase Auth pasan a un modo de saltarse-todo cuando ven el claim service_role.

Decodifica cualquier clave de Supabase en jwt.io y observa el payload. La forma de un JWT de rol de servicio es inconfundible:

Payload decodificado de un JWT de rol de servicio (mostrado como bloque resaltado abajo).

json
{
  "iss": "supabase",
  "ref": "[project-ref]",
  "role": "service_role",
  "iat": 1700000000,
  "exp": 2000000000
}

Los proyectos Supabase más nuevos emiten claves de tipo secreto con el prefijo sb_secret_ en lugar de un JWT. El comportamiento es idéntico — cualquier cosa que contenga sb_secret_ en un bundle público es igualmente catastrófica.

Cómo las herramientas de codificación con IA filtran la clave de rol de servicio

Hemos visto los mismos tres patrones en miles de aplicaciones generadas por IA. Cada uno empieza con un desarrollador pidiéndole ayuda a una herramienta de IA y termina con la clave de servicio incrustada en un bundle.

Patrón 1: archivo .env único con prefijo NEXT_PUBLIC_

El desarrollador le pide a la herramienta de IA que "configure Supabase" y acepta un único .env con ambas claves. La herramienta de IA — entrenada con un corpus donde la mayoría de variables de entorno se exponen vía NEXT_PUBLIC_* — prefija ambas con NEXT_PUBLIC_. Next.js incrusta cualquier cosa que coincida con ese prefijo en el bundle cliente en tiempo de compilación. Publicas en Vercel y la clave de servicio queda en main.[hash].js.

Patrón 2: clave incorrecta en la llamada a createClient

El desarrollador pega ambas claves en un archivo config.ts generado por la IA, y la IA rellena por error la llamada createClient() del lado del navegador con process.env.SUPABASE_SERVICE_ROLE_KEY. La compilación trae la variable y el JWT acaba en el bundle.

Patrón 3: clave de rol de servicio incrustada en scripts de seed

El desarrollador pide a la herramienta de IA que escriba un script para poblar la base de datos. La IA incrusta la clave de rol de servicio directamente en el archivo (en lugar de leerla del entorno), confirma el archivo en el repositorio, y el repositorio público de GitHub o la ruta /scripts/seed.js de la aplicación desplegada ahora está sirviendo la clave.

Cómo detecta la fuga el escaneo de bundle de FixVibe

La verificación de secretos en bundle de FixVibe descarga cada archivo JavaScript referenciado por la aplicación desplegada — chunks de entrada, chunks de carga diferida, web workers, service workers — y los pasa por un detector que decodifica cualquier cosa que coincida con la forma de JWT (eyJ[base64-header].eyJ[base64-payload].[signature]). Si el payload decodificado contiene "role": "service_role", el escaneo lo reporta como hallazgo crítico con la ruta del archivo y la línea exacta donde aparece la clave. La misma verificación también detecta el patrón más nuevo sb_secret_* por prefijo.

El escaneo nunca se autentica con la clave descubierta. Identifica la forma y reporta la fuga — usar la clave para demostrar la explotabilidad sería un acceso no autorizado a tu base de datos. La prueba está en el propio payload del JWT.

Detectada — qué hacer en la primera hora

Una clave de rol de servicio filtrada es una emergencia en tiempo de ejecución. Asume que la clave ha sido extraída — los atacantes monitorean bundles públicos en tiempo real. Trata la base de datos como comprometida hasta que hayas rotado la clave y auditado la actividad reciente.

  1. Rota la clave inmediatamente. En el panel de Supabase, ve a Configuración del Proyecto → API → Service role key → Reset. La clave antigua queda invalidada en segundos. Cualquier código del lado del servidor que use la clave debe actualizarse y redesplegarse antes de que la rotación se aplique.
  2. Audita la actividad reciente de la base de datos. Abre Database → Logs en el panel. Filtra por los últimos 7 días. Busca consultas SELECT * inusuales contra tablas con PII, instrucciones UPDATE o DELETE grandes, y solicitudes desde IPs fuera de tu infraestructura conocida. Supabase registra la cabecera x-real-ip en cada solicitud.
  3. Comprueba los objetos de storage. Visita Storage → Logs y revisa las descargas de archivos recientes. Una clave de rol de servicio filtrada también otorga acceso de saltarse-todo a buckets privados.
  4. Elimina la clave del control de versiones. Incluso tras la rotación, dejar el JWT en tu historial git significa que se puede descubrir en el repositorio público. Usa git filter-repo o BFG Repo-Cleaner para limpiarlo del historial, luego haz force-push (avisa primero a los colaboradores).
  5. Vuelve a escanear tras la corrección. Ejecuta un nuevo escaneo de FixVibe contra la aplicación redesplegada. El hallazgo de secretos en bundle debería desaparecer. Confirma que no quede ningún JWT service_role ni ninguna cadena sb_secret_* en ningún chunk.

Prevenir la fuga desde el inicio

La corrección estructural es disciplina de nomenclatura más medidas de seguridad a nivel de herramientas:

  • Nunca prefijes la clave de servicio con NEXT_PUBLIC_*, VITE_* ni ningún otro prefijo que incruste en el bundle. La convención de nomenclatura es el límite — todos los frameworks la respetan.
  • Mantén la clave de servicio fuera del .env de la máquina del desarrollador por completo. Léela de un gestor de secretos (Doppler, Infisical, variables de entorno cifradas de Vercel) en el despliegue, nunca la confirmes localmente.
  • <strong>Mark every Supabase client construction with explicit context.</strong> Files named <code>supabase/browser.ts</code> use the anon key; files named <code>supabase/server.ts</code> use the service-role key with <code>import 'server-only'</code> at the top. The <code>server-only</code> import causes a build error if a client component tries to consume the module.
  • <strong>Add a pre-commit hook that greps for JWT-shaped strings.</strong> <code>git diff --staged | grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'</code> catches both anon and service tokens before they leave your machine.
  • Añade una verificación de CI que escanee la salida de la compilación. Tras next build, busca la cadena service_role en la salida de .next/static/chunks/. Falla la compilación si encuentra algo.
bash
# Pre-commit hook: refuse any staged JWT-shaped string.
git diff --staged \
  | grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' \
  && echo "JWT detected in staged changes — refusing commit" \
  && exit 1

# CI gate: fail the build if "service_role" shipped to the static bundle.
grep -RE 'service_role|sb_secret_' .next/static/chunks/ \
  && echo "Service-role credential leaked into bundle" \
  && exit 1

Preguntas frecuentes

¿Con qué rapidez encuentran realmente los atacantes claves de rol de servicio de Supabase filtradas?

Los escáneres de bundles públicos rastrean nuevos despliegues en minutos. Los investigadores han documentado exploits funcionales contra nuevos proyectos Supabase en menos de una hora desde el primer despliegue. Trata cualquier exposición de rol de servicio como una ventana de 60 minutos, no de 60 días.

¿Basta con rotar la clave o tengo que asumir exfiltración de datos?

La rotación invalida la clave filtrada pero no deshace los datos ya extraídos. Si tus tablas contienen PII, datos de pago o cualquier dato regulado, puede que tengas obligación de notificación bajo GDPR (72 horas), CCPA o HIPAA. Audita los logs y consulta a asesoría legal si la auditoría muestra accesos sospechosos.

¿Puede RLS protegerme si la clave de rol de servicio se filtra?

No. La seguridad a nivel de fila se salta por completo con el claim service_role. Es por diseño — la clave existe precisamente para que el código backend pueda saltarse RLS para operaciones de administración. La mitigación es asegurarse de que la clave nunca llegue a un contexto donde un atacante pueda leerla.

¿Esto aplica al nuevo modelo de claves publicable / secret de Supabase (<code>sb_publishable_</code> / <code>sb_secret_</code>)?

Sí — clase de riesgo idéntica. La clave sb_secret_* es el nuevo formato de clave secreta que reemplaza al JWT de rol de servicio en los proyectos más nuevos. Cualquier cosa que contenga sb_secret_* en un bundle es tan catastrófica como un JWT de rol de servicio filtrado. El detector de secretos en bundle de FixVibe detecta ambas formas.

¿Qué hay de la clave anon / publicable — es segura en el bundle?

Sí, por diseño. La clave anon está pensada para vivir en el navegador y es lo que usa cada cliente web de Supabase. Su seguridad depende enteramente de que RLS esté correctamente configurado en cada tabla pública. Consulta el artículo Escáner de RLS de Supabase para saber qué comprobar.

Próximos pasos

Ejecuta un escaneo de FixVibe contra tu URL de producción — la verificación de secretos en bundle es gratuita, sin registro, y reporta exposición de service_role en menos de un minuto. Combina esto con el artículo Escáner de RLS de Supabase para verificar que la capa RLS está haciendo su trabajo, y la Lista de comprobación de seguridad de buckets de Supabase Storage para bloquear el acceso a archivos. Para el contexto sobre por qué las herramientas de IA generan esta clase de fuga con tanta fiabilidad, lee Por qué las herramientas de codificación con IA dejan huecos de seguridad.

// escanea tu superficie de baas

Encuentra la tabla abierta antes que otra persona lo haga.

Introduce una URL de producción. FixVibe enumera los proveedores de BaaS con los que habla tu aplicación, identifica sus endpoints públicos y reporta lo que un cliente no autenticado puede leer o escribir. Gratis, sin instalación, sin tarjeta.

  • Plan gratuito — 3 escaneos al mes, sin tarjeta de registro.
  • Identificación pasiva de BaaS — no se requiere verificación de dominio.
  • Supabase, Firebase, Clerk, Auth0, Appwrite y más.
  • Prompts de corrección con IA en cada hallazgo — pégalos de vuelta en Cursor / Claude Code.
Clave de rol de servicio de Supabase expuesta en JavaScript: qué significa y cómo detectarla — Docs · FixVibe