← Volver al blog
Vibe codingEquipo de Scanaris · 6 min de lectura · 04 de octubre de 2026

Seguridad al desplegar en Vercel

Vercel hace que desplegar sea tan fácil que se olvida que sigue siendo un servidor en internet. La plataforma en sí está bien protegida: TLS automático, red global, aislamiento entre proyectos. Los agujeros los pone uno mismo en la configuración, y casi siempre son los mismos cinco o seis. Esta guía los repasa uno a uno con el comando para comprobarlos y el cambio exacto para cerrarlos, pensando en apps generadas con Cursor, Bolt o v0 que acaban en Vercel.

1. Variables de entorno que acaban en el navegador

En Vercel defines las variables en el panel del proyecto, y el framework decide cuáles llegan al cliente. En Next.js, todo lo que empiece por NEXT_PUBLIC_; en Vite, por VITE_; en Nuxt, lo que esté bajo runtimeConfig.public. Es una convención útil hasta que alguien pone NEXT_PUBLIC_STRIPE_SECRET_KEY porque la IA necesitaba acceder a ella desde un componente. Esa clave viaja en el bundle a todos los visitantes.

Dos hábitos que lo evitan. Primero, revisar el prefijo: si una variable tiene el prefijo público, asume que es pública y pregúntate si debería serlo. Segundo, usar los entornos de Vercel (Production, Preview, Development) para que las claves de producción no existan en las ramas de prueba, y marcar las sensibles como Sensitive, que impide verlas después desde el panel. Para comprobar qué ha llegado al bundle:

curl -s https://tudominio.com/ | grep -o '/_next/static/chunks/[^"]*\.js' | sort -u | head -20

Descarga esos archivos y busca sk_live, service_role, AKIA o el nombre de tus variables. Más en secretos que tu app filtra en el navegador.

2. Despliegues de vista previa públicos

Cada rama y cada pull request genera una URL del tipo proyecto-git-rama-equipo.vercel.app. Es cómodo para enseñar cambios, y es público: cualquiera con la URL ve la versión en desarrollo, con sus funciones a medias y, a veces, sus datos de prueba. Vercel añade por defecto una cabecera para que los buscadores no indexen las vistas previas, pero eso no impide que una persona la abra.

La solución está en Settings, Deployment Protection: activa Vercel Authentication para que solo miembros del equipo accedan a las previews (está disponible en el plan gratuito), o protección por contraseña en los planes de pago. Y recuerda que la URL proyecto.vercel.app de producción también existe junto a tu dominio: redirígela a tu dominio o pon una canónica para que no compita contigo en Google.

3. Cabeceras de seguridad que nadie puso

Vercel sirve tu app con las cabeceras mínimas; las de seguridad las tienes que añadir tú. Sin ellas, tu web puede incrustarse en un iframe ajeno, el navegador adivina tipos de contenido y no hay política que limite qué scripts se ejecutan. En vercel.json se añaden así:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
        { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
      ]
    }
  ]
}

En Next.js puedes hacerlo también desde next.config.js con la función headers(). La Content-Security-Policy merece un paso aparte porque hay que ajustarla a tus scripts; la explicación de cada cabecera está en cabeceras de seguridad HTTP explicadas para humanos, y puedes comprobar las tuyas con el analizador de cabeceras.

4. Mapas de código publicados

Los source maps permiten ver el código original en lugar del minificado. Son útiles para depurar y una mala idea en producción: regalan tu lógica, tus rutas internas y los comentarios con "TODO: quitar esta clave". Next.js los desactiva por defecto en el navegador (productionBrowserSourceMaps: false); en Vite depende de build.sourcemap, que algunas plantillas ponen en true. Comprueba si existen:

curl -sI https://tudominio.com/_next/static/chunks/main-abc123.js.map | head -1

Si responde 200, están publicados. Si necesitas mapas para rastrear errores, súbelos a tu servicio de monitorización en el build y no los sirvas al público.

