← inicio

Vercel: breach por OAuth y env vars sin cifrar

El 19 de abril de 2026, Vercel publicó un aviso de seguridad que confirmó una brecha en sus sistemas internos. No fue un zero-day sofisticado ni una vulnerabilidad en Next.js. Fue algo mucho más mundano — y por eso mismo, más preocupante: el OAuth de una herramienta externa comprometió la cuenta de Google Workspace de un empleado, y desde ahí el atacante enumeró variables de entorno marcadas como “no sensibles” para escalar acceso.

Repaso técnico de lo que pasó, cómo funcionó la cadena de ataque y qué puedes hacer para que no te pase lo mismo.

Qué pasó: la cadena de ataque

Vercel confirmó estos hechos en su aviso y posteriormente Guillermo Rauch (CEO) añadió detalles en X:

  1. Un atacante comprometió la app OAuth de Google Workspace de Context.ai, una herramienta de IA de terceros. El OAuth app ID publicado como IOC es 110671459871-30f1spbu0hptbs60cb4vsmv79i7bbvqj.apps.googleusercontent.com.

  2. Mediante ese token OAuth comprometido, el atacante obtuvo acceso a la cuenta de Google Workspace de un empleado de Vercel.

  3. Desde esa cuenta, escaló a los entornos internos de Vercel, donde accedió a variables de entorno de clientes que no estaban marcadas como sensibles.

  4. Las variables marcadas como sensibles en Vercel se cifran en reposo y son ilegibles incluso para el personal interno. Las “no sensibles” se almacenan en claro — y ahí estaba la brecha: el atacante enumeró esas variables para construir caminos de acceso adicionales.

  5. Next.js, Turbopack y los proyectos open-source no se vieron afectados.

El vector es clarísimo: un token OAuth de terceros actúa como llave maestra, y las variables de entorno sin cifrar son las puertas que esa llave puede abrir.

El problema de las “env vars no sensibles”

Vercel ofrece dos modos para variables de entorno:

  • Normal: legible por cualquier miembro del proyecto con acceso. Se almacena sin cifrar en reposo. Pensada para datos como NEXT_PUBLIC_APP_URL o flags de feature toggles.
  • Sensitive: cifrada en reposo, irrecuperable una vez creada. Solo inyectable en Producción y Preview, no en Development.

El incidente reveló que muchos equipos marcan como “no sensibles” variables que sí lo son — API keys de bases de datos, tokens de servicio, credenciales de integraciones — porque el modo sensitive requiere eliminar y recrear la variable y hasta ahora la interfaz no era tan evidente.

# Antes del incidente: una variable "normal" (no sensible)
vercel env add DATABASE_URL production
# Vercel la almacena SIN cifrar en reposo

# Después: la misma variable como "sensitive"
curl -X POST "https://api.vercel.com/v9/projects/<project-id>/env" \
  -H "Authorization: Bearer $VERCEL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{
    "key": "DATABASE_URL",
    "value": "postgresql://user:pass@host/db",
    "type": "sensitive",
    "target": ["production"]
  }]'

Con el SDK de Node es igual de directo:

import { Vercel } from "@vercel/sdk";

const vercel = new Vercel({
  bearerToken: process.env.VERCEL_TOKEN!,
});

// Crear una variable de entorno sensible
async function crearEnvSensible() {
  const result = await vercel.projects.createProjectEnv({
    idOrName: "mi-proyecto",
    requestBody: {
      key: "STRIPE_SECRET_KEY",
      value: "sk_live_...",
      type: "sensitive",
      target: ["production"],
    },
  });
  console.log("Variable creada:", result.key, "→ tipo:", result.type);
}

crearEnvSensible();

La clave es el "type": "sensitive". Sin ese flag, tu STRIPE_SECRET_KEY vive en texto plano en los servidores de Vercel.

Cómo auditar tus apps OAuth en Google Workspace

El vector de entrada fue una app OAuth de terceros. Este es el patrón de ataque que vamos a ver más: le das permiso a una app de IA, un generador de diagramas, una herramienta de productividad — y si esa app se compromete, el atacante hereda tus permisos de Google Workspace.

Google te permite auditar las apps que tienen acceso a tu workspace. Desde la Admin Console:

Security → API Controls → Manage OAuth Apps

Para automatizar, puedes usar la Google Admin SDK Directory API para inspeccionar los tokens OAuth de tu organización. El endpoint tokens.list requiere el userKey de cada usuario y devuelve todas las apps OAuth autorizadas:

# Listar tokens OAuth de un usuario del workspace
# Requiere rol de admin y un token de acceso válido

# 1. Obtener un token de acceso
ACCESS_TOKEN=$(gcloud auth print-access-token)

