Il banner di consenso di FAZ Cookie Manager 1.28.0

Contenuto

Due segnalazioni, una cache che non si riempiva mai

Tutto è cominciato con due utenti che, a poche ore di distanza, hanno scritto la stessa cosa con parole diverse. Uno usava LiteSpeed, l’altro FlyingPress. Entrambi avevano notato che dall’aggiornamento del plugin la cache di pagina aveva smesso di funzionare: il server rispondeva sempre con Cache-Control: no-store, no-cache, e ogni visita ripartiva da zero. Uno dei due aveva perfino trovato il rimedio da sé, aggiungendo una riga di PHP al proprio sito, e chiedeva la cosa più ragionevole del mondo: che spegnere il Geo-Targeting dall’interfaccia bastasse, senza dover scrivere codice.

Aveva ragione, e il motivo era imbarazzante nella sua semplicità. Il runtime che applica le regole giurisdizionali — quello che decide se un visitatore va trattato secondo il GDPR, secondo una legge statunitense di opt-out o secondo una delle altre quarantasette normative che il plugin conosce — era acceso in modo incondizionato. L’interruttore nell’interfaccia c’era, si poteva spegnere, si salvava. Semplicemente non governava nulla. L’unico modo di fermarlo era un filtro PHP che nessun utente normale ha motivo di conoscere.

Un controllo che si può muovere e che non fa niente è peggio di un controllo assente: quello assente almeno è onesto.

La correzione che stava per rompere tutto il resto

Rendere autorevole quell’interruttore è stato semplice: poche righe, e la cache è tornata a riempirsi. Ma proprio quella semplicità nascondeva il problema serio, e l’ho trovato solo mettendomi a leggere cosa succede a un sito che aggiorna invece che a uno appena installato.

L’impostazione Geo-Targeting nasce spenta. Nelle versioni precedenti questo non contava, perché l’applicazione delle regole era incondizionata e girava comunque. Rendendo l’interruttore autorevole, però, ogni installazione che non l’aveva mai spuntato — cioè la maggioranza, essendo quello il valore predefinito — avrebbe smesso di applicare le regole giurisdizionali. In silenzio, senza un avviso, semplicemente aggiornando.

Nella maggior parte dei casi il risultato sarebbe stato un comportamento più severo, quindi innocuo. Ma su un sito il cui banner è configurato per una legge di opt-out — la modalità americana, o quella combinata “GDPR più Stati Uniti” — la conseguenza era che nulla veniva più bloccato prima del consenso. Nemmeno per un visitatore europeo. Il codice, non trovando una regola per paese, ricadeva sul modello dichiarato nel banner, e un banner di opt-out per definizione non blocca in anticipo.

Una correzione che risolve il problema segnalato e ne introduce uno più grave che nessuno segnalerà, perché è invisibile, è il modo peggiore di chiudere un ticket.

La risposta è stata una migrazione che gira una volta sola, al primo caricamento dopo l’aggiornamento: accende l’interruttore per chi arriva da una versione precedente, così l’applicazione delle regole resta esattamente com’era, e spiega in un avviso cosa è cambiato e come spegnerla se davvero serve la cache. Chi installa il plugin oggi, invece, parte con l’applicazione attiva — perché il caso peggiore dell’alternativa era un sito europeo che non blocca nulla senza che il suo amministratore lo sappia.

Conformità o cache: un compromesso che non doveva esistere

Restava però la domanda vera: perché mai un sito dovrebbe scegliere fra rispettare le normative e avere una cache funzionante? Le due cose non sono in conflitto per natura. Lo diventano solo se la pagina servita cambia a seconda del paese di chi la richiede.

Nel plugin esisteva già, da qualche versione, un meccanismo pensato esattamente per questo: servire a tutti un unico guscio rigoroso, identico e quindi memorizzabile in cache, e risolvere la giurisdizione reale nel browser prima che il banner venga mostrato e prima che qualunque risorsa opzionale venga liberata. Funzionava, era coperto da test — ed era raggiungibile solo scrivendo PHP, perché nessuno gli aveva mai dato un interruttore.

In questa versione ce l’ha. Se la procedura guidata rileva un plugin di cache, lo propone già attivo. Quando non può funzionare — con IAB TCF, con AMP, con banner assegnati a paesi specifici, con il fallback della lingua derivato dal paese — non prova ad arrangiarsi: mantiene l’applicazione delle regole e rinuncia alla cache, dicendo nell’interfaccia quale configurazione lo sta impedendo. Il fallimento è sempre nella direzione conservativa.

