Sicurezza

Perché un altro sito può leggere la risposta del tuo sito

Una pagina di un altro sito può chiedere una risposta al tuo sito. Il browser le consegna il corpo solo se la tua risposta lo dice.

Marco Conte 12 min di lettura Aggiornato il:

Perché un altro sito può leggere la risposta del tuo sito
In questa guida (9)
  1. 1La chiamata e la lettura non sono la stessa cosa
  2. 2Senza la riga la pagina non legge
  3. 3Asterisco, origine e credenziali
  4. 4La parola null
  5. 5Quando parte la richiesta di controllo
  6. 6Quali righe della risposta la pagina vede
  7. 7Esempio ipotetico: la vetrina chiede gli ordini
  8. 8La cache e la riga Vary
  9. 9Che cosa questa pagina lascia stare

Una pagina di un altro sito può chiedere una risposta al tuo sito, per esempio l'elenco degli ordini. Il browser consegna il corpo di quella risposta alla pagina solo se la tua risposta lo dice. La riga che lo dice si chiama Access-Control-Allow-Origin. Il protocollo che la usa si chiama CORS ed è nel Fetch Living Standard. Il 2 novembre 2026 ho letto Fetch, ultimo aggiornamento 19 ottobre 2026, e la pagina MDN di quell'header, ultima modifica 21 novembre 2025. Non ho misurato un browser e non ho letto il log di un server.

Questa pagina non descrive il banner dei cookie. Mostrare una foto è un altro controllo, già scritto nell'articolo un altro sito può mostrare la foto del tuo sito. Non lo riscrivo. La pagina sul clic che porta l'indirizzo non la riapro.

Schema: senza la riga la pagina non legge, senza credenziali basta l'asterisco, con credenziali servono l'origine e true

La chiamata e la lettura non sono la stessa cosa

Fetch, sezione 2.2.5, dà a una richiesta un mode. Se non è detto altrimenti, il mode è no-cors e il response tainting è basic. Il mode cors rende la richiesta una richiesta CORS: se la risorsa non partecipa al protocollo, fetch restituisce un network error. Il mode no-cors limita metodi e header a quelli safelisted e, se la richiesta riesce, restituisce una risposta filtrata opaca. Il mode same-origin serve a restare sulla stessa origine: fetch restituisce un network error se l'indirizzo non è della stessa origine.

Nel main fetch, sezione 4.1, il primo ramo che corrisponde vince. Se l'origine dell'indirizzo è la stessa della richiesta e il tainting è basic, oppure lo schema è data, oppure il mode è navigate, websocket o webtransport, il tainting diventa basic e si continua con scheme-fetch. In quel ramo il controllo CORS non parte. Una pagina del tuo sito che legge una risposta del tuo sito, con il tainting di base, è in questo ramo.

Se quel ramo non vale e il mode è same-origin, l'algoritmo restituisce un network error e non chiama scheme-fetch. È il caso di un indirizzo di un'altra origine. Se il mode è no-cors e il redirect mode non è follow, restituisce un network error. Altrimenti, per no-cors, imposta il tainting a opaque e continua con scheme-fetch. Non ho letto tutto scheme-fetch, quindi non dico che un browser abbia già mandato il pacchetto. Dico l'ordine scritto.

Una risposta filtrata opaca, sezione 2.2.6, ha tipo opaque, lista di indirizzi vuota, status 0, messaggio di status vuoto, lista di header vuota e body null. La stessa sezione dice che, per uno script, è quasi indistinguibile da un network error. Il server può comunque aver visto la richiesta. Non l'ho misurato.

Se lo schema non è HTTP né HTTPS, e i rami precedenti non hanno già risposto, il main fetch restituisce un network error. Se lo schema è HTTP o HTTPS, il preflight scatta in due casi: il flag use-CORS-preflight è acceso, oppure il flag unsafe-request è acceso e succede una di queste due cose, il metodo non è safelisted oppure la lista dei nomi di header non safelisted non è vuota. In quei casi il tainting diventa cors e si chiama HTTP fetch con il preflight. Altrimenti il tainting diventa cors e si chiama HTTP fetch senza quel flag.

