Evitar que una clave de servicio llegue al navegador

Next.js incrusta en el paquete de cliente, en tiempo de compilación, cualquier variable de entorno con el prefijo NEXT_PUBLIC_. Vite hace lo mismo con VITE_, y Create React App con REACT_APP_. Ese es el comportamiento documentado y buscado: esos prefijos existen para marcar un valor como público.

El fallo es poner un secreto detrás de uno de ellos. La credencial es real y tiene el formato correcto; simplemente está en la compilación equivocada. Cualquiera abre las herramientas del navegador y la lee.

Qué claves pueden ir detrás del prefijo y cuáles no

Seguras — pensadas para ser públicasNo seguras — dan acceso al servidor
NEXT_PUBLIC_SUPABASE_ANON_KEYNEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYNEXT_PUBLIC_STRIPE_SECRET_KEY
NEXT_PUBLIC_FIREBASE_API_KEY
pública por diseño; se protege con reglas
VITE_OPENAI_API_KEY
factura contra tu cuenta, y no hay regla que la proteja

Cómo comprobar tu proyecto en un minuto

# cualquier variable con prefijo de navegador cuyo nombre sugiera un secreto
grep -rEn "(NEXT_PUBLIC_|VITE_|REACT_APP_)[A-Z_]*(SECRET|SERVICE_ROLE|PRIVATE|API_KEY|TOKEN)" \
  .env* src app lib 2>/dev/null

Y luego lee cada coincidencia, porque dos de ellas no van a ser fallos:

Los dos casos que nos costaron una disculpa pública

Estábamos preparando avisos de seguridad para repositorios públicos, y comprobamos cada uno a mano antes de enviarlo. Los dos estaban mal, y los dos están ahora fijados por una prueba de regresión escrita con la línea exacta:

1. Una variable forzada a vacío es el arreglo, no el fallo

...(command === "build"
  ? { "import.meta.env.VITE_OPENAI_API_KEY": '""' }
  : {}),

Esa línea elimina la clave de todas las compilaciones de producción. Un comentario de seis líneas encima explica que Vite incrusta las VITE_* en el paquete. Le habríamos dicho a un equipo que entiende esto mejor que nuestro escáner que tenía el fallo que ya había arreglado.

2. Leer una variable en Node no es publicarla

module.exports = {
  openAIApiKey: process.env.VITE_OPENAI_API_KEY,
}

La configuración de una herramienta de traducción. Corre en Node en tiempo de compilación. El prefijo VITE_ despista: el código de cliente nunca referencia la variable, así que nada llega al navegador. Cualquier escáner que busque el prefijo va a marcar esto, y se va a equivocar.

Cómo se arregla

  1. Rota la clave primero. Da por hecho que está comprometida: el paquete es público y puede estar cacheado o archivado. Limpiar el historial lleva horas; la clave está viva durante todas ellas.
  2. Muévela a una variable sin el prefijo, leída solo desde código de servidor —un route handler, una server action, una ruta de API.
  3. Si el navegador necesita de verdad llegar al proveedor, haz de intermediario con tu propio backend, o usa la clave publicable, que existe exactamente para esto.

Lo que medimos, y publicamos

MediciónResultado
Primera ejecución automática sobre 249 pull requests públicos 150 hallazgos, 79 CRÍTICOS — casi ninguno real
La misma población tras cuatro rondas de correcciones 39 hallazgos, 0–1 CRÍTICO
Dependencias resueltas en vivo contra PyPI y npm la cifra en vivo
Paquetes alucinados por IA confirmados 0

Esa última fila juega en nuestra contra: construimos esto alrededor de los nombres de paquete inventados, lo medimos, y el ataque resulta ser más raro de lo que sugiere el sector. Un paquete que no existe hace que pip install falle y que el CI se ponga rojo, gratis. Lo publicamos porque un proveedor al que puedes comprobar vale más que un proveedor al que tienes que creer.

Instalar en GitHub →

Última revisión: 21 de septiembre de 2026. Todas las cifras de esta página son medición propia y reproducibles contra nuestro endpoint público.