FlawOpen
● Aislamiento de solicitudes serverless

Filtración de estado serverless: mantén los datos de usuario por solicitud

Cómo los runtimes serverless reutilizados pueden exponer datos antiguos y cómo aislar la identidad y el estado de cada solicitud.

💡 Explicación sencilla (ELI5)

Trata la autenticación, la identidad del tenant y los datos de cada solicitud como una acreditación de visitante: pertenecen a una visita y no deben dejarse en una mesa compartida. Algunos runtimes serverless reutilizan un entorno o isolate para solicitudes posteriores. Si el código guarda el contexto de un usuario en estado mutable a nivel de módulo y otra solicitud lo lee, la aplicación puede cruzar su propio límite entre usuarios o tenants.

La reutilización es un comportamiento de implementación, no una garantía de que el siguiente usuario llegue al mismo runtime ni de que este siga activo durante un plazo fijo. Para que haya filtración, debe existir estado de ejecución compartido y una ruta de código que conserve datos de la solicitud o lea estado obsoleto. Mantén la identidad dentro de la solicitud y pásala explícitamente a las operaciones que la necesiten.

Conceptos principales y términos

Execution environment reuse
Reutilización del entorno: la plataforma puede conservar un runtime o isolate para invocaciones posteriores, aunque también puede retirarlo en cualquier momento.
Module-level state
Estado a nivel de módulo: valor inicializado fuera del handler. Puede permanecer en ese runtime entre invocaciones y no debe contener datos de usuario o tenant.
AsyncLocalStorage
AsyncLocalStorage: API de Node.js que propaga un almacén por un contexto asíncrono. Rastrea contexto; no autentica ni autoriza usuarios.
Cross-request data exposure
Exposición entre solicitudes: una solicitud posterior lee datos retenidos por una anterior en el mismo contexto reutilizado. La reutilización por sí sola no demuestra una filtración.

Flujo paso a paso de la exposición de datos

Step 1

Una solicitud crea datos propios

La solicitud A se autentica y resuelve su contexto de usuario o tenant. El fallo aparece si el código guarda esa identidad en una variable mutable a nivel de módulo en vez de mantenerla dentro del handler.

Step 2

El runtime puede reutilizarse

Tras responder, la plataforma puede mantener activo el entorno. AWS documenta la reutilización; Cloudflare Workers puede reutilizar un isolate, pero ni el enrutamiento ni su duración forman una secuencia fija entre usuarios.

Step 3

Otra solicitud llega al mismo runtime

Si la solicitud B se atiende en el mismo proceso o isolate, el valor de módulo dejado por A sigue disponible hasta que se sobrescriba o limpie. Una salida anticipada o una autenticación ausente puede hacer accesible el estado antiguo.

Step 4

La aplicación devuelve datos con la identidad equivocada

Si el handler usa ese valor obsoleto en una consulta o respuesta, B podría recibir datos asociados a A. Es un fallo condicional de aislamiento de la aplicación, no una propiedad inevitable de las plataformas serverless.

Código fuente: patrón defectuoso y solución segura

INSEGURO: ESTADO COMPARTIDO
// UNSAFE: request identity is mutable process-wide state.
import { NextResponse } from "next/server";

let cachedUserContext: { userId: string; role: string } | null = null;

export async function POST(req: Request) {
  const token = req.headers.get("authorization");

  if (token) {
    cachedUserContext = await decodeUser(token);
  }

  // If token is missing, stale state can still be used.
  return NextResponse.json({ actor: cachedUserContext?.userId });
}
MÁS SEGURO: IDENTIDAD LOCAL
// SAFE PATTERN: keep identity local and pass it to the service.
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const user = await authenticate(req);
  if (!user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const account = await loadAccountForUser(user.id);
  return NextResponse.json({ actor: user.id, account });
}

// The service enforces ownership in its query:
async function loadAccountForUser(userId: string) {
  return db.account.findFirst({ where: { ownerId: userId } });
}

Lista de endurecimiento de ingeniería

← Explorar el directorio completo de seguridad Explorar guías de referencia →

Referencias

Véase también PHP == y las comparaciones de magic hash ¿El bloque unsafe de Rust sigue siendo seguro? ¿strncpy() es más segura que strcpy()? CWE frente a CVE: ¿cuál es la diferencia? Mass assignment: riesgos y listas seguras de campos