Nella HTTP fetch, sezione 4.4, il controllo CORS arriva dopo HTTP-network-or-cache fetch. Se il tainting è cors e il controllo fallisce, l'algoritmo restituisce un network error alla pagina. La richiesta di rete o di cache è già stata fatta. La sezione 3.3.3 avvisa che il lavoro del server può comunque uscire da canali laterali, per esempio i tempi. Uno status 403 insieme all'assenza degli header può segnalare il rifiuto. Se invece gli header corrispondono, anche uno status 403 su una richiesta che non è il preflight è una risposta condivisa: la specifica dice che condividerla la rende una risposta riuscita. Il preflight, per riuscire, deve avere uno status ok. Gli esempi scritti sono 200 e 204. Non ho misurato i tempi.

Senza la riga la pagina non legge

Il controllo CORS, sezione 4.10, legge Access-Control-Allow-Origin. Se la lettura non trova la riga, il risultato è null e il controllo fallisce. La nota dice che quel null non è la parola null di quattro lettere. Non ho aperto che cosa fa la lettura quando la risposta porta due copie della stessa riga.

La sezione 3.3.3 dice che l'header indica se la risposta può essere condivisa, restituendo il valore letterale dell'header di richiesta Origin, che può essere la parola null, oppure l'asterisco. Nell'ABNF della sezione 3.2 il valore è uno solo: un'origine serializzata, la parola null, o l'asterisco. Non è un elenco. L'origine serializzata è lo schema, poi due punti e due barre, poi l'host, e la porta solo se c'è. Non ha un percorso e non ha la barra finale. Schema e dominio sono in minuscolo. L'header Origin è una versione di Referer che non rivela il percorso. Parte per i fetch HTTP il cui tainting è cors, e anche quando il metodo non è GET né HEAD. Non parte in tutti i fetch. Una richiesta CORS, sezione 3.3.2, è una richiesta HTTP che include Origin, ma non si riconosce in modo affidabile, perché lo stesso header parte anche per i metodi che non sono GET né HEAD.

Asterisco, origine e credenziali

Il controllo, sempre sezione 4.10, prosegue così. Se il credentials mode non è include e il valore è l'asterisco, il controllo riesce. Altrimenti confronta il valore, byte per byte, con l'origine della richiesta serializzata. Se non coincidono, fallisce, e non arriva a leggere Access-Control-Allow-Credentials. Se coincidono e il mode non è include, riesce, e anche in quel caso non legge quell'header. Se il mode è include, lo legge: riesce solo se il valore è esattamente true, tutto minuscolo. True fallisce. Se l'header manca, fallisce.

La tabella della sezione 3.3.5 illustra le combinazioni per una richiesta a https://rabbit.invalid/. È la tabella della specifica, non una misura mia. Con mode omit e valore asterisco, la risposta è condivisa, anche se accanto c'è true: se il mode non è include, Allow-Credentials è ignorato. Con mode omit e valore https://rabbit.invalid/, con la barra finale, non è condivisa: l'origine serializzata non ha la barra. Senza la barra, è condivisa. Con mode include, asterisco e true, non è condivisa. Con mode include, origine senza barra e true, è condivisa. Con True maiuscolo, non è condivisa.

MDN, nella direttiva dell'asterisco, dice che per le richieste senza credenziali si può usare il valore letterale asterisco, e che usarlo con le credenziali produce un errore. Non distingue il caso in cui l'header Allow-Credentials c'è ma il mode non è include. Fetch, nella tabella, quel caso lo segna come condiviso. Le due pagine non dicono la stessa cosa. Non le ho unite. MDN dice anche che un server con più origini deve restituire l'origine del client che ha fatto la richiesta. L'ABNF di Fetch è un solo valore, e il controllo lo confronta per intero: un elenco non coincide con un'origine serializzata.

La sezione 3.3.5 aggiunge che il credentials mode non è sempre osservabile sul server. Lo si vede solo quando le credenziali ci sono e quindi partono. Il preflight non include mai le credenziali. La stessa sezione dice che condividere risposte e permettere richieste con credenziali è piuttosto rischioso, e che serve estrema cura per evitare il problema del confused deputy. Non descrivo quel problema oltre il nome.

Nella sezione sul setup di base, Fetch dice che l'asterisco da solo è adatto quando la risorsa si può già leggere da una macchina qualsiasi sul web, senza passare dal browser. Se la risorsa è protetta dall'indirizzo di rete o da un firewall, quella riga non va messa: è il motivo per cui il protocollo è opt-in. MDN dice che l'asterisco permette al codice di qualunque origine di accedere alla risorsa, e ripete il limite delle richieste senza credenziali. Anche qui non ho fuso le due frasi.

