Aikido

¿Por qué las variables globales causan fugas de datos en los servidores Node.js?

Seguridad

Regla
Evite involuntarias global variable caching.In Node.js
y Python servidores, globales variables persisten en a través de
peticiones, causando datos de datos y carrera condiciones de carrera.

Lenguajes compatibles: JavaScript, TypeScript, Python

Introducción

Las variables globales en los servidores Node.js persisten durante la vida útil del proceso, no solo para una única solicitud. Cuando los manejadores de solicitudes almacenan datos de usuario en variables globales, esos datos permanecen accesibles para solicitudes posteriores de diferentes usuarios. Esto crea vulnerabilidades de seguridad donde los datos de sesión, los tokens de autenticación o la información personal del usuario A se filtran al usuario B.

Por qué es importante

Implicaciones de seguridad (fugas de datos): Las variables globales que almacenan en caché datos específicos del usuario crean fugas de datos entre solicitudes. El estado de autenticación, los datos de sesión o la información personal de un usuario se hacen visibles para otros usuarios, violando los límites de privacidad y seguridad.

Condiciones de carrera: Cuando múltiples solicitudes concurrentes modifican la misma variable global, existe una alta probabilidad de comportamiento impredecible. Los datos del usuario A pueden ser sobrescritos por la solicitud del usuario B a mitad del procesamiento, lo que lleva a cálculos incorrectos, estados corruptos o que los usuarios vean los datos de otros.

Complejidad de la depuración: Los problemas causados por el almacenamiento en caché de variables globales son notoriamente difíciles de reproducir porque dependen del tiempo de la solicitud y la concurrencia. Los errores aparecen intermitentemente en producción bajo carga, pero rara vez se manifiestan en pruebas de desarrollo de un solo hilo.

Fugas de memoria: Las variables globales que acumulan datos sin limpieza crecen sin límite con el tiempo. Cada solicitud añade más datos a las cachés o arrays globales, agotando finalmente la memoria del servidor y requiriendo reinicios del proceso.

Ejemplos de código

❌ No 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 qué está mal: Las variables globales currentUser y requestData persisten entre solicitudes. Cuando múltiples solicitudes se ejecutan concurrentemente, la solicitud del usuario B puede sobrescribir currentUser mientras buildUserProfile() del usuario A todavía se está ejecutando, haciendo que el usuario A vea los datos del usuario B.

✅ Conforme:

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 qué esto es importante: Todos los datos específicos de la solicitud se almacenan en variables locales con ámbito en el manejador de solicitudes. Cada solicitud tiene un estado aislado que no puede filtrarse a otras solicitudes concurrentes. Las funciones reciben datos a través de parámetros en lugar de acceder al estado global, eliminando las condiciones de carrera.

Conclusión

Mantén todos los datos específicos de la solicitud en variables locales u objetos de solicitud proporcionados por tu framework. Usa variables globales solo para estados verdaderamente compartidos, como la configuración, los pools de conexión o las cachés de solo lectura. Cuando el estado global sea necesario, utiliza controles de concurrencia adecuados y asegúrate de que los datos nunca sean específicos del usuario.

Preguntas frecuentes

¿Tiene preguntas?

¿Cuándo es seguro usar variables globales en Node.js?

Las variables globales son seguras para datos de solo lectura que se aplican a todas las solicitudes: configuración de la aplicación, pools de conexiones de bases de datos, plantillas compiladas o utilidades compartidas. Nunca almacene datos específicos de la solicitud o del usuario de forma global. Si necesita almacenar datos en caché globalmente, asegúrese de que estén correctamente indexados y que el acceso sea thread-safe, o utilice soluciones de caché adecuadas como Redis.

¿Qué hay de las variables a nivel de módulo que no son explícitamente globales?

Las variables a nivel de módulo (const, let, var en el ámbito del archivo) se comportan exactamente como las globales en Node.js. Persisten en todas las solicitudes y son compartidas por todos los manejadores de solicitudes concurrentes. Se aplican los mismos riesgos de fuga de datos y condiciones de carrera. Trate las variables a nivel de módulo con la misma precaución que las globales explícitas.

¿Cómo comparto datos entre middleware y manejadores de rutas?

Utilice las propiedades del objeto de solicitud proporcionadas por su framework. Express proporciona req.locals o propiedades personalizadas en req. Fastify tiene request.decorateRequest(). Estos objetos tienen alcance de solicitud y se limpian automáticamente después de que la solicitud se completa, evitando fugas entre solicitudes.

¿Qué hay de los patrones singleton y las instancias de clase?

Las instancias Singleton a nivel de módulo son estado global. Si contienen datos específicos de la solicitud, se aplican los mismos problemas. Diseñe los singletons para que sean sin estado (stateless) o que solo contengan configuración. Para operaciones con estado (stateful), cree nuevas instancias por solicitud o utilice patrones de fábrica que aseguren el aislamiento.

¿Cómo detecto estos problemas durante el desarrollo?

Ejecuta pruebas de carga con solicitudes concurrentes utilizando diferentes contextos de usuario. Las condiciones de carrera y las fugas de datos a menudo no se manifiestan con pruebas secuenciales. Utiliza herramientas como Apache Bench o autocannon para generar carga concurrente. Añade un registro que incluya IDs de solicitud para rastrear cuándo los datos de una solicitud aparecen en otra.

¿Esto se aplica a funciones sin servidor como AWS Lambda?

Parcialmente. Cada invocación de Lambda obtiene un entorno de ejecución nuevo, pero el contenedor puede reutilizarse entre invocaciones. Las variables globales persisten entre invocaciones que reutilizan el mismo contenedor. No confíe en que las variables globales se restablezcan. Siga las mismas prácticas: mantenga los datos de la solicitud en el ámbito local.

¿Qué hay de las aplicaciones Python WSGI/ASGI?

Se aplican los mismos principios. Los servidores web de Python se ejecutan en modo multihilo o asíncrono, por lo que las variables a nivel de módulo se comparten entre las solicitudes. El objeto 'g' de Flask y la inyección de dependencias de FastAPI proporcionan almacenamiento con ámbito de solicitud. Django tiene objetos de solicitud. Utiliza los mecanismos proporcionados por el framework en lugar de las variables globales del módulo para los datos de la solicitud.

Asegúrate ahora.

Proteja su código, la nube y el entorno de ejecución en un único sistema central.
Encuentre y corrija vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.