Ci sono bug che nascono da una riga sbagliata e bug che nascono da una responsabilità assegnata al posto sbagliato. Nel secondo caso puoi aggiungere controlli, filtri e checklist finché vuoi: prima o poi qualcuno tornerà a usare la scorciatoia, perché il sistema continua a chiederglielo. Le traduzioni HTML sono un esempio perfetto. Abbiamo una stringa pensata per cambiare lingua e, a un certo punto, le chiediamo anche di decidere quale markup finirà nel browser.
Symfony 8.2 introduce HtmlTranslator e il filtro Twig trans_html, presentati nel blog ufficiale del framework e sviluppati nella pull request #66690. La novità non è un altro modo di stampare una frase tradotta. È il tentativo di rendere esplicito un confine: chi scrive la traduzione può decidere dove collocare un elemento; il codice dell’applicazione decide quale elemento sia, quali attributi abbia e come vadano trattati i dati.
Il problema non è la traduzione. È quel raw messo per comodità
Nel normale rendering Twig, una traduzione restituita da trans viene sottoposta all’escaping dell’output. Questo comportamento è documentato anche nella guida Symfony alle traduzioni. Quando la frase deve contenere un link o un grassetto, la tentazione è comprensibile: inserire direttamente l’HTML nel catalogo e aggiungere |raw nel template. Il risultato appare corretto, il ticket viene chiuso e il problema viene rinviato a chi modificherà quella stringa tra sei mesi.
Attenzione alla distinzione: trans|raw non significa automaticamente vulnerabilità XSS. Significa però che stai disattivando una protezione e trasferendo la fiducia sull’intero risultato tradotto, compresi eventuali parametri interpolati. Se il catalogo, il processo di localizzazione o un parametro non hanno il livello di fiducia che il template presume, quella scelta diventa una superficie di rischio. È un problema di modello, prima ancora che di caratteri speciali.
Un’applicazione internazionale può avere decine di lingue, file di traduzione modificati da soggetti diversi, revisioni asincrone e messaggi riutilizzati in più contesti. Chiedere a tutti questi passaggi di comportarsi come se stessero modificando codice HTML verificato è un contratto piuttosto fragile. E la fragilità non sparisce perché il file di traduzione è versionato su Git.
Il markup torna a essere una decisione del programma
L’idea di trans_html è usare nelle traduzioni degli pseudo-tag dichiarativi, per esempio <signin_link>. Non sono tag HTML arbitrari con attributi decisi nel catalogo. Sono segnaposto strutturali che l’applicazione associa a elementi e attributi espliciti. Il traduttore può spostare il link dentro la frase per rispettare la grammatica della lingua; non può inventare liberamente un onclick, un URL o un elemento eseguibile attraverso quel segnaposto.
Questo dettaglio è molto più importante del nome del filtro. Un sistema di traduzione deve gestire la posizione del testo e la sua resa linguistica. Il template, invece, possiede la conoscenza del DOM, dei link consentiti e del contesto in cui il messaggio verrà inserito. Mischiare le due cose in una stringa significa spostare un pezzo di architettura in un luogo che non dovrebbe governarlo.
La sicurezza migliora quando non chiedi più a una stringa tradotta di comportarsi come codice affidabile.
La proposta Symfony separa questi livelli anche nei casi meno appariscenti: il rendering effettua escaping del testo e dei parametri, inclusi quelli globali, oltre ai valori degli attributi costruiti dal mapping. I tag non dichiarati non diventano automaticamente markup; i tag sbilanciati vengono trattati conservativamente e gli elementi raw-text pericolosi come script e style sono rifiutati. Sono scelte che privilegiano un output sicuro rispetto al tentativo di indovinare cosa volesse fare un messaggio malformato.
Perché il caso difficile non è il link statico
Un link fisso dentro una traduzione di prova è il caso semplice. Quelli interessanti arrivano quando il messaggio contiene un nome utente, un parametro globale, un indirizzo costruito dal routing, oppure quando una lingua deve spostare il link rispetto al soggetto della frase. In questi scenari il sistema deve distinguere almeno tre materiali: testo localizzato, dati variabili e struttura HTML. Hanno origini e livelli di fiducia diversi, quindi non dovrebbero attraversare lo stesso passaggio senza controlli specifici.
La nuova API non rende irrilevante la qualità del codice applicativo. Se lo sviluppatore costruisce un attributo con un valore semanticamente pericoloso o utilizza un URL che non avrebbe dovuto autorizzare, il problema resta suo. L’escaping di un attributo evita che un valore rompa la sintassi HTML; non decide se una destinazione sia legittima per il tuo prodotto. Sicurezza del contesto e policy applicativa sono due verifiche diverse. Confonderle sarebbe soltanto un modo più elegante di ripetere l’errore iniziale.
Vale anche il contrario: se una traduzione è sbagliata, il sistema deve poterla segnalare o mostrare in forma non interpretata senza trasformare quell’errore editoriale in codice eseguibile. È una proprietà importante perché gli errori di localizzazione sono inevitabili. Un’architettura robusta non pretende input perfetti; definisce cosa succede quando perfetti non sono.
Cosa cambia per chi mantiene un progetto Symfony
La prima reazione potrebbe essere cercare tutti i |trans|raw e sostituirli automaticamente. Sarebbe una migrazione apparentemente efficiente, ma poco intelligente. Il nuovo filtro ha un contratto diverso: richiede di dichiarare quali pseudo-tag siano ammessi e come diventino markup. Se il catalogo contiene HTML libero, non basta cambiare il nome di una funzione per ottenere un comportamento equivalente.
- Inventariare i punti di rendering. Individuare le traduzioni stampate con
raw, i tag di traduzione Twig e i messaggi HTML generati in PHP. - Separare i casi. Alcune traduzioni devono restare semplice testo; altre richiedono link o enfasi controllata. Non hanno bisogno della stessa soluzione.
- Dichiarare il mapping nel codice. La traduzione descrive la posizione dello pseudo-tag, il template ne stabilisce semantica e attributi.
- Testare lingue e input ostili. Verificare parametri contenenti markup, tag non dichiarati, tag non bilanciati e casi di nesting.
- Controllare il DOM finale. Un test che confronta soltanto la stringa tradotta non vede necessariamente gli effetti del rendering.
Questa lista non è un invito a migrare in fretta solo perché una funzionalità è comparsa nel changelog. Al 19 ottobre 2026 stiamo parlando di una novità della linea Symfony 8.2 in sviluppo, non della prova che ogni applicazione possa adottarla immediatamente in produzione. Servono verifiche sulla versione effettivamente installata, sulle dipendenze Twig e sui test di regressione del progetto. L’API va trattata come una possibilità concreta da valutare, non come una patch retroattiva magicamente applicata.
Non finisce nei template Twig
Un dettaglio che merita attenzione è la presenza di HtmlTranslator come servizio utilizzabile anche da PHP. Pensiamo alle email HTML: spesso vengono costruite fuori dal normale percorso di rendering Twig, ma condividono gli stessi problemi di testo localizzato, link, parametri e markup. Avere un modello riutilizzabile permette di evitare che ogni canale inventi la propria combinazione di sostituzioni e concatenazioni. Non elimina la necessità di testare i client email, ma può ridurre la divergenza tra percorsi applicativi.
Qui c’è una lezione architetturale che va oltre Symfony: quando una regola di sicurezza è distribuita in dieci template, cinque helper e tre servizi di invio, non possiedi davvero una regola. Possiedi dieci interpretazioni della stessa intenzione. Un servizio dedicato può aiutare a rendere il comportamento esplicito e verificabile. Naturalmente la centralizzazione ha valore soltanto se il contratto è chiaro e se chi lo usa non può disattivarlo per ottenere un risultato visivo più comodo.
La differenza tra correggere l’output e correggere il confine
Molte discussioni sulla sicurezza finiscono su una domanda troppo piccola: “questa stringa viene escapata?”. È una domanda necessaria, ma non sufficiente. Quella più interessante è: “chi ha il diritto di decidere che cosa diventa markup?”. Se la risposta è “chiunque possa modificare una traduzione”, abbiamo creato un accoppiamento che rende la sicurezza dipendente da un processo editoriale. Se la risposta è “il codice che conosce il contesto di rendering”, abbiamo almeno assegnato la responsabilità a un luogo verificabile.
È lo stesso principio che ricorre quando si ragiona di lock e atomicità delle operazioni: un meccanismo utile non può sostituire la definizione precisa del confine che deve proteggere. Qui il confine non è una transazione, ma il passaggio tra contenuto e codice interpretato dal browser. La differenza è sostanziale; l’errore mentale è sorprendentemente simile.
Symfony 8.2 non inventa il principio della separazione tra dati e presentazione. Offre però una soluzione concreta a un punto in cui quella separazione veniva spesso sacrificata per comodità. Ed è proprio questo che rende interessante la novità: non il filtro in più da imparare, ma una scorciatoia in meno da giustificare durante una code review. Il miglior risultato non è poter scrivere “HTML sicuro” nel titolo di una feature. È poter spiegare, senza acrobazie, chi controlla ogni pezzo dell’output.
Domande frequenti
Perché trans|raw può essere rischioso in Twig?
Perché raw disattiva l’escaping dell’output: il risultato tradotto, inclusi eventuali parametri, deve quindi essere considerato affidabile. Non è automaticamente una vulnerabilità, ma aumenta la responsabilità di chi controlla contenuti e interpolazioni.
Che cosa cambia con trans_html in Symfony 8.2?
La traduzione può contenere pseudo-tag dichiarativi, mentre il codice applicativo definisce quali elementi HTML e attributi generare. Il rendering gestisce l’escaping del testo e dei valori nel contesto previsto dalla nuova API.
Posso sostituire automaticamente trans|raw con trans_html?
No. Occorre distinguere le traduzioni testuali da quelle con markup, definire il mapping degli pseudo-tag e verificare i cataloghi nelle diverse lingue, oltre ai casi con parametri e tag malformati.
trans_html elimina ogni rischio XSS?
No. Riduce il rischio specifico di interpretare markup arbitrario dalle traduzioni, ma non sostituisce la validazione degli URL, le policy applicative e i controlli di sicurezza negli altri contesti di output.
HtmlTranslator serve soltanto nei template Twig?
No. Il servizio HtmlTranslator è utilizzabile anche da PHP, per esempio quando si producono email HTML localizzate. Va comunque verificato il rendering nel canale di destinazione.