La parola null

Nell'ABNF della sezione 3.2, origin-or-null è un'origine serializzata oppure la parola null, sensibile alle maiuscole. Il controllo la accetta solo se coincide, byte per byte, con l'origine serializzata della richiesta. Le origini opache si serializzano con quelle quattro lettere, quindi la parola non è legata a una pagina sola. MDN dice di non usare quel valore. Spiega che i documenti in sandbox e le risorse con uno schema non gerarchico, come data o file, si serializzano così, e che qualunque origine può creare un documento con quell'origine. Fetch, sezione 3.3.3, dice che il valore può essere il letterale di Origin, compresa la parola null, e nel controllo non aggiunge il divieto. Non do i passi per costruire quel documento. Il valore non lo consiglio.

Quando parte la richiesta di controllo

Per le richieste più ricche di quelle di un form HTML, la sezione 3.3.1 fa partire una richiesta di preflight, per vedere se l'indirizzo capisce il protocollo. È una richiesta CORS con metodo OPTIONS e con l'header Access-Control-Request-Method. Può portare anche Access-Control-Request-Headers. Il suo credentials mode è sempre same-origin: le credenziali non partono. Il controllo CORS sulla sua risposta, sezione 4.8, si fa però sulla richiesta originale, così si usa il mode giusto, e lo status deve essere ok. L'header Allow non conta per CORS.

fetch() e XMLHttpRequest accendono il flag unsafe-request, sezione 2.2.5. Il flag use-CORS-preflight si accende se c'è un listener sull'oggetto di upload di XMLHttpRequest, o se il body è un ReadableStream. I metodi safelisted, sezione 2.2.1, sono GET, HEAD e POST. I metodi CONNECT, TRACE e TRACK sono forbidden. Non ho misurato un preflight.

Un header di richiesta è safelisted, sezione 2.2.2, solo per pochi nomi, e solo se il valore sta nei limiti. Il valore più lungo di 128 byte non lo è. Per il tipo del contenuto, dopo il parse, l'essenza deve essere una delle tre ammesse: la codifica urlencoded dei form, il form multiparte, o il testo semplice. Un tipo JSON non è fra le tre. Se il valore contiene un byte non sicuro, l'header non è safelisted prima ancora del parse. La stessa sezione dice di non usare l'algoritmo più permissivo che estrae il tipo, perché un parser ingenuo sul server potrebbe leggere il corpo in un modo e il browser in un altro. Non copio l'esempio. La sezione 3.3.7 aggiunge quattro eccezioni, per rapporti e per una richiesta sullo stato di un certificato, che il contenuto web può far partire senza preflight. Chiede di non aggiungere eccezioni nuove alla leggera.

Se la somma dei valori safelisted supera 1024, anche quei nomi finiscono tra i non safelisted. Il nome Authorization è un CORS non-wildcard request-header name: nel preflight deve comparire per nome in Access-Control-Allow-Headers, anche se il valore di quell'header è l'asterisco.

Sulla risposta del preflight, sezione 4.8, se il metodo della richiesta non è nell'elenco di Access-Control-Allow-Methods, non è un metodo safelisted, e il mode è include oppure l'elenco non contiene l'asterisco, il risultato è un network error. Per ogni nome di header non safelisted vale la stessa regola su Access-Control-Allow-Headers. Senza credenziali, l'asterisco in quegli header copre i nomi. Con include, l'asterisco non copre. La sezione 3.3.4 dice che, per Access-Control-Expose-Headers, Access-Control-Allow-Methods e Access-Control-Allow-Headers, non c'è modo di far corrispondere solo un nome che sia esso stesso un asterisco. Access-Control-Max-Age dice per quanti secondi si possono tenere in cache i metodi e gli header ammessi. Se l'estrazione fallisce o è null, l'algoritmo mette 5. La sezione 3.3.3 dice lo stesso default. Se il numero supera un limite imposto dal browser, viene abbassato. Il passo non scrive il numero del limite. Non l'ho inventato.

Quali righe della risposta la pagina vede

