# Spuncin, decisioni tecniche

Stato: aggiornato il 6 agosto 2026, dopo la prima stesura del plugin e due audit multi-agente.
Da rivedere con Niccolò.

Cosa fa il prodotto oggi: riconosce i crawler AI e li blocca. Niente blockchain, niente pagamenti,
niente identità verificabile. Quella roba resta nel repo `andromeda` per un eventuale seguito.

---

## 0. Cosa è cambiato rispetto alla prima bozza

Tre cose emerse dagli audit hanno cambiato il progetto, non solo il codice.

**Il blocco via User-Agent da solo non basta, e non per una questione di qualità del codice.**
Alcuni dei permessi più utili non sono crawler e non hanno uno User-Agent: esistono solo come nome
da scrivere in robots.txt. Google dichiara che Google-Extended *"doesn't have a separate HTTP
request user agent string"*, Apple che Applebot-Extended *"does not crawl webpages itself"*.
Nessun matching, per quanto buono, può intercettarli. Il plugin scrive quindi anche robots.txt.
I due strati coprono casi diversi: robots.txt raggiunge chi non ha uno User-Agent, il 403 ferma
chi ignora robots.txt.

**Il match a sottostringa era un difetto grave.** `Spider` bloccava Baiduspider, Sogou e Screaming
Frog, `Code` bloccava Visual Studio Code, `ExaBot` bloccava Exabot di Exalead. La correzione ha
tre parti che servono tutte e tre: match su token delimitato, esclusione di 16 nomi troppo
generici, ordinamento per lunghezza decrescente perché `Applebot-Extended` vinca su `Applebot`.

**La lista di ai.robots.txt non è un elenco di bot da bloccare.** È un catalogo di crawler
AI-related, dentro cui convivono cose molto diverse. I 163 nomi sono stati classificati in quattro
categorie e il blocco predefinito riguarda solo l'addestramento, per decisione presa il 6 agosto:
tutto ciò che porta visite o è azionato da una persona passa.

---

## 1. Il perimetro della detection

La distinzione "IA o persona" non è un problema solo, sono due, con difficoltà molto diverse.

**Bot dichiarati.** GPTBot, ClaudeBot, PerplexityBot, CCBot, Bytespider e compagnia si annunciano
nello User-Agent e non si nascondono, perché hanno interesse a essere riconoscibili e a rispettare
robots.txt. Qui l'accuratezza è vicina al 100%: match sullo User-Agent più verifica dell'IP.

**Scraper mascherati.** Un Playwright con UA di Chrome e IP residenziale. Qui la tecnica che
funziona davvero nel 2026 è il fingerprinting TLS (JA4), che guarda l'handshake. Un plugin PHP non
ci arriva: quando PHP entra in gioco l'handshake è chiuso da un pezzo. Serve un modulo del web
server o un reverse proxy davanti.

