El 19 de abril de 2026, Vercel publicó un aviso oficial de incidente de seguridad causado por un ataque de cadena de suministro a través de la app OAuth de Context.ai. Las variables de entorno no marcadas como Sensitive pudieron leerse en texto plano. Si ves el aviso naranja Need To Rotate en tu panel de Vercel, rota en cuestión de horas, no de días. Este artículo documenta la secuencia exacta que ejecuté en 32blog.com dos días después del aviso, incluyendo dos errores que casi cometo en el camino.
Si llevas un tiempo en Vercel Pro, probablemente nunca has pensado seriamente en la postura de seguridad de tus variables de entorno. Yo tampoco, hasta que inicié sesión y vi avisos naranjas junto a RESEND_API_KEY, UPSTASH_REDIS_REST_URL y UPSTASH_REDIS_REST_TOKEN. El reflejo es hacer clic en "rotar" en cada fila. Pero eso no es lo que Vercel recomienda realmente y hacerlo a ciegas te deja una variable huérfana duplicada que voy a mostrarte cómo evitar.
Qué pasó realmente
El aviso oficial de Vercel del 2026-04-19 confirma acceso no autorizado a sistemas internos mediante la cuenta de Google Workspace de un empleado. La cadena de ataque:
- La app OAuth de Context.ai fue comprometida (ataque de cadena de suministro contra una herramienta de terceros conectada al Workspace del empleado)
- Los tokens de sesión del empleado se exfiltraron a través del permiso OAuth
- El atacante accedió a sistemas internos de Vercel durante una ventana que, según reportes, se remonta a ~junio de 2024
- Alcance: "Un subconjunto limitado de clientes cuyas variables de entorno no sensibles almacenadas en Vercel (las que se descifran a texto plano) fueron comprometidas."
ShinyHunters se atribuyó la responsabilidad, según reporta BleepingComputer. El análisis de GitGuardian señala que las integraciones del marketplace (Upstash, Resend, Supabase, etc.) aprovisionan variables en texto plano por defecto — por eso son la superficie de riesgo principal.
La recomendación explícita de Vercel: "Revisa y rota las variables de entorno que no estén marcadas como 'sensitive'."
Qué variables marcó Vercel
En el panel de Vercel → Settings → Environment Variables, las filas afectadas muestran un aviso naranja Need To Rotate. En 32blog.com, tres variables estaban marcadas:
RESEND_API_KEY— usada por el formulario de contacto mediante la API de email transaccional de ResendUPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN
Otras tres variables no tenían el aviso:
GA_SERVICE_KEY_BASE64— clave de cuenta de servicio de Google para GA4 (sin aviso, pero es un secreto)GA_PROPERTY_ID— ID numérico de propiedad, público por diseñoVERCEL_DEEP_CLONE— flag booleano que controla la profundidad del clon de git
La ausencia de aviso no significa que la variable sea segura. Significa que los registros internos de Vercel no asocian esa fila específica con el incidente, pero si tienes cualquier secreto almacenado en texto plano, la medida defensiva es migrarlo a almacenamiento Sensitive de todos modos.
Decisión: rotar o eliminar
Antes de tocar nada, pregúntate: ¿sigo usando esto?
| Escenario | Acción |
|---|---|
| La variable está en uso activo | Rotar. Generar clave nueva en el proveedor, actualizar en Vercel, redesplegar. |
| La variable está sin uso (referencia eliminada del código, servicio ya no necesario) | Eliminar. Borrar de Vercel, desconectar la integración del Marketplace. |
| Variable auto-aprovisionada por una integración del Marketplace | Eliminar vía integración. Desinstalar la integración borra sus variables de forma atómica. |
Rotar una variable sin uso es tiempo perdido y deja una clave muerta en tu gestor de secretos. Eliminar una variable en uso rompe producción. Haz grep antes de cada decisión:
grep -rn "UPSTASH_REDIS" --include="*.ts" --include="*.tsx" --include="*.mjs" .
En 32blog.com, el grep reveló que Upstash solo se referenciaba en dos archivos MDX de tutoriales — la funcionalidad real del botón de "me gusta" se había eliminado hace meses. Eso convirtió lo que parecían tres rotaciones en dos rotaciones reales más una limpieza.
La secuencia de emergencia que ejecuté
Hice esto en un par de horas después de iniciar sesión y ver los avisos. Tiempo total: unos 30 minutos incluyendo pruebas.
Paso 1: Rotar la API key de Resend (primero, por la reputación del email)
Una clave de Resend filtrada permite a un atacante enviar correo desde @32blog.com con firmas SPF/DKIM válidas. Ese es el peor escenario en este incidente porque el daño a la reputación del dominio es lento de recuperar. Un phishing enviado con tu firma DKIM te pone en las listas de penalización de remitente masivo de Gmail durante semanas.
- Entré a Resend → API Keys → revoqué la clave antigua
- Creé una clave nueva, la copié una sola vez (Resend solo la muestra al crearla)
- Vercel → Settings → Environment Variables → eliminé el
RESEND_API_KEYantiguo - Hice clic en Add New, mismo nombre
RESEND_API_KEY, pegué el valor nuevo - Marqué la casilla "Sensitive" — este es el verdadero motivo de rotar
- Guardé y hice clic en Redeploy sobre el último despliegue
Paso 2: Eliminar la integración de Upstash (no solo las variables)
Upstash era el caso de "servicio sin uso". En lugar de rotar, limpié:
- Entré a Upstash → eliminé la base de datos Redis y toda la cuenta (sin cargos en curso porque nunca añadí tarjeta)
- Vercel → Integrations → Upstash → Remove Integration (esto debería auto-eliminar las variables que la integración aprovisionó)
- Verifiqué que
UPSTASH_REDIS_REST_URLyUPSTASH_REDIS_REST_TOKENdesaparecieron de Environment Variables
Esto es más limpio que borrar las dos variables manualmente porque la entrada de integración ya no existe — una reinstalación futura no resucitará los nombres antiguos.
Paso 3: El duplicado que casi dejo atrás
Después de re-agregar Resend, mi lista de variables mostraba dos filas:
RESEND_API_KEY(Sensitive, Production and Preview, "Updated just now")RESEND_API_KEY_(con guion bajo al final, All Environments, "Updated 50s ago")
La variante con guion bajo era un sobrante de cuando brevemente guardé la clave antigua bajo un nombre distinto "por si necesito hacer rollback". Este es un mal patrón: la fila huérfana aún contiene el valor comprometido y nada la referencia. La eliminé.
Paso 4: Marcar proactivamente como Sensitive los demás secretos
GA_SERVICE_KEY_BASE64 no tenía aviso de rotación pero es una clave privada de cuenta de servicio de Google. Mismo patrón que Resend:
- Google Cloud Console → IAM → Service Accounts → añadí una clave JSON nueva
base64 -w 0 new-key.jsonpara codificar- Vercel → eliminé el
GA_SERVICE_KEY_BASE64antiguo → re-agregué con Sensitive marcado y el nuevo valor base64 - Redeploy → verifiqué que la sección de artículos populares sigue cargando en el sitio en vivo
- Google Cloud Console → eliminé la clave antigua de la cuenta de servicio
GA_PROPERTY_ID se quedó como estaba. Un ID numérico de propiedad ya es visible en las etiquetas GA4 del lado cliente; no es un secreto.
Qué te da realmente el flag Sensitive
Según el aviso del incidente de Vercel, las variables marcadas como Sensitive se almacenan de una forma que "impide que sean leídas". Operativamente:
- Almacenamiento cifrado, descifrado solo en build/runtime
- No se pueden ver en la UI tras guardar (solo se muestra
●●●●●●) - No se pueden editar in situ, solo reemplazar por completo
- No pueden existir en el entorno Development — si marcas "Sensitive", la casilla de Development se desactiva
Esa última restricción es intencional: las variables de Development están pensadas para descargarse a tu laptop con vercel dev, lo cual es lo opuesto a lo que significa Sensitive. Production + Preview es la única combinación que convive con Sensitive.
La contrapartida
Una vez Sensitive, si pierdes el valor (olvidaste guardarlo en un gestor de contraseñas), tu único recurso es emitir una clave nueva desde el proveedor. Vercel realmente no puede mostrártelo. Esta es la seguridad funcionando como debe, y es el comportamiento por defecto correcto para claves API — los proveedores siempre pueden regenerarlas, así que perder el valor es recuperable.
La trampa de VERCEL_DEEP_CLONE
Esta no es una cuestión de seguridad, pero me atrapó al revisar la lista. VERCEL_DEEP_CLONE=true no es una variable documentada oficialmente por Vercel, pero la infraestructura de build de Vercel la lee. Al definirla, el clon de git pasa de shallow (profundidad 10 por defecto) a un clon completo.
Por qué importa: mi script de build scripts/generate-updated-dates.mjs ejecuta git log -1 --format=%aI -- <archivo> por cada artículo MDX para poblar la fecha de "última actualización". Sin un clon profundo, git log devuelve vacío para la mayoría de archivos y el JSON de fechas queda silenciosamente vacío.
Si tienes herramientas de build que dependen del historial de git — timestamps de Nextra, Nx affected-graph para monorepos, generadores de changelog — probablemente necesites VERCEL_DEEP_CLONE=true también. Hay una discusión de GitHub con referencias de la comunidad. No es un secreto, déjalo en texto plano.
¿Deberías mudarte de Vercel?
La reacción obvia ante una brecha de plataforma es "hora de auto-alojar". Considera el tradeoff con honestidad:
- A favor: auto-alojar en un VPS con Coolify elimina esta clase de riesgo de cadena de suministro — tus secretos están en tu propio servidor, no en los sistemas internos de un proveedor
- En contra: ahora eres responsable de parcheos del SO, renovación TLS, mitigación DDoS y los equivalentes a Fluid Compute tú mismo. La superficie de ataque se movió, no desapareció.
Mi postura: 32blog.com se queda en Vercel. El beneficio marginal de seguridad de auto-alojar es menor que el coste operativo marginal para un sitio de una sola persona. Lo que estoy haciendo en cambio es lo que trata este artículo — tratar Sensitive como el default para cada nuevo secreto que añada, y auditar ocasionalmente la lista completa de variables buscando regresiones a texto plano.
Si gestionas un equipo más grande o almacenas secretos genuinamente de alto valor (credenciales de pago, claves de acceso a PII), el cálculo cambia. Coolify en un VPS de 7 USD/mes cubre la mayoría de necesidades de despliegue de Next.js y deja el almacenamiento de secretos en tus manos. El nivel de dolor es real pero no prohibitivo.
Para lectores preocupados por los costos ya elevados de Vercel — este incidente no cambia esa matemática, pero la guía de Spend Management sigue siendo la primera parada antes de cualquier decisión de auto-alojamiento.
FAQ
¿Cómo sé si mis variables de Vercel fueron comprometidas?
Revisa Settings → Environment Variables. Cualquier fila con el aviso naranja Need To Rotate fue marcada por Vercel como parte del alcance del incidente. Sin aviso no significa que Vercel no asocie esa variable específica con el incidente, pero si está almacenada sin el flag Sensitive, trátala como riesgo y migra a Sensitive en tu próxima ventana de mantenimiento.
¿Puedo seguir usando la clave antigua si no veo abuso obvio?
Técnicamente sí, pero es peligroso. El abuso de claves por esta clase de brecha suele aparecer semanas o meses después — mediante caídas repentinas de reputación en Resend, quejas de spam sobre tu dominio, o picos inexplicados de facturación por uso. El costo fijo de rotar ahora es pequeño; el costo variable de esperar es grande e impredecible.
¿Por qué Sensitive no impidió esto si supuestamente es el default?
No es el default. Cada variable que entré a través de la UI de Vercel tenía "no sensible" por defecto, y cada integración de Marketplace (Upstash, Resend, Supabase, etc.) aprovisiona variables en texto plano a menos que la integración soporte Sensitive explícitamente. Vercel ha declarado desde entonces que están revisando los defaults. Hasta que eso cambie, Sensitive es opt-in y tienes que acordarte de marcar la casilla.
¿Rotar en Vercel también invalida la clave antigua en el proveedor?
No. Vercel solo almacena el valor — el proveedor (Resend, Upstash, Google Cloud) tiene la clave real. Debes revocar explícitamente la clave antigua en el panel del proveedor. Olvidar este paso es el error de rotación más común: actualizaste Vercel pero la clave antigua comprometida sigue siendo válida en el proveedor.
Roté pero la clave nueva no funciona. ¿Qué me falta?
Casi siempre: olvidaste redesplegar. Los cambios de variables de entorno en Vercel no se propagan a despliegues existentes — solo aplican a builds nuevos. Ve a Deployments, encuentra el último, haz clic en ⋯ → Redeploy. Puedes dejar marcado "Use existing Build Cache" porque la salida del build no cambia.
¿VERCEL_DEEP_CLONE es parte del incidente?
No. Es un flag booleano (true/false), no un secreto, y controla la profundidad del clon de git durante los builds. Si tus herramientas de build leen historial de git, déjalo configurado. Si nada en tu build lee git, puedes quitarlo sin impacto.
¿Debería activar 2FA en mi cuenta de Vercel ahora?
Sí. Este incidente vino por un Workspace de empleado comprometido, no por una cuenta de cliente, pero el principio general aplica: Vercel → Settings → Security → activa 2FA, luego revisa sesiones activas y revoca cualquiera que no reconozcas. Toma dos minutos y cierra uno de los vectores de ataque más comunes contra cualquier cuenta SaaS.
Conclusión
Tres cosas que vale la pena hacer ahora mismo, independientemente de si viste el aviso hoy:
- Audita tu lista de variables de Vercel buscando secretos en texto plano. Cualquier API key, credencial de base de datos o clave de cuenta de servicio que no sea Sensitive debería migrarse. Las integraciones Resend/Upstash/Supabase son las sospechosas habituales.
- Elimina lo que no uses. Las integraciones del Marketplace de experimentos descontinuados son una larga cola de secretos dormidos — quitar la integración los limpia de forma atómica.
- Adopta Sensitive como default para cada variable nueva que añadas de ahora en adelante. Es ligeramente menos cómodo (no puedes volver a ver el valor después), pero es lo único que demostrablemente defendió a clientes en este incidente.
Las brechas de cadena de suministro contra proveedores de plataforma son raras, pero no son eventos de frecuencia cero. La postura defensiva que puedes controlar — flags Sensitive, principio de mínimo privilegio, disciplina de rotación — es barata de configurar una vez y sigue funcionando silenciosamente después.