Le moderne applicazioni web basate su Next.js e Node.js elaborano sempre più dati sensibili – chiavi API, token JWT, credenziali di database. Una gestione inadeguata di questi segreti porta a perdite che possono costare all'organizzazione milioni di euro. In questo articolo presentiamo un modello di minaccia completo, descriviamo il funzionamento di HashiCorp Vault e mostriamo frammenti di codice pratici – dalla versione vulnerabile a quella corretta – oltre a una checklist di mitigazione basata su OWASP, CWE e sul principio del least‑privilege.
Modello di minaccia – dove si presenta il rischio?
Il tipico flusso di un segreto in un progetto Next.js/Node.js è il seguente: lo sviluppatore salva il valore in un file .env, il repository CI/CD preleva il file e lo inietta nel container Docker, e l'applicazione legge la variabile d'ambiente al momento dell'esecuzione. In questo modello esistono tre punti critici di attacco:
- memorizzazione del segreto nel repository (anche in un repository privato può essere clonato o trapelare);
- trasmissione del segreto come stringa non crittografata nelle variabili d'ambiente del container;
- assenza di limitazioni di accesso dell'applicazione al segreto specifico (mancanza del principio least‑privilege).
Tutti questi scenari sono descritti nell'OWASP Top 10 come Security Misconfiguration (A05) e nel CWE‑522 – Insufficiently Protected Credentials. Il modo migliore per ridurre il rischio è la gestione centralizzata dei segreti, che garantisce audit, rotazione e ambito minimo di autorizzazioni.
HashiCorp Vault – come funziona?
Vault è un sistema di gestione dei segreti che conserva i valori crittografati in un backend sicuro (ad esempio uno storage basato su consenso). L'accesso ai segreti avviene dopo l'autenticazione tramite metodi come AppRole, Kubernetes Auth o certificati TLS. Dopo un'autenticazione riuscita, Vault rilascia un token a breve durata (TTL da pochi secondi a diverse ore) e consente la lettura o la generazione di credenziali dinamiche (ad esempio utenti temporanei di database).
Caratteristiche chiave che soddisfano i requisiti OWASP e CWE:
- encryption‑at‑rest e encryption‑in‑transit (TLS);
- controllo rigoroso delle policy (policy‑as‑code) che definisce quali percorsi e operazioni sono consentiti;
- rotazione automatica e revoca dei token.
Integrazione di HashiCorp Vault con Node.js – esempio di codice
Di seguito è riportato un frammento di codice semplice ma vulnerabile, in cui i segreti vengono letti dal file .env e usati direttamente nelle chiamate API:
require('dotenv').config();
const axios = require('axios');
const apiKey = process.env.API_KEY; // vulnerabile: chiave in .env
async function callExternal() {
const resp = await axios.get('https://api.example.com/data', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
return resp.data;
}
Il problema è che API_KEY può finire nei log CI, nell'immagine Docker o nella memoria del container, senza alcun controllo su quali processi possano leggerla.
La versione corretta utilizza il Vault SDK, si autentica tramite AppRole e recupera il segreto in tempo reale. In questo modo il token ha un TTL breve e la chiave non rimane in memoria più a lungo del necessario:
const vault = require('node-vault')({
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_APPROLE_TOKEN // token breve, non permanente
});
async function getApiKey() {
const result = await vault.read('secret/data/app/api-key');
return result.data.data.key;
}
async function callExternal() {
const apiKey = await getApiKey();
const resp = await axios.get('https://api.example.com/data', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
return resp.data;
}
È importante notare che il Vault SDK gestisce automaticamente il refresh del token e la policy app/api-key può limitare l'accesso esclusivamente alla lettura di questa chiave specifica.
Docker e Kubernetes – archiviazione sicura dei token JWT
Negli ambienti containerizzati si usano spesso variabili d'ambiente per passare i token. In alternativa è possibile utilizzare:
- Vault Agent Injector – inietta automaticamente il segreto come file in
/vault/secretsnel container; - Container sidecar con Vault Agent, che rinnova il token in background.
Esempio di manifesto Kubernetes con sidecar:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextjs-app
spec:
replicas: 2
template:
spec:
containers:
- name: app
image: myregistry/nextjs:latest
env:
- name: VAULT_ADDR
value: "https://vault.mycompany.com"
volumeMounts:
- name: vault-secrets
mountPath: /vault/secrets
- name: vault-agent
image: hashicorp/vault-agent:latest
args:
- "agent"
- "-config=/etc/vault/agent-config.hcl"
volumeMounts:
- name: vault-config
mountPath: /etc/vault
- name: vault-secrets
mountPath: /vault/secrets
volumes:
- name: vault-config
configMap:
name: vault-agent-config
- name: vault-secrets
emptyDir: {}
Il sidecar salva il token aggiornato in /vault/secrets/jwt, e l'applicazione lo legge all'avvio. In questo modo il token non appare nelle variabili d'ambiente ed è automaticamente revocato al momento della scadenza.
Checklist di mitigazione – da OWASP al principio del minimo privilegio
- Usa un archivio centrale (Vault) invece di file
.envnel repository. - Applica TTL brevi per i token e rotazione automatica.
- Definisci le policy di Vault in stile
path "secret/data/app/*" { capabilities = ["read"] }– limita l'accesso a uno specifico namespace. - In Docker e Kubernetes inietta i segreti come file o volume, non come env.
- Esegui audit degli accessi – abilita il device di audit in Vault e integra i log con il SIEM.
- Verifica i privilegi dell'applicazione (least‑privilege) – l'app dovrebbe avere accesso solo ai segreti necessari.
Errori comuni e compromessi
Even experienced teams commettono alcune trappole:
- Hard‑coding token – inserire token statici nel codice sorgente sotto la scusa di un prototipo rapido; soluzione: usa sempre AppRole o Kubernetes Auth.
- Impostare policy troppo ampie – ad es.
path "secret/*" { capabilities = ["read"] }concede accesso a tutti i segreti; soluzione: suddividi i segreti per domini e assegna i privilegi minimi. - Mancanza di rotazione – mantenere chiavi API immutate per anni; soluzione: configura
max_ttlerotation_periodin Vault. - Log dei segreti – stampare involontariamente valori nei log dell'applicazione; soluzione: usa un logger che mascheri i campi con nomi
*secret*,*key*,*token*.
La sicurezza non è una configurazione una tantum, è un processo continuo di controllo, audit e riduzione dei privilegi.
Esempio pratico – workflow completo
1. Configurazione di Vault: crea il namespace app, abilita il device di audit, definisci la policy app-read con accesso a secret/data/app/*.
2. Creazione di AppRole: role_id e secret_id sono conservati in un Kubernetes Secret (non nel repository).
3. Deploy dell'applicazione: manifest con sidecar Vault Agent, come nell'esempio sopra.
4. In Next.js (pages/api/data.ts):
import { createClient } from '@next-auth/vault';
export default async function handler(req, res) {
const client = createClient({
endpoint: process.env.VAULT_ADDR,
roleId: process.env.VAULT_ROLE_ID,
secretId: process.env.VAULT_SECRET_ID
});
const { data } = await client.read('secret/data/app/jwt');
const jwt = data.data.token;
// utilizzo del token per l'autorizzazione al servizio esterno
res.status(200).json({ status: 'ok' });
}
5. Test di sicurezza: esegui una scansione OWASP ZAP, verifica che non vi siano perdite di variabili d'ambiente nei log del container.
Dopo aver implementato il workflow sopra, il team di ingegneria ottiene:
- un punto centrale per gestire tutti i segreti;
- la possibilità di ruotare immediatamente in caso di incidente;
- un controllo di audit completo conforme a OWASP e CWE.
Se desideri che i tuoi progetti Next.js e Node.js siano resistenti alle perdite di dati, contatta Coderia.it – ti aiuteremo a implementare Vault, a definire le policy e a condurre un audit di sicurezza.