Dopo un controllo riuscito, la pagina non vede tutti gli header. Una risposta filtrata CORS, sezione 2.2.6, tiene solo i nomi safelisted della risposta, più quelli nella lista esposta. I nomi safelisted, sezione 2.2.2, sono Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified e Pragma, più i nomi elencati che non sono forbidden. I forbidden response-header name sono Set-Cookie e Set-Cookie2. Access-Control-Expose-Headers è la lista dei nomi in più. Nella HTTP fetch, se il mode non è include e la lista contiene l'asterisco, la lista esposta diventa tutti i nomi unici della risposta. Altrimenti, se la lista c'è, si usano quei nomi. Un asterisco rimasto nella lista corrisponde solo a un header che si chiama asterisco.

Esempio ipotetico: la vetrina chiede gli ordini

I nomi bottega.example e vetrina.example sono di scuola. Non ho letto una loro risposta.

Esempio ipotetico: la vetrina non legge senza la riga, legge con l'origine e true, non legge con l'asterisco e le credenziali

La pagina è https://vetrina.example/scheda. Chiede https://bottega.example/ordini in mode cors. Se la risposta non porta Access-Control-Allow-Origin, il controllo della sezione 4.10 fallisce e la scheda non riceve il corpo. La HTTP fetch ha già fatto la richiesta di rete o di cache. Non ho un log della bottega.

Se la risposta porta https://vetrina.example e, con credentials mode include, Access-Control-Allow-Credentials: true, la tabella della sezione 3.3.5 segna la risposta come condivisa. Se il valore è https://vetrina.example/, con la barra, il confronto dei byte fallisce. Se il valore è l'asterisco e il mode è include, fallisce anche con true. Se il mode è omit e il valore è l'asterisco, riesce, e un eventuale true viene ignorato. È lo scenario delle pagine lette, non una misura.

La cache e la riga Vary

MDN dice che, se il valore è un'origine esplicita e non l'asterisco, la risposta dovrebbe portare anche Vary: Origin, per dire che le risposte possono cambiare in base a Origin.

Fetch, nella sezione CORS protocol and HTTP caches, dice un'altra cosa. Vary va usato se le regole sono più complicate di un asterisco o di un'origine statica. Se il valore è l'asterisco oppure un'origine statica, la stessa sezione dice di mandare sempre Access-Control-Allow-Origin, anche sulle richieste che non sono CORS, e di non usare Vary. Spiega il caso in cui la riga manca sulla risposta a una navigazione, quella risposta finisce in cache, e la richiesta CORS successiva riusa la copia senza la riga. Con Vary: Origin, in quello scenario, il browser va a prendere una risposta che la riga ce l'ha. MDN non scrive l'eccezione dell'origine statica. Non ho corretto una pagina con l'altra.

Che cosa questa pagina lascia stare

Il controllo Cross-Origin-Resource-Policy è un altro passo della HTTP fetch, e vale anche per le risposte di un service worker. Il controllo CORS, nella nota della sezione 4.4, non si applica alle risposte 304 e 407, né alle risposte di un service worker: si applica dopo HTTP-network-or-cache fetch. Non ho riscritto la pagina sulla foto.

Non ho aperto l'algoritmo che legge un header quando la risposta ne porta due copie. Non ho misurato un browser. Non ho un numero per il tetto di Max-Age. La riga Accept del preflight non la descrivo.

Fonte: Fetch, Living Standard, ultimo aggiornamento 19 ottobre 2026, sezioni 2.2.1, 2.2.2, 2.2.5, 2.2.6, 3.2, 3.3, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5, 3.3.7, 4.1, 4.4, 4.8 e 4.10, e la sezione CORS protocol and HTTP caches, lette il 2 novembre 2026. Pagina MDN Access-Control-Allow-Origin, ultima modifica 21 novembre 2025, letta lo stesso giorno. Non ho misurato un browser.

I due schemi nel testo sono disegni di questa pagina, non fotografie. La copertina non è stata cambiata.

Potrebbe interessarti anche...

Hai bisogno di scegliere una webagency per il tuo progetto?

Abbiamo la soluzione giusta

Parliamone
  • Consulenza specializzata
  • Soluzioni su misura
  • Risultati concreti

Tecnologialiberaper tutti

Hai bisogno d'aiuto?

Parla con noi. Il nostro supporto è attivo tutti i giorni dalle 9 alle 18

+390835239514 Chiamaci

Crea il tuo eCommerce

Creiamo eCommerce su misura senza farti spendere un patrimonio

info@roxxoitalia.com Scopri il nostro CMS

Crea il tuo sito web

Siti web già pronti per andare online. Vuoi saperne di più? Contattaci

info@roxxoitalia.com Scrivici