Una domanda a cinque minuti dalla pubblicazione

Il pacchetto era pronto, i controlli erano verdi, mancava solo premere il tasto. Poi è arrivata una domanda apparentemente innocua: ma il geo-targeting ha bisogno di elenchi esterni?

La risposta è no, e verificarla ha richiesto dieci minuti. La catena di rilevamento è tutta locale: l’header di Cloudflare quando il sito ci sta dietro, un modulo GeoIP del server se c’è, e il database MaxMind GeoLite2 che si scarica una volta e poi si interroga in locale. Nessuna chiamata a servizi terzi, nessun indirizzo IP di visitatore che lascia il server.

Solo che il file readme.txt del plugin dichiarava il contrario. Sei volte, con un paragrafo dedicato che descriveva l’invio dell’IP del visitatore a un servizio esterno di geolocalizzazione. Quella chiamata non esiste nel codice: ne erano rimasti due commenti, testimoni di un’implementazione tolta tempo fa. Per un plugin che si occupa di privacy, una sezione “servizi esterni” che sovrastima quello che esce dal server è il tipo sbagliato di errore — ed è la sezione che i revisori di WordPress.org leggono per prima.

La stessa domanda ne ha fatto emergere un secondo, più concreto. Il veto alla cache scattava ogni volta che l’applicazione delle regole era accesa, punto. Ma su un sito che non ha alcun modo di determinare il paese di un visitatore — niente Cloudflare, niente modulo GeoIP, niente database — la pagina è identica per tutti: non c’è nessuna variazione da proteggere. Quel sito perdeva la cache per una differenza che non poteva verificarsi. Con l’applicazione delle regole ora attiva di default, sarebbe successo sulla maggior parte degli hosting condivisi: la versione che chiude due segnalazioni sulla cache le avrebbe ricreate con la propria impostazione predefinita.

Cosa ho imparato guardando i test

La parte che mi porto dietro di questo rilascio non riguarda il codice, ma i controlli che avrebbero dovuto sorvegliarlo. Ne ho trovati cinque incapaci di fare il proprio lavoro, e nessuno di loro era rosso: erano tutti verdi, o silenziosi.

Il test che protegge il checkout di WooCommerce verificava che uno script necessario non venisse bloccato, ma mai che uno script dal nome somigliante restasse bloccato. Così un’esenzione documentata come strettissima ha potuto allargarsi senza che nulla diventasse rosso. Gli undici test dell’integrazione con FlyingPress si autoescludevano nella modalità che uso come cancello di rilascio, per un dettaglio su come Playwright conta i propri worker: passavano in locale e non giravano mai dove contava. Un test di unità fissava nel proprio fixture uno stato che in produzione, su quelle pagine, non si verifica. Il controllo su WordPress Playground — quello obbligatorio, l’ultimo prima della pubblicazione — installava il plugin per nome dalla directory, quindi scaricava sempre la versione precedente: era strutturalmente incapace di dire qualcosa su ciò che stavo per rilasciare.

E lo script di rilascio stesso non riusciva a leggere il proprio changelog, per una sottigliezza su come awk interpreta le parentesi quadre quando una stringa viene compilata come espressione regolare.

Verde non significa verificato. Significa che nessuno ha ancora posto la domanda giusta.

Sono tutti corretti, e la correzione più utile non è stata sistemare i singoli casi ma cambiare il modo di provarli: per ogni asserzione nuova ho rimesso il difetto e ho controllato che il test diventasse rosso, poi l’ho tolto verificando che il file tornasse identico byte per byte. Un test che resta verde quando il bug c’è non è un test debole: è un test che non esiste, e occupa il posto di quello che servirebbe.

In sintesi

Se aggiorni da una versione precedente non devi fare nulla: l’applicazione delle regole giurisdizionali che avevi resta accesa, e un avviso ti spiega cosa è cambiato. Se la tua priorità è la cache di pagina, ora puoi spegnere il Geo-Targeting dall’interfaccia — senza scrivere PHP — oppure attivare il bootstrap compatibile con la cache e tenerti entrambe le cose.

E se il tuo sito non ha modo di determinare il paese dei visitatori, la cache non viene più sacrificata per una distinzione che il tuo server non è in grado di fare.