# 2. Listar apps OAuth autorizadas para un usuario
curl -s "https://admin.googleapis.com/admin/directory/v1/users/alice@ejemplo.com/tokens" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  | jq '.items[] | {clientId: .clientId, appName: .clientName, scopes: .scopes}'

# 3. Revocar una app sospechosa por su client ID
curl -X DELETE \
  "https://admin.googleapis.com/admin/directory/v1/users/alice@ejemplo.com/tokens/CLIENT_ID" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

Vercel publicó el OAuth app ID del atacante como IOC: 110671459871-30f1spbu0hptbs60cb4vsmv79i7bbvqj.apps.googleusercontent.com. Si ese ID aparece en los tokens de cualquier usuario de tu organización, revoca el acceso inmediatamente usando el endpoint tokens.delete del paso 3.

La lección: si tu organización de Google Workspace tiene apps de terceros conectadas, revísalas. Cada app OAuth es una superficie de ataque.

Estrategia: separa lo sensible de lo que no lo es

A la hora de configurar variables de entorno, aplica una regla simple:

Si la variable permite acceder a un sistema, servicio o dato que no es público, márcala como sensible.

# ✅ No sensible — flags públicos, URLs de frontend
NEXT_PUBLIC_APP_URL=https://mi-app.com
NEXT_PUBLIC_API_VERSION=v2

# ❌ También debería ser sensible — esto NO es un flag inocente
DATABASE_URL=postgresql://admin:secret@db.internal:5432/prod
REDIS_URL=redis://:secret@cache.internal:6379

# ❌ Absolutamente sensible
STRIPE_SECRET_KEY=sk_live_abc123
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG
SENDGRID_API_KEY=SG.xyz123

Un patrón que funciona bien es auditar todas tus variables periódicamente con un script:

// audit-env-vars.ts — Lista las env vars de un proyecto Vercel
// y marca las que parecen sensibles pero no están cifradas

const SENSITIVE_PATTERNS = [
  /secret/i,
  /password/i,
  /token/i,
  /key/i,
  /database.*url/i,
  /redis.*url/i,
  /mongodb.*uri/i,
  /api.*key/i,
  /private.*key/i,
  /auth.*token/i,
];

for (const env of projectEnvs) {
  const looksSensitive = SENSITIVE_PATTERNS.some(
    (p) => p.test(env.key)
  );

  if (looksSensitive && env.type !== "sensitive") {
    console.warn(
      `⚠️  ${env.key} parece sensible pero NO está cifrada (type="${env.type}")`
    );
  }
}

Defense in depth: no confíes en una sola capa

Este incidente es un ejemplo clásico de defensa en profundidad fallida. El atacante tuvo que superar varias capas, pero una vez dentro de la primera (el token OAuth), las siguientes capas no resistieron porque había variables sin cifrar. Para una infraestructura seria, considera:

  • Rotación automática de secrets: herramientas como Doppler, Infisical o el propio Vercel con políticas de rotación pueden forzar la rotación periódica.
  • Zero-trust para variables: marca todo como sensible por defecto. Vercel ahora permite configurar una policy que obliga a que todas las nuevas variables en Producción y Preview sean sensibles.
  • Principio de mínimo privilegio para OAuth: no conectes apps que no necesitas. Cada conexión es una puerta.
  • Monitoriza los accesos: si una variable de entorno de producción se lee desde una IP o sesión inhabitual, debería generar una alerta.

Mi veredicto

El incidente de Vercel no es una vulnerabilidad técnica exótica. Es el viejo problema de las variables de entorno en claro combinado con un OAuth de terceros comprometido. Lo preocupante es que Vercel — una plataforma que maneja el deploy de millones de proyectos — tenía un modelo donde las variables “no sensibles” se almacenaban sin cifrar en reposo. En 2026, todo secreto debería cifrarse en reposo por defecto.

Lo positivo: Vercel ha reaccionado rápido, ha publicado el OAuth app ID como IOC y ha mejorado el dashboard para gestionar variables sensibles. Lo negativo: la comunicación inicial fue vaga (“ciertos sistemas internos”), y el tiempo entre el compromiso y la notificación pública aún no está claro.

Para los desarrolladores que usan Vercel, la acción es simple: revisa tus variables de entorno, marca como sensibles todo lo que no sea un flag público y audita las apps OAuth de tu Google Workspace. Hoy. No mañana.

Y si estás en otra plataforma — Netlify, Railway, Render — aplica la misma regla. El vector de ataque no es exclusivo de Vercel. Es cualquier plataforma que almacene secrets sin cifrar y acepte OAuth de terceros.