El Payload Que Nunca Inspeccionaste
¡Hola Devs! Abre la pestaña Network en cualquier página del Next.js App Router y filtra por Content-Type: text/x-component. Lo que ves no es JSON. Es Flight — el protocolo de streaming line-delimited de React, con su propio sistema de tipos, su propia resolución de referencias y sus propias reglas para reconstruir comportamiento ejecutable en el cliente. 🚀
La mayoría de los devs React nunca mira. El framework lo maneja, así que nadie cuestiona lo que esa confianza implica. Luego en diciembre de 2025, cayó CVE-2025-55182 — un RCE no autenticado de CVSS 10.0 en la capa de deserialización de Flight. CISA lo agregó al catálogo de vulnerabilidades explotadas. Sysdig vinculó la explotación in-the-wild con actores estado-nación de Corea del Norte desplegando implantes file-less vía blockchain Ethereum.
Esto no es un bug de parsing aislado. Es un síntoma. Flight reconstruye referencias ejecutables, componentes lazy-loaded, endpoints RPC y estado asíncrono desde un stream de texto. Eso es un sistema de deserialización — y hereda todos los riesgos clásicos de deserialización.

Flight On The Wire: El Sistema de Prefijo $
Cada línea de Flight sigue ID:TAG:PAYLOAD. Lo interesante es el sistema de prefijo $ — cuando el parser del cliente encuentra un string que empieza con $, no lo trata como texto literal. Lo rutea por un camino de resolución específico en parseModelString.
| Prefijo | Tipo | Comportamiento del Parser |
|---|---|---|
$ | Model Reference | Resuelve a otro chunk en el stream |
$: | Property Access | Navega en propiedades de un chunk resuelto ($1:user:name) |
$F | Server Reference | Representa una Server Action invocable (RPC) |
$L | Lazy Component | Difiere el loading del componente |
$@ | Raw Chunk | Retorna el wrapper interno Chunk (Thenable) |
$B | Blob/Binary | Dispara el handler de blob |
El $: es el crítico para explotación. Permite property traversal arbitrario dirigido por datos en el stream. Si has auditado JavaScript para prototype pollution, ese patrón te suena familiar.
La Gadget Chain de React2Shell
La causa raíz estaba en getOutlinedModel, la función que resuelve paths profundos de referencias $::
// Loop vulnerable — sin check de hasOwnProperty
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]];
El atacante manda $1:__proto__:constructor:constructor, y el loop sube de un objeto JSON plano hasta Object.prototype, luego Function. Function("código arbitrario")() ejecuta. Sin allowlist, sin check de __proto__.
Desde ahí, la chain compone features legítimas de Flight:
- Prototype walk a Function vía
$: - Raw chunk self-reference vía
$@0para obtener un handle interno mutable - Thenable hijack — setea
.thenaChunk.prototype.then - Context confusion — sobrescribe
_response._formData.getapuntando aFunction - Trigger vía
$B0— blob handler llamaFunction("comando_atacante")()
Cada paso es una feature legítima. La vulnerabilidad emerge de la composición. ¡Fíjate en esto! 👀

Defensas, Rankeadas por Impacto
1. Validación de Schema en Cada Server Action
La defensa más impactante a nivel de aplicación. Valida con Zod o Valibot al inicio de cada Server Action — antes de loggear, antes de cualquier lógica. Usa .safeParse(), no .parse().
"use server"
import { z } from "zod"
const CommentSchema = z.object({
postId: z.string().uuid(),
body: z.string().min(1).max(5000),
})
// Bien: valida el argumento crudo primero
export async function addComment(data) {
const parsed = CommentSchema.safeParse(data)
if (!parsed.success) return { error: "Invalid input" }
await db.comments.create(parsed.data)
}
Si tu Server Action no empieza con un schema parse, trátalo como vulnerabilidad. Considera una regla ESLint custom que flaggee cualquier export "use server" sin validación en la primera instrucción.
2. El Paquete server-only
Importa server-only al inicio de cualquier archivo con credenciales de DB o lógica interna. El build falla si un Client Component lo importa transitivamente. Cuidado con barrel files — rompen la frontera silenciosamente.
3. CSRF Más Allá de los Defaults del Framework
CVE-2026-27978 mostró que el check Origin vs Host de Next.js tiene edge cases (bypass de Origin: null vía iframes sandboxed). Setea SameSite=Strict en cookies de sesión, agrega tokens CSRF por sesión para operaciones críticas, y nunca pongas "null" en allowedOrigins.
4. Verifica Tu Versión del Patch
El fix del RCE llegó en React 19.0.1, 19.1.2, 19.2.1. Fixes de DoS necesitan 19.0.4+, 19.1.5+ o 19.2.4+. Checa tu lockfile:
npm ls react react-dom react-server-dom-webpack
5. Taint API — Guardrail de Dev, No Frontera de Seguridad
taintObjectReference atrapa errores honestos (pasar user object completo al Client Component). Pero taint rastrea referencias, no contenido — cualquier spread o round-trip de serialización lo rompe. Defensa en profundidad útil, no frontera primaria.
6. WAFs — Reducción de Ruido, No Frontera
WAFs atrapan scanners automatizados. No paran atacantes motivados: buffers de inspección son ~128KB, así que prepende 130KB de padding y el payload pasa. Trata WAF como reducción de ruido, no seguridad.
Lo Que Sigue Estructuralmente Expuesto
- MITM en el Flight stream — texto plano, estructura predecible. Inyecta imports
$I, server references$F, o modifica data rowsD. - Enumeración de Server Actions —
server-reference-manifest.jsonexpuesto entrega un mapa completo de API para ataques IDOR. - Tampering de closures encriptadas —
NEXT_SERVER_ACTIONS_ENCRYPTION_KEYestático en multi-server + file read = closures forjadas. - Activación de supply chain — paquetes npm dormantes bundled pero nunca cargados pueden activarse vía referencias
$Iinyectadas.

Hacia Dónde Va Esto
El Flight protocol resuelve un problema genuinamente difícil: streaming de árboles de componentes interactivas con hidratación progresiva y code splitting server-driven. Funciona. Pero funciona serializando referencias ejecutables en un protocolo de texto y confiando en la estructura del stream de ambos lados.
El patch de hasOwnProperty está correcto. Los fixes de DoS están en su lugar. Pero exponer property traversal arbitrario y reconstrucción de Thenables ejecutables a través de un protocolo de red fue un error de diseño. El patch trata el síntoma.
Esperar que el parser maneje cada edge case no ha funcionado históricamente — GWT, JSF ViewState, ASP.NET ViewState siguieron el mismo patrón. Si shipeas Server Components, lee react-client/src/ReactFlightClient.js. Sabe qué confía tu framework en tu nombre.
Lectura complementaria:
- Cloudflare DDoS Report H1 2026: Ataques de 1 Tbps Crecen 519% — Qué Significa Para Tu Infraestructura
- De Oracle a PostgreSQL en Azure: Un Blueprint Práctico de Migración Enterprise
Fuente: Smashing Magazine — Weaponizing and Defending the React Flight Protocol