Quello che resta a portata di un plugin sul secondo caso: presenza e ordine degli header,
comportamento (chi prende l'HTML e non tocca mai CSS, immagini, font), rate per IP, honeypot link
invisibili nel DOM.

**Decisione.** La prima release copre bene il primo caso e alza il costo sul secondo. Il claim
pubblico è "blocchiamo i crawler AI dichiarati", non "blocchiamo tutte le IA". Il secondo claim
sarebbe smentibile in mezza giornata da chiunque, e su wordpress.org le recensioni a una stella
arrivano in fretta.

---

## 2. Le quattro categorie e i tre livelli

I 163 nomi del catalogo sono classificati in quattro categorie. La classificazione è nostra, non
di ai.robots.txt, ed è stata fatta con revisione adversariale separata su chi blocca troppo e su
chi blocca troppo poco.

- **training** (60). Raccoglie contenuti per addestrare modelli, costruire dataset o rivendere
  dati. Il sito non riceve niente in cambio.
- **search** (56). Alimenta un motore di ricerca o un assistente che può mandare visite o
  citazioni. Bloccarlo costa visibilità.
- **on_demand_user** (31). Non è un crawler: è una persona che ha chiesto a un assistente di
  aprire quella pagina adesso.
- **ambiguous** (16). Il nome è troppo generico per essere cercato dentro uno User-Agent senza
  colpire traffico che non c'entra. Non viene mai cercato, a nessun livello, qualunque cosa faccia
  il bot. Sono `Code`, `Spider`, `Cursor`, `Devin`, `ExaBot`, `LCC`, `newsai`, `OpenAI`,
  `opencode`, `Operator`, `Scrapy`, `Terra Cotta`, `TerraCotta`, `Trae`, `UseAI`, `YaK`.

I tre livelli sono cumulativi: 1 blocca training, 2 aggiunge search, 3 aggiunge on_demand_user.
Il default è 1. Ogni livello dichiara nell'interfaccia cosa costa, perché senza quella spiegazione
la gente sceglie il più severo per istinto.

Sopra ai livelli ci sono due liste di eccezioni per singolo crawler, una che forza il blocco e una
che forza il passaggio, entrambe filtrate contro il catalogo così che un'opzione manomessa non
possa iniettare stringhe arbitrarie nel matcher o in robots.txt.

**Mai bloccabili, a nessun livello e senza opzione per farlo:** Googlebot, bingbot, Applebot,
DuckDuckBot, YandexBot, Baiduspider, PetalBot, verificati via reverse DNS con conferma in avanti,
più i bot delle anteprime dei link (Facebook, Twitter, LinkedIn, Slack, WhatsApp, Telegram,
Discord, Reddit, Pinterest, Mastodon, Bluesky) che si fidano dello User-Agent perché non esiste
un metodo di verifica pubblicato e non sono crawler AI.

### Rimandato

Honeypot con link invisibile nel DOM, e segnali comportamentali tipo rate per IP o richieste HTML
senza mai gli asset. Servono a intercettare gli scraper mascherati, che restano fuori portata di
questa release.

---

## 3. Niente chiamate a server esterni nella prima release

La Guideline 8 di wordpress.org vieta di *"usare servizi di terze parti per gestire liste di dati
aggiornate regolarmente, quando non esplicitamente permesso dai termini d'uso del servizio"*.
La Guideline 7 elenca fra i comportamenti vietati l'*"uso non documentato o documentato male di
dati esterni (come le blocklist)"*. La review checklist aggiunge che qualsiasi raccolta dati deve
essere opt-in e disattivata di default.

**Decisione.** La lista sta dentro il plugin. Si aggiorna quando rilasciamo una versione nuova.
Zero chiamate in uscita, zero telemetria, zero server nostro da mantenere.

Cosa ci fa guadagnare: la sezione privacy del readme diventa quella di Blackhole, che è tre righe e
passa la review senza discussioni. Nessuna disclosure di servizi esterni, nessun rischio che un
reviewer ci chieda terms of use e privacy policy di un servizio che non esiste. E se un giorno
vogliamo l'aggiornamento remoto, si aggiunge come opzione spenta di default con un fallback locale.

Il costo: la lista invecchia fra una release e l'altra. Considerato che i nuovi bot rilevanti
escono due o tre volte l'anno, e che rilasceremo comunque più spesso, è un non problema.

---

## 4. Privacy, e la trappola della dashboard

Il quadro normativo è meno banale di quanto sembri.

**L'IP è dato personale** per il gestore del sito (Breyer, C-582/14). La base giuridica per loggarlo
a fini di sicurezza è il legittimo interesse, e il considerando 49 del GDPR lo dice quasi con le
nostre parole: il trattamento *"strettamente necessario e proporzionato"* per garantire la sicurezza
di rete e informazione *"costituisce un legittimo interesse del titolare"*.

**Chi risponde di cosa.** Il gestore del sito è il titolare. Noi che scriviamo il plugin, se non
riceviamo nessun dato, non siamo né titolari né responsabili: i dati non lasciano mai il suo server.
Questo però funziona solo finché il punto 3 resta valido. Nel momento in cui il plugin chiama un
nostro server, il nostro server riceve l'IP del sito cliente e la faccenda cambia.

**Il punto che cambia il progetto.** Le linee guida EDPB 2/2023 (§42-43) dicono che l'articolo 5(3)
ePrivacy si applica anche agli header HTTP, User-Agent incluso, e all'IP, e che *"il fatto che
l'entità ricevente non sia quella che ha istruito l'invio dell'informazione non esclude
l'applicazione dell'art. 5(3)"*. Quindi anche la ricezione passiva rientra. L'articolo 5(3) si
applica pure quando l'informazione non è dato personale (§12).

L'esenzione c'è, ed è nella WP29 Opinion 9/2014, §7.5, "User centric security": le tecniche usate
*"per il compito specifico di aumentare la sicurezza del servizio esplicitamente richiesto
dall'utente"* sono esenti da consenso, e il testo aggiunge che *"questa esenzione si applicherebbe
anche al device fingerprinting"*.

Ma con tre paletti, e uno è quello che ci riguarda: **non possono esserci finalità secondarie**. E
se un dato serve a più scopi, l'esenzione vale solo se *tutti* gli scopi sono individualmente
esenti. Uno scopo non esente contamina l'intera raccolta. Il fingerprinting per web analytics non è
esente, il consenso serve.

**Decisione che ne discende.** La dashboard del plugin mostra solo eventi di sicurezza: cosa è stato
bloccato, quando, quale bot. Niente "visitatori unici", niente pagine più viste, niente grafici
sul traffico umano. Nel momento in cui aggiungiamo una riga di analytics perdiamo l'esenzione e ci
serve un banner di consenso, che per un plugin gratuito che deve installarsi in trenta secondi è
la morte. La tentazione di mettere due statistiche carine va rimandata al 2027, e in quel caso
come modulo separato con consenso suo.

**Cosa implementiamo comunque**, perché la checklist privacy di wordpress.org lo chiede:
- `wp_privacy_anonymize_data()` sugli IP nei log, o hash con salt per sito
- exporter e eraser hook (`wp_privacy_personal_data_exporters` / `_erasers`)
- `wp_add_privacy_policy_content()` con il testo pronto da incollare nella privacy policy del sito
- retention configurabile con default breve, e pulizia completa alla disinstallazione

Il logger che sta in `plugins/iota-guardian-wp/includes/class-request-logger.php` si può riusare,
ma oggi salva l'IP in chiaro. Va sistemato prima.

---

## 5. Lingue

WordPress ha **209 locale** su translate.wordpress.org, di cui **41 al 100%** sul branch 7.0.

Non traduciamo noi. Il plugin va preparato bene e la community traduce gratis:

- il text domain deve coincidere con lo slug del plugin, minuscolo, con trattini
- l'import su translate.wordpress.org è automatico e obbligatorio per tutti i plugin della
  directory, non è una scelta
- il language pack per una lingua viene generato quando quella lingua arriva al **90%** delle
  stringhe del codice, approvate
- le stringhe del readme non contano ai fini di quella soglia
- da WordPress 4.6 non serve più chiamare `load_plugin_textdomain()`. Se lo chiamiamo lo stesso, va
  agganciato a `init` e non a `plugins_loaded`, altrimenti dalla 6.7 esce un `_doing_it_wrong`

Noi partiamo con italiano e inglese fatti da noi. Il resto arriva se il plugin viene usato.

---

## 6. Vincoli tecnici

- **PHP minimo 7.4.** WordPress 7.0.2 gira da PHP 7.4 in su, e il 22,7% delle installazioni reali
  sta ancora sotto PHP 8.0. Mettere `Requires PHP: 8.0` taglierebbe fuori un quarto del mercato per
  comodità nostra. Quindi niente enum, niente readonly, niente named arguments.
- `Requires PHP` e `Requires at least` vanno tenuti allineati fra header PHP e readme: dalla 5.8
  WordPress legge l'header del file principale, non il readme.
- Il readme deve passare il [validator ufficiale](https://wordpress.org/plugins/about/validator/),
  altrimenti la submission viene respinta. Tag: da 1 a 5, e mai nomi di concorrenti.
- Conviene far girare [Plugin Check](https://wordpress.org/plugins/plugin-check/) prima di inviare.
  Non risulta obbligatorio, ma è lo stesso strumento che usano i reviewer.

**Tempi di review.** Il tempo dichiarato è 14 giorni lavorativi. La coda a fine luglio 2026 era di
circa 5.000 plugin, ma il numero è ingannevole: 4.118 erano in attesa che rispondesse l'autore, e
solo 174 erano nuovi mai toccati, su circa 550 submission a settimana. La prima risposta arriva in
giorni. Quanto dura in totale dipende da quanti giri di correzioni ci fanno fare, e quello dipende
da noi.

---

## 7. Il campo

Installazioni attive dei plugin che fanno una cosa simile, da API wordpress.org il 3 agosto 2026:

```
block-ai-crawlers      1.000
bot-traffic-shield       500
ai-deny                   40
ai-crawler-guard           0
blackhole-bad-bots    30.000   (generico, honeypot)
wordfence          5.000.000   (security generale)
```

Nessuno domina, e quasi tutti si limitano a scrivere in robots.txt, che è una richiesta cortese che
il bot è libero di ignorare. Un blocco vero a livello HTTP è un'altra cosa, ed è la frase da mettere
in home page.

---

## 8. Dominio e hosting

Prezzi verificati il 3 agosto 2026. Il nome è libero su tutte le estensioni, e nessuno usa
"Spuncin" come marchio software.

**Il .ai è fuori budget.** Il registry di Anguilla impone un minimo di due anni, in registrazione
e in rinnovo. Su Porkbun sono 165,40 dollari all'ingresso e altrettanti a ogni rinnovo biennale,
cioè circa 83 dollari l'anno. Gandi chiede 320 euro a biennio. Otto volte un .com per una vocale.

**Vercel Hobby non va bene.** Le loro fair use guidelines definiscono commerciale *"qualsiasi
deployment usato ai fini del guadagno finanziario di chiunque sia coinvolto in qualsiasi parte
della produzione del progetto"*, e fra gli esempi mettono il pubblicizzare la vendita di un
prodotto e, in nota, il chiedere donazioni. Un bottone GitHub Sponsors basta a farci uscire.
Il piano Pro costa 240 dollari l'anno. C'è anche un limite pratico: su Hobby non si collegano
repository di organizzazioni GitHub, solo del proprio account personale.

**Scelta: `.com` su Porkbun più Cloudflare Pages.** Circa 11 dollari l'anno, con rinnovo identico
alla registrazione. Cloudflare Pages ha banda illimitata sugli asset statici, 100 domini custom
per progetto e nessuna restrizione sull'uso commerciale. Porkbun pubblica il listino via API,
quindi il prezzo è verificabile, e include la privacy WHOIS.

Cloudflare fa anche da registrar at-cost (circa 10,44 sul .com) ma il listino sta dietro login e
non è verificabile dall'esterno. Sessanta centesimi non giustificano la seccatura.

Due cose da ricordare:
- Verisign alza il prezzo all'ingrosso del .com del 7% **dal 1° novembre 2026**. Conviene
  registrare prima, eventualmente già su più anni.
- Evitare .site, .online e .xyz: due dollari il primo anno, poi fra 13 e 29 a ogni rinnovo.

Alternative valutate: GitHub Pages vieta e-commerce e SaaS ma non il resto, quindi sarebbe
utilizzabile. Netlify sul piano gratuito dà 300 crediti al mese non ricaricabili, e a crediti
esauriti il sito va offline con una pagina di errore. Scartato.

---

## 9. Cosa manca prima di scrivere codice

- slug definitivo del plugin, che poi diventa il text domain e non si cambia più
- registrare il dominio
- decidere se la prima release blocca (403) o serve una pagina di spiegazione
- capire cosa succede ai bot bloccati sul piano SEO: bloccare Google-Extended non tocca il ranking,
  bloccare Googlebot sì. Va scritto chiaro nelle impostazioni