5. Archivos y rutas que no deberían responder

Todo lo que hay en la carpeta public/ se sirve tal cual. Un .env copiado ahí por error, un backup.json, un config.yml con credenciales: públicos. Las rutas de API también cuentan: /api/debug, /api/seed o /api/admin que la IA creó para probar y que siguen en producción sin autenticación. Y las rutas de cron: Vercel las llama con una cabecera Authorization que contiene tu CRON_SECRET; si no lo compruebas en el código, cualquiera puede lanzar tu tarea programada.

for p in .env .env.local .git/config config.json backup.zip api/debug api/seed; do
  printf "%s -> " "$p"; curl -s -o /dev/null -w "%{http_code}\n" "https://tudominio.com/$p"
done

Todo lo que responda 200 merece una mirada.

6. Funciones sin límites

Una ruta de API que llama a OpenAI o envía correos sin limitar peticiones es una factura esperando a que la descubran. Vercel tiene un cortafuegos con reglas de límite de peticiones por IP y un modo de desafío contra bots que se activa desde el panel sin tocar código. Para endpoints con coste, añade además un límite por usuario en tu propia lógica y exige sesión.

7. CORS y cookies en las rutas de API

Si tu API responde con Access-Control-Allow-Origin: * y además usa cookies, cualquier web puede hacer peticiones en nombre de tus usuarios. En Vercel se configura por ruta en vercel.json o en el propio handler. Lista los orígenes permitidos de forma explícita y nunca combines el comodín con credenciales. Detalle en CORS mal configurado.

8. Lo que Vercel ya hace por ti

Para que no pierdas tiempo: el certificado TLS se emite y renueva solo, HTTP redirige a HTTPS automáticamente, las funciones corren aisladas y los registros de despliegue quedan auditados. Lo que no hace es decidir por ti qué es público, qué cabeceras envías ni quién puede ver tus previews. Esa parte es tuya.

Checklist de cinco minutos

  • Ninguna variable con prefijo público contiene una clave secreta.
  • Deployment Protection activada para las vistas previas.
  • Cabeceras de seguridad en vercel.json o next.config.js.
  • Source maps no accesibles en producción.
  • Nada sensible en public/; rutas de prueba eliminadas; cron protegido con su secreto.
  • Límite de peticiones en las rutas con coste.
  • CORS con orígenes explícitos.
  • La URL .vercel.app redirige a tu dominio.

Scanaris lo comprueba por ti

Scanaris revisa desde fuera tu despliegue en Vercel: cabeceras, secretos en el bundle, source maps, archivos y rutas expuestas, CORS, cookies y TLS, y te da un fix prompt por cada fallo para pegar en Cursor, Bolt o v0 con el cambio exacto en vercel.json o en tu código. Son comprobaciones de solo lectura sobre lo que ya es público; nunca toca tu proyecto. Los riesgos específicos de la plataforma están en la guía de seguridad para Vercel, y si tu app es Next.js, en la checklist de Next.js.

Preguntas frecuentes

¿Las vistas previas de Vercel son privadas por defecto?

No. Son públicas para quien tenga la URL, aunque no se indexen en buscadores. Activa Vercel Authentication en Deployment Protection para limitarlas a tu equipo.

¿Vercel pone cabeceras de seguridad automáticamente?

Solo las básicas de funcionamiento. HSTS, X-Frame-Options, Content-Security-Policy y el resto las tienes que configurar tú en vercel.json o en el framework.

¿Qué pasa si una clave secreta ya se publicó con prefijo público?

Dala por comprometida aunque la quites del código: el bundle anterior puede estar cacheado o descargado. Rota la clave en el servicio de origen, muévela a una variable sin prefijo y despliega de nuevo.

¿Y tu web, cómo va?

Pásale Scanaris y compruébalo en 30 segundos, gratis.

Escanear mi web

Checks relacionados

Guías de seguridad por stack