Aikido

Por que variáveis globais causam vazamentos de dados em servidores Node.js

Segurança

Regra
Evitar não intencional global variáveis caching.In Node.js
e Python servidores, globais variáveis persistem através de
pedidos, causando dados fugas e condições de corrida condições de corrida.

Linguagens suportadas: JavaScript, TypeScript, Python

Introdução

Variáveis globais em servidores Node.js persistem durante todo o ciclo de vida do processo, não apenas para uma única requisição. Quando os manipuladores de requisição armazenam dados de usuário em variáveis globais, esses dados permanecem acessíveis a requisições subsequentes de diferentes usuários. Isso cria vulnerabilidades de segurança onde dados de sessão, tokens de autenticação ou informações pessoais do usuário A vazam para o usuário B.

Por que isso importa

Implicações de segurança (vazamentos de dados): Variáveis globais que armazenam em cache dados específicos do usuário criam vazamentos de dados entre requisições. O estado de autenticação, dados de sessão ou informações pessoais de um usuário tornam-se visíveis para outros usuários, violando os limites de privacidade e segurança.

Condições de corrida: Quando múltiplas requisições concorrentes modificam a mesma variável global, há uma grande chance de comportamento imprevisível. Os dados do Usuário A podem ser sobrescritos pela requisição do Usuário B no meio do processamento, levando a cálculos incorretos, estado corrompido ou usuários vendo os dados uns dos outros.

Complexidade de depuração: Problemas causados por caching de variáveis globais são notoriamente difíceis de reproduzir porque dependem do tempo da requisição e da concorrência. Bugs aparecem intermitentemente em produção sob carga, mas raramente se manifestam em testes de desenvolvimento single-threaded.

Vazamentos de memória: Variáveis globais que acumulam dados sem limpeza crescem ilimitadamente ao longo do tempo. Cada requisição adiciona mais dados a caches ou arrays globais, eventualmente esgotando a memória do servidor e exigindo reinícios de processo.

Exemplos de código

❌ Não-conforme:

let currentUser = null;
let requestData = {};

app.get('/profile', async (req, res) => {
    currentUser = await getUserById(req.userId);
    requestData = req.body;

    const profile = await buildUserProfile(currentUser);
    res.json(profile);
});

function buildUserProfile(user) {
    return {
        name: currentUser.name,
        data: requestData
    };
}

Por que está errado: As variáveis globais currentUser e requestData persistem entre as requisições. Quando múltiplas requisições são executadas concorrentemente, a requisição do usuário B pode sobrescrever currentUser enquanto buildUserProfile() do usuário A ainda está sendo executado, fazendo com que o usuário A veja os dados do usuário B.

✅ Compatível:

app.get('/profile', async (req, res) => {
    const currentUser = await getUserById(req.userId);
    const requestData = req.body;

    const profile = buildUserProfile(currentUser, requestData);
    res.json(profile);
});

function buildUserProfile(user, data) {
    return {
        name: user.name,
        data: data
    };
}

Por que isso importa: Todos os dados específicos da requisição são armazenados em variáveis locais com escopo no handler da requisição. Cada requisição possui um estado isolado que não pode vazar para outras requisições concorrentes. As funções recebem dados por meio de parâmetros em vez de acessar o estado global, eliminando condições de corrida.

Conclusão

Mantenha todos os dados específicos da requisição em variáveis locais ou objetos de requisição fornecidos pelo seu framework. Use variáveis globais apenas para estados verdadeiramente compartilhados, como configuração, pools de conexão ou caches somente leitura. Quando o estado global for necessário, utilize controles de concorrência adequados e garanta que os dados nunca sejam específicos do usuário.

FAQs

Dúvidas?

Quando é seguro usar variáveis globais em Node.js?

Variáveis globais são seguras para dados somente leitura que se aplicam a todas as requisições: configuração de aplicação, pools de conexão de banco de dados, templates compilados ou utilitários compartilhados. Nunca armazene dados específicos de requisição ou de usuário globalmente. Se precisar armazenar dados em cache globalmente, garanta que estejam devidamente indexados e que o acesso seja thread-safe, ou use soluções de cache adequadas como o Redis.

E quanto a variáveis de nível de módulo que não são explicitamente globais?

Variáveis de nível de módulo (const, let, var no escopo do arquivo) se comportam exatamente como variáveis globais no Node.js. Elas persistem em todas as requisições e são compartilhadas por todos os manipuladores de requisições concorrentes. Os mesmos riscos de vazamento de dados e condição de corrida se aplicam. Trate as variáveis de nível de módulo com a mesma cautela que as variáveis globais explícitas.

Como compartilhar dados entre middlewares e handlers de rota?

Use as propriedades do objeto de requisição fornecidas pelo seu framework. Express fornece `req.locals` ou propriedades personalizadas em `req`. Fastify possui `request.decorateRequest()`. Esses objetos têm escopo de requisição e são automaticamente limpos após a conclusão da requisição, prevenindo vazamentos entre requisições.

E quanto aos padrões singleton e instâncias de classe?

Instâncias Singleton em nível de módulo são estado global. Se elas contêm dados específicos da requisição, os mesmos problemas se aplicam. Projete singletons para serem stateless ou para conterem apenas configuração. Para operações com estado, crie novas instâncias por requisição ou use padrões de fábrica que garantam o isolamento.

Como detectar esses problemas durante o desenvolvimento?

Execute testes de carga com requisições concorrentes usando diferentes contextos de usuário. Condições de corrida e vazamentos de dados frequentemente não aparecem com testes sequenciais. Use ferramentas como Apache Bench ou autocannon para gerar carga concorrente. Adicione logs que incluam IDs de requisição para rastrear quando dados de uma requisição aparecem em outra.

Isso se aplica a funções serverless como AWS Lambda?

Parcialmente. Cada invocação Lambda recebe um ambiente de execução novo, mas o Container pode ser reutilizado entre invocações. Variáveis globais persistem entre invocações que reutilizam o mesmo Container. Não dependa que as variáveis globais sejam redefinidas. Siga as mesmas práticas: mantenha os dados da requisição no escopo local.

E quanto a aplicações Python WSGI/ASGI?

Os mesmos princípios se aplicam. Servidores web Python rodam em modo multi-thread ou assíncrono, então variáveis em nível de módulo são compartilhadas entre requisições. O objeto `g` do Flask e a injeção de dependência do FastAPI fornecem armazenamento com escopo de requisição. O Django possui objetos de requisição. Use mecanismos fornecidos pelo framework em vez de variáveis globais de módulo para dados de requisição.

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.