FlawOpen
● Isolamento de requisições serverless

Vazamento de estado serverless: mantenha dados de usuários no escopo da requisição

Veja como runtimes serverless reutilizados podem expor dados antigos de usuários e como isolar identidade e estado por requisição.

💡 Explicação em linguagem simples (ELI5)

Trate autenticação, identidade do tenant e dados específicos da requisição como um crachá de visitante: pertencem a uma visita e não devem ficar numa bancada compartilhada. Alguns runtimes serverless reutilizam um ambiente de execução ou isolate em requisições posteriores. Se o código guardar o contexto de um usuário em estado mutável no escopo do módulo e outra requisição o ler, a aplicação pode cruzar a própria fronteira entre usuários ou tenants.

A reutilização é um comportamento de implementação, não uma garantia de que o próximo usuário chegará ao mesmo runtime nem de que ele ficará ativo por um período fixo. O vazamento exige estado de execução compartilhado e um caminho de código que preserve dados da requisição ou leia estado antigo. Mantenha a identidade local à requisição e passe-a explicitamente às operações que precisam dela.

Conceitos principais e termos

Execution environment reuse
Reutilização do ambiente de execução: a plataforma pode manter um runtime ou isolate disponível para invocações posteriores, mas também pode encerrá-lo a qualquer momento.
Module-level state
Estado no escopo do módulo: valor inicializado fora do handler. Pode permanecer no runtime entre invocações e não deve guardar dados específicos de usuário ou tenant.
AsyncLocalStorage
AsyncLocalStorage: API do Node.js para propagar um armazenamento por um contexto assíncrono. Ela rastreia contexto; não autentica nem autoriza usuários.
Cross-request data exposure
Exposição de dados entre requisições: falha em que uma requisição posterior lê dados mantidos de uma anterior no mesmo contexto reutilizado. A reutilização, sozinha, não prova vazamento.

Fluxo passo a passo da exposição de dados

Step 1

Uma requisição cria dados próprios

A Requisição A autentica e resolve seu contexto de usuário ou tenant. O bug aparece se o código grava essa identidade numa variável mutável no escopo do módulo, em vez de mantê-la no handler.

Step 2

O runtime pode ser reutilizado

Depois da resposta, a plataforma pode manter o ambiente aquecido. A AWS documenta reutilização de ambientes; o Cloudflare Workers pode reutilizar um isolate, mas o roteamento e a vida útil não formam uma sequência fixa entre usuários.

Step 3

Uma requisição posterior chega ao mesmo runtime

Se a Requisição B for atendida pelo mesmo processo ou isolate, um valor no escopo do módulo deixado pela Requisição A continua disponível até ser sobrescrito ou limpo. Um retorno antecipado ou uma falha de autenticação pode tornar o estado antigo acessível.

Step 4

A aplicação retorna dados com a identidade errada

Se o handler usar esse valor antigo numa consulta ou resposta, B pode receber dados associados a A. É uma falha condicional de isolamento da aplicação, não uma propriedade inevitável das plataformas serverless.

Código-fonte: padrão defeituoso e implementação segura

INSEGURO: ESTADO COMPARTILHADO
// 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 });
}
MAIS SEGURO: IDENTIDADE 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 } });
}

Checklist de hardening de engenharia

← Navegar pelo diretório completo de segurança Explorar guias de referência →

Referências

Veja também PHP == e comparações de magic hash O bloco unsafe do Rust ainda é seguro? strncpy() é mais segura que strcpy()? CWE vs. CVE: qual é a diferença? Mass assignment: riscos e listas seguras de campos