Aikido

Perché le variabili globali causano perdite di dati nei server Node.js

Sicurezza

Regola
Evitare un globali variabile . In Node.js
e Python server, le variabili variabili rimangono attraverso
richieste, causando perdite perdite e situazioni condizioni di competizione.

Linguaggi supportati: JavaScript, TypeScript, Python

Introduzione

Le variabili globali nei server Node.js permangono per tutta la durata del processo, non solo per una singola richiesta. Quando i gestori delle richieste memorizzano i dati degli utenti nelle variabili globali, tali dati rimangono accessibili alle richieste successive provenienti da utenti diversi. Ciò crea vulnerabilità di sicurezza in cui i dati di sessione, i token di autenticazione o le informazioni personali dell'utente A vengono divulgati all'utente B.

Perché è importante

Implicazioni per la sicurezza (fughe di dati): le variabili globali che memorizzano nella cache dati specifici dell'utente causano fughe di dati tra le richieste. Lo stato di autenticazione, i dati di sessione o le informazioni personali di un utente diventano visibili ad altri utenti, violando i limiti di privacy e sicurezza.

Condizioni di competizione: quando più richieste simultanee modificano la stessa variabile globale, vi è un’elevata probabilità che si verifichino comportamenti imprevedibili. I dati dell’utente A possono essere sovrascritti dalla richiesta dell’utente B durante l’elaborazione, causando calcoli errati, uno stato corrotto o la visualizzazione reciproca dei dati da parte degli utenti.

Complessità del debug: i problemi causati dalla memorizzazione nella cache delle variabili globali sono notoriamente difficili da riprodurre, poiché dipendono dalla tempistica delle richieste e dalla concorrenza. I bug compaiono in modo intermittente in produzione sotto carico, ma raramente si manifestano nei test di sviluppo a thread singolo.

Perdite di memoria: le variabili globali che accumulano dati senza essere ripulite crescono senza limiti nel tempo. Ogni richiesta aggiunge ulteriori dati alle cache o agli array globali, finendo per esaurire la memoria del server e rendendo necessario il riavvio del processo.

Esempi di codice

❌ Non 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
    };
}

Perché è sbagliato: le variabili globali `currentUser` e `requestData` permangono tra una richiesta e l'altra. Quando più richieste vengono eseguite contemporaneamente, la richiesta dell'utente B può sovrascrivere `currentUser` mentre la funzione `buildUserProfile()` dell'utente A è ancora in esecuzione, facendo sì che l'utente A visualizzi i dati dell'utente 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
    };
}

Perché è importante: tutti i dati specifici di una richiesta vengono memorizzati in variabili locali il cui ambito è limitato al gestore della richiesta. Ogni richiesta ha uno stato isolato che non può interferire con altre richieste in corso. Le funzioni ricevono i dati tramite parametri anziché accedere allo stato globale, eliminando così le condizioni di competizione.

Conclusione

Conserva tutti i dati specifici della richiesta in variabili locali o negli oggetti di richiesta forniti dal framework. Utilizza le variabili globali solo per lo stato effettivamente condiviso, come la configurazione, i pool di connessioni o le cache di sola lettura. Quando lo stato globale è necessario, utilizza adeguati controlli di concorrenza e assicurati che i dati non siano mai specifici per un utente.

Domande frequenti

Hai delle domande?

Quando è sicuro utilizzare le variabili globali in Node.js?

Le variabili globali sono sicure per i dati di sola lettura che si applicano a tutte le richieste: configurazione dell'applicazione, pool di connessioni al database, modelli compilati o utilità condivise. Non memorizzare mai a livello globale dati specifici di una richiesta o di un utente. Se è necessario memorizzare dati nella cache a livello globale, assicurarsi che siano indicizzati correttamente e che l'accesso sia thread-safe, oppure utilizzare soluzioni di caching adeguate come Redis.

E le variabili a livello di modulo che non sono esplicitamente globali?

Le variabili a livello di modulo (const, let, var nell'ambito del file) si comportano esattamente come le variabili globali in Node.js. Permangono per tutte le richieste e sono condivise da tutti i gestori di richieste concorrenti. Si applicano gli stessi rischi di fuga di dati e di condizioni di competizione. Trattare le variabili a livello di modulo con la stessa cautela riservata alle variabili globali esplicite.

Come posso condividere i dati tra il middleware e gli handler delle route?

Utilizza le proprietà dell'oggetto richiesta fornite dal tuo framework. Express mette a disposizione req.locals o proprietà personalizzate su req. Fastify dispone invece di request.decorateRequest(). Questi oggetti hanno ambito limitato alla richiesta e vengono automaticamente liberati al termine della stessa, evitando così perdite di memoria tra una richiesta e l'altra.

E per quanto riguarda i pattern singleton e le istanze di classe?

Le istanze singleton a livello di modulo costituiscono uno stato globale. Se contengono dati specifici della richiesta, si verificano gli stessi problemi. Progettate i singleton in modo che siano privi di stato o contengano solo informazioni di configurazione. Per le operazioni che comportano uno stato, create nuove istanze per ogni richiesta oppure utilizzate modelli factory che garantiscano l’isolamento.

Come posso individuare questi problemi durante la fase di sviluppo?

Eseguire test di carico con richieste simultanee utilizzando contesti utente diversi. Le condizioni di competizione e le fughe di dati spesso non emergono nei test sequenziali. Utilizzare strumenti come Apache Bench o autocannon per generare un carico simultaneo. Aggiungere una registrazione che includa gli ID delle richieste per monitorare quando i dati di una richiesta compaiono in un’altra.

Questo vale anche per le funzioni serverless come AWS Lambda?

In parte. Ogni invocazione di Lambda dispone di un ambiente di esecuzione nuovo, ma il container può essere riutilizzato tra una invocazione e l'altra. Le variabili globali permangono tra le invocazioni che riutilizzano lo stesso container. Non dare per scontato che le variabili globali vengano azzerate. Segui le stesse pratiche: mantieni i dati della richiesta nell'ambito locale.

E le applicazioni Python WSGI/ASGI?

Valgono gli stessi principi. I server web Python funzionano in modalità multithread o asincrona, quindi le variabili a livello di modulo sono condivise tra le richieste. L’oggetto `g` di Flask e l’iniezione di dipendenze di FastAPI consentono di memorizzare i dati nell’ambito della singola richiesta. Django dispone di oggetti `request`. Per i dati relativi alle richieste, è consigliabile utilizzare i meccanismi forniti dal framework anziché le variabili globali del modulo.

Metti in sicurezza ora

Metti in sicurezza il tuo codice, il cloud e il runtime in un unico sistema centralizzato.
Trova e risolvi le vulnerabilità rapidamente e automaticamente.

Nessuna carta di credito richiesta | Risultati della scansione in 32 secondi.