Aikido

Por qué las variables globales provocan 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 toda la vida del proceso, no sólo durante una única petición. Cuando los gestores de peticiones almacenan datos de usuario en variables globales, esos datos permanecen accesibles para peticiones posteriores de usuarios diferentes. Esto crea vulnerabilidades de seguridad donde los datos de sesión del usuario A, tokens de autenticación o información personal se filtran al usuario B.

Por qué es importante

Consecuencias para la 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 vuelven visibles para otros usuarios, lo que viola los límites de privacidad y seguridad.

Condiciones de carrera: Cuando múltiples peticiones concurrentes modifican la misma variable global, hay una alta probabilidad de comportamiento impredecible. Los datos del usuario A pueden ser sobrescritos por la petición del usuario B a mitad del proceso, lo que puede dar lugar a cálculos incorrectos, estados corruptos o que los usuarios vean los datos de los demás.

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 solicitud y de la concurrencia. Los errores aparecen de forma intermitente en producción bajo carga, pero rara vez se manifiestan en pruebas de desarrollo con un solo hilo.

Fugas de memoria: Las variables globales que acumulan datos sin limpiar crecen sin límites con el tiempo. Cada petición añade más datos a las cachés o matrices globales, lo que acaba por agotar la memoria del servidor y obliga a reiniciar el proceso.

Ejemplos de códigos

❌ 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 a través de las peticiones. Cuando varias peticiones se ejecutan simultáneamente, la petición 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é es importante: Todos los datos específicos de la solicitud se almacenan en variables locales asignadas al gestor de la solicitud. 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, lo que elimina las condiciones de carrera.

Conclusión

Mantén todos los datos específicos de la petición en variables locales u objetos de petición proporcionados por tu framework. Utiliza variables globales sólo para estados realmente compartidos, como configuración, agrupaciones de conexiones o cachés de sólo 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 alguna pregunta?

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

Las variables globales son seguras para datos de sólo lectura que se aplican a todas las peticiones: configuración de la aplicación, pools de conexión a bases de datos, plantillas compiladas o utilidades compartidas. Nunca almacene datos específicos de la solicitud o del usuario de forma global. Si necesitas almacenar datos en caché de forma global, asegúrate de que están correctamente codificados y de que el acceso es seguro para los hilos, o utiliza soluciones de almacenamiento en caché adecuadas como Redis.

¿Qué ocurre con 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 igual que las globales en Node.js. Persisten en todas las peticiones y son compartidas por todos los gestores de peticiones concurrentes. Se aplican los mismos riesgos de fuga de datos y condiciones de carrera. Trata las variables a nivel de módulo con la misma precaución que las globales explícitas.

¿Cómo comparto datos entre el middleware y los gestores de rutas?

Utiliza las propiedades del objeto de petición proporcionadas por tu framework. Express proporciona req.locals o propiedades personalizadas en req. Fastify tiene request.decorateRequest(). Estos objetos son request-scoped y se limpian automáticamente después de la solicitud se completa, la prevención de fugas entre las solicitudes.

¿Qué ocurre con 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ña singletons sin estado o que sólo contengan configuración. Para operaciones con estado, crea nuevas instancias por petición o utiliza patrones de fábrica que garanticen el aislamiento.

¿Cómo detectar estos problemas durante el desarrollo?

Ejecute pruebas de carga con solicitudes concurrentes utilizando diferentes contextos de usuario. Las condiciones de carrera y las fugas de datos no suelen aparecer con las pruebas secuenciales. Utilice herramientas como Apache Bench o autocannon para generar carga concurrente. Añada registros que incluyan los ID de las peticiones para saber cuándo los datos de una petición aparecen en otra.

¿Se aplica esto a las 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íes en que las globales se restablezcan. Siga las mismas prácticas: mantenga los datos de la solicitud en el ámbito local.

¿Qué ocurre con las aplicaciones WSGI/ASGI de Python?

Se aplican los mismos principios. Los servidores web de Python funcionan con múltiples hilos o de forma asíncrona, 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 dependencia de FastAPI proporcionan almacenamiento a nivel de petición. Django tiene objetos de petición. Utiliza mecanismos proporcionados por el framework en lugar de módulos globales para los datos de las peticiones.

Asegúrese gratis

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

No requiere tarjeta de crédito | Escanea resultados en 32segs.