Los Componentes de Servidor de React (RSC) en App Router representan un paradigma novedoso que elimina gran parte de la redundancia y riesgos potenciales asociados con métodos convencionales. Dada su novedad, los desarrolladores y equipos de seguridad pueden encontrar desafíos para alinear sus protocolos existentes con este modelo.
Este documento destaca áreas clave a considerar, protecciones integradas e incluye una guía para auditar aplicaciones, enfocándose especialmente en riesgos de exposición accidental de datos.
Elección del modelo de manejo de datos
Los Componentes de Servidor de React difuminan la línea entre servidor y cliente. El manejo de datos es crucial para entender dónde se procesa la información y dónde queda disponible.
Primero debemos elegir el enfoque adecuado para nuestro proyecto:
- APIs HTTP (recomendado para proyectos grandes existentes)
- Capa de Acceso a Datos (recomendado para nuevos proyectos)
- Acceso a Datos a Nivel de Componente (recomendado para prototipos y aprendizaje)
Recomendamos mantener un enfoque consistente sin mezclar demasiado. Esto clarifica expectativas tanto para desarrolladores como auditores de seguridad. Las excepciones destacan como sospechosas.
APIs HTTP
Para proyectos existentes adoptando Componentes de Servidor, el enfoque recomendado es tratarlos como no confiables por defecto, similar a SSR o en el cliente. No asuma redes internas como zonas de confianza, aplicando el concepto de Confianza Cero. Llame endpoints API como REST o GraphQL usando fetch()
desde Componentes de Servidor, igual que en el cliente, pasando cookies correspondientes.
Si tenía getStaticProps
/getServerSideProps
conectando a bases de datos, considere consolidar moviendo esta lógica a endpoints API para uniformidad.
Cuidado con controles de acceso que asuman que llamadas desde la red interna son seguras.
Este enfoque permite mantener estructuras organizacionales existentes, donde equipos backend especializados pueden aplicar prácticas de seguridad establecidas. Funciona bien incluso con lenguajes distintos a JavaScript.
Aprovecha beneficios de Componentes de Servidor como menos código enviado al cliente y ejecución eficiente de cascadas de datos.
Capa de Acceso a Datos
Para nuevos proyectos, recomendamos crear una Capa de Acceso a Datos separada en su código JavaScript, consolidando todo acceso a datos. Esto asegura consistencia y reduce bugs de autorización, siendo más fácil de mantener al centralizar en una sola librería. Permite mejor cohesión de equipo con un solo lenguaje y ventajas de rendimiento como caché en memoria compartida.
Construya una librería interna que implemente verificaciones antes de devolver datos, similar a endpoints HTTP pero en el mismo modelo de memoria. Cada API debe aceptar el usuario actual y verificar permisos antes de retornar datos. El principio es que un Componente de Servidor solo debe ver datos autorizados para el usuario actual.
Estos métodos deben exponer objetos seguros para transferir al cliente (Objetos de Transferencia de Datos - DTO). Esto crea capas donde auditorías pueden enfocarse en la Capa de Acceso a Datos mientras la UI itera rápidamente.
Claves secretas deben almacenarse en variables de entorno, pero solo la capa de acceso a datos debe acceder a process.env
en este enfoque.
Acceso a Datos a Nivel de Componente
Este enfoque coloca consultas directamente en Componentes de Servidor, siendo apropiado solo para prototipado rápido. En este caso, audite cuidadosamente archivos "use client"
. Revise funciones exportadas que acepten objetos demasiado amplios como User
, o props como token
o creditCard
. Campos sensibles como phoneNumber
requieren escrutinio extra.
Siempre use consultas parametrizadas o librerías que lo hagan para evitar inyección SQL.
Solo Servidor
Marque código que solo debe ejecutarse en servidor con:
Esto genera errores si un Componente de Cliente importa este módulo, evitando fugas de código sensible.
En Next.js 14 puede usar experimentalmente las APIs React Taint activando taint
en next.config.js
:
Esto marca objetos que no deben pasarse al cliente:
Para valores únicos como tokens, use taintUniqueValue
.
SSR vs RSC
Para carga inicial, Next.js ejecuta Componentes de Servidor y Cliente en el servidor para generar HTML.
Los Componentes de Servidor (RSC) ejecutan en un sistema de módulos separado de los Componentes de Cliente para evitar fugas de información.
Componentes de Cliente renderizados vía SSR deben considerarse bajo la misma política de seguridad que el navegador. No deben acceder a datos privilegiados o APIs privadas. Next.js fallará el build si se importan módulos server-only
en Componentes de Cliente.
Lectura
En App Router, la lectura se implementa renderizando páginas con Componentes de Servidor.
Las entradas son searchParams en la URL, parámetros dinámicos y headers. Estos no son confiables y deben revalidarse siempre. No use searchParams para rastrear estados como ?isAdmin=true
.
Renderizar un Componente de Servidor nunca debe causar efectos secundarios como mutaciones. Next.js no permite establecer cookies o trigger revalidación durante el renderizado.
Escritura
La forma idiomática de realizar mutaciones en App Router es usando Server Actions.
Las funciones con "use server"
deben siempre validar que el usuario actual puede invocar la acción e integridad de argumentos, manualmente o con herramientas como zod
.
Closures
Las Acciones de Servidor (Server Actions) también pueden codificarse en closures. Esto permite que la acción se asocie con una instantánea de los datos utilizados en el momento del renderizado, para que puedas usarlos cuando se invoque la acción:
La instantánea del closure debe enviarse al cliente y de vuelta cuando se invoque el servidor.
En Next.js 14, las variables capturadas en el closure se cifran con el ID de la acción antes de enviarse al cliente. Por defecto, se genera una clave privada automáticamente durante la construcción de un proyecto Next.js. Cada reconstrucción genera una nueva clave privada, lo que significa que cada Acción de Servidor solo puede invocarse para una compilación específica. Puedes considerar usar Skew Protection para asegurarte de siempre invocar la versión correcta durante los re-despliegues.
Si necesitas una clave que rote con más frecuencia o que persista entre múltiples compilaciones, puedes configurarla manualmente usando la variable de entorno NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
.
Al cifrar todas las variables capturadas en el closure, evitas exponer accidentalmente secretos. Al firmarlas, dificultas que un atacante manipule la entrada de la acción.
Otra alternativa a usar closures es emplear la función .bind(...)
en JavaScript. Estas NO se cifran. Esto proporciona una opción de exclusión por rendimiento y también es consistente con .bind()
en el cliente.
El principio es que la lista de argumentos de las Acciones de Servidor ("use server"
) siempre debe tratarse como hostil y la entrada debe verificarse.
CSRF
Todas las Acciones de Servidor pueden invocarse mediante un <form>
simple, lo que podría exponerlas a ataques CSRF. Internamente, las Acciones de Servidor siempre se implementan usando POST y solo se permite este método HTTP para invocarlas. Esto por sí solo previene la mayoría de vulnerabilidades CSRF en navegadores modernos, especialmente debido a que las cookies Same-Site son la configuración predeterminada.
Como protección adicional, las Acciones de Servidor en Next.js 14 también comparan la cabecera Origin
con la cabecera Host
(o X-Forwarded-Host
). Si no coinciden, la Acción será rechazada. En otras palabras, las Acciones de Servidor solo pueden invocarse en el mismo host que la página que las aloja. Navegadores muy antiguos, no compatibles y desactualizados que no soportan la cabecera Origin
podrían estar en riesgo.
Las Acciones de Servidor no usan tokens CSRF, por lo que la sanitización del HTML es crucial.
Cuando se usan Manejadores de Ruta Personalizados (route.tsx
) en su lugar, puede ser necesaria una auditoría adicional, ya que la protección CSRF debe hacerse manualmente allí. Las reglas tradicionales aplican en esos casos.
Manejo de Errores
Los errores ocurren. Cuando se lanzan errores en el Servidor, eventualmente se relanzan en el código del Cliente para manejarlos en la interfaz de usuario. Los mensajes de error y los seguimientos de pila podrían contener información sensible. Por ejemplo, [número de tarjeta de crédito] no es un número de teléfono válido
.
En modo producción, React no envía errores o promesas rechazadas al cliente. En su lugar, se envía un hash que representa el error. Este hash puede usarse para asociar múltiples errores iguales y vincular el error con los registros del servidor. React reemplaza el mensaje de error con uno genérico propio.
En modo desarrollo, los errores del servidor aún se envían en texto plano al cliente para facilitar la depuración.
Es importante siempre ejecutar Next.js en modo producción para cargas de trabajo en producción. El modo desarrollo no está optimizado para seguridad y rendimiento.
Rutas Personalizadas y Middleware
Los Manejadores de Ruta Personalizados y el Middleware se consideran mecanismos de escape de bajo nivel para funciones que no pueden implementarse con ninguna otra funcionalidad integrada. Esto también abre posibles riesgos que el marco normalmente protege. Con gran poder viene gran responsabilidad.
Como se mencionó anteriormente, las rutas route.tsx
pueden implementar manejadores GET y POST personalizados que podrían sufrir problemas CSRF si no se hacen correctamente.
El Middleware puede usarse para limitar el acceso a ciertas páginas. Por lo general, es mejor hacer esto con una lista de permitidos en lugar de una lista de denegados. Esto se debe a que puede ser complicado conocer todas las formas diferentes de acceder a los datos, como si hay una reescritura o solicitud del cliente.
Por ejemplo, es común pensar solo en la página HTML. Next.js también soporta navegación del cliente que puede cargar cargas útiles RSC/JSON. En el Enrutador de Páginas, esto solía estar en una URL personalizada.
Para facilitar la escritura de coincidencias, el Enrutador de Aplicaciones de Next.js siempre usa la URL simple de la página tanto para el HTML inicial, como para las navegaciones del cliente y las Acciones de Servidor. Las navegaciones del cliente usan el parámetro de búsqueda ?_rsc=...
como rompedor de caché.
Las Acciones de Servidor residen en la página donde se usan y, como tal, heredan el mismo control de acceso. Si el Middleware permite leer una página, también puedes invocar acciones en esa página. Para limitar el acceso a las Acciones de Servidor en una página, puedes prohibir el método HTTP POST en esa página.
Auditoría
Si estás realizando una auditoría de un proyecto con el Enrutador de Aplicaciones de Next.js, aquí hay algunas cosas que recomendamos revisar especialmente:
- Capa de Acceso a Datos. ¿Existe una práctica establecida para una Capa de Acceso a Datos aislada? Verifica que los paquetes de base de datos y las variables de entorno no se importen fuera de la Capa de Acceso a Datos.
- Archivos
"use client"
. ¿Los props de los Componentes esperan datos privados? ¿Las firmas de tipos son demasiado amplias? - Archivos
"use server"
. ¿Los argumentos de las Acciones se validan en la acción o dentro de la Capa de Acceso a Datos? ¿Se reautoriza al usuario dentro de la acción? /[param]/
. Las carpetas con corchetes son entrada del usuario. ¿Se validan los parámetros?middleware.tsx
yroute.tsx
tienen mucho poder. Dedica tiempo adicional a auditar estos usando técnicas tradicionales. Realiza Pruebas de Penetración o Escaneo de Vulnerabilidades regularmente o en alineación con el ciclo de vida de desarrollo de software de tu equipo.