C’è un’abitudine comoda nello sviluppo software: considerare sicuro tutto quello che arriva da un componente interno. Il database è nostro, la coda è nostra, il worker è nostro. Quindi il messaggio sarà attendibile. Peccato che un sistema distribuito non funzioni sulla base della proprietà degli oggetti, ma dei confini attraverso cui passano i dati. Un messaggio può essere stato alterato, iniettato o prodotto da un servizio che non dovrebbe avere quella capacità. E quando un worker lo legge, spesso non si limita a mostrarlo: esegue un’operazione.
Le novità di Symfony Messenger 8.2 sono interessanti proprio per questo. La firma non viene più pensata soltanto come requisito di un singolo handler: può diventare una proprietà dell’intero transport, verificata prima di interpretare il messaggio. Sembra una differenza di configurazione. In realtà sposta il punto in cui un sistema decide di fidarsi.
Il problema non è la coda. È quello che autorizziamo a fare a chi la legge
Un messaggio asincrono può generare un PDF, aggiornare un ordine, richiamare un’API, inviare una comunicazione o avviare un processo. Chi gestisce il broker tende a concentrarsi su disponibilità, retry, throughput e persistenza. Sono questioni importanti, ma non rispondono alla domanda più semplice: chi garantisce che il contenuto arrivato al worker sia quello effettivamente prodotto da un mittente autorizzato?
La rete privata e le credenziali del broker riducono la superficie di attacco. Non sono però una prova crittografica dell’origine e dell’integrità di ogni singolo payload. Se un account viene compromesso, una configurazione concede permessi eccessivi o un altro producer scrive sulla stessa coda, il consumer deve avere un criterio per distinguere un messaggio legittimo da uno introdotto abusivamente.
Questo conta soprattutto quando gli handler possono eseguire comandi o processi. Symfony aveva già introdotto la firma dei messaggi in Symfony 7.4, permettendo di richiederla sul singolo handler. Il passo successivo è interrogarsi non solo su cosa controllare, ma su quando controllarlo.
Verificare dopo aver interpretato il payload significa aver già attraversato il confine
Un consumer deve ricevere byte, interpretare metadati e ricostruire un oggetto applicativo. Se per decidere quale handler richiede una firma bisogna prima determinare il tipo del messaggio, la catena di elaborazione rischia di leggere dati non ancora verificati. In alcune configurazioni questo passaggio può coinvolgere deserializzazione e caricamento di classi: operazioni che non andrebbero trattate come innocue quando l’input è ostile.
Qui serve precisione. Non significa che ogni applicazione con firma per handler sia automaticamente vulnerabile. Symfony aveva già corretto una specifica sequenza di verifica nel rilascio 8.0.11, assicurando che SigningSerializer non decodificasse il contenuto prima di verificare la firma. La novità architetturale di 8.2 è diversa: consente di imporre la firma a livello di transport e di rifiutare il messaggio prima di leggerne il contenuto, senza dipendere dalla selezione di un handler.
La sicurezza non è il controllo che esegui alla fine della pipeline. È il confine oltre il quale smetti di trattare un dato come non attendibile.
È una distinzione che torna in moltissimi progetti: validare un input dopo averlo usato, verificare i permessi dopo aver caricato risorse, controllare una firma dopo aver interpretato un oggetto. Il codice può anche apparire ordinato. L’ordine delle operazioni, però, è già sbagliato.
In Symfony 8.2 la firma può appartenere al transport
La documentazione ufficiale di Messenger 8.2 introduce l’opzione sign: true nella configurazione del transport. In quel caso i messaggi inviati vengono firmati con HMAC e quelli ricevuti senza firma valida sono rifiutati prima della lettura del payload. È un controllo d’integrità e autenticità rispetto al segreto condiviso, non una forma di cifratura: il contenuto non diventa riservato solo perché è firmato.
framework:
messenger:
failure_transport: failed
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
sign: true
failed:
dsn: 'doctrine://default?queue_name=failed'
sign: trueLa scelta ha conseguenze concrete. Se un transport firma tutti i messaggi, anche il relativo failure transport e l’eventuale outbox devono firmarli; altrimenti la configurazione non compila. La firma registra inoltre il livello di attendibilità: un messaggio considerato non verificato non può trasformarsi magicamente in verificato solo perché è stato spostato in un’altra coda.
Anche il comportamento degli errori merita attenzione. Un messaggio senza firma valida non viene semplicemente ritentato fino a funzionare: viene rifiutato. Può finire nel failure transport, ma un retry non ripara una firma assente o incompatibile. Non è un dettaglio: è la differenza tra una policy di sicurezza e un guasto operativo che si ripete all’infinito.
Il deploy che sembra riuscito e lascia indietro i messaggi
La tentazione è aggiungere sign: true, fare deploy e dichiarare chiuso il problema. È il genere di intervento che rende felice una checklist e molto meno chi deve gestire il lunedì mattina. I messaggi già presenti nella coda, se prodotti senza la nuova firma, non superano il controllo. La documentazione Symfony è esplicita: la firma va abilitata su transport senza messaggi pendenti.
Un rollout serio comincia quindi dall’inventario: quali code esistono, quali producer scrivono, quali consumer leggono, dove finiscono i fallimenti, quali outbox sono collegate e quanto possono restare in attesa i messaggi? La risposta cambia completamente il piano. Una coda svuotabile in pochi minuti non è un sistema con producer distribuiti e job differiti di giorni.
Una sequenza prudente
- Mappare il percorso completo di producer, transport, consumer, failure transport e outbox. Le eccezioni sono spesso il punto che un diagramma troppo pulito dimentica.
- Ridurre a zero il backlog prima di attivare la firma sul transport; coordinare la pausa e la ripresa dei producer quando necessario, evitando messaggi legacy che arrivino dopo il cambio.
- Verificare la configurazione in ambiente di test con messaggi validi, alterati, senza firma e con segreto errato. Controllare esplicitamente la destinazione degli errori.
- Distribuire in modo coordinato producer e consumer che condividono la coda. Un deploy parziale può trasformare una protezione corretta in un’interruzione del servizio.
- Monitorare dopo il rilascio rifiuti di firma, backlog, failure transport e tempi di consumo, senza interpretare ogni messaggio scartato come un semplice problema da ritentare.
Questi passaggi non sono un comando Symfony da copiare: sono un runbook da adattare alla topologia reale. E sono la parte che nessuna opzione YAML può conoscere al posto di chi ha progettato il sistema.
Rotazione dei segreti: il nuovo segreto non deve cancellare il passato
C’è poi un altro problema spesso sottovalutato. Cambiare il segreto usato per firmare è una buona pratica, ma i messaggi firmati con quello precedente possono restare in coda. Se il consumer riconosce soltanto la nuova chiave, quei messaggi smettono di essere validi. Symfony 8.2 introduce signing_secret come elenco: il primo valore firma i nuovi messaggi, tutti i valori configurati possono verificare quelli ricevuti.
framework:
messenger:
serializer:
signing_secret:
- '%env(MESSENGER_SIGNING_SECRET)%'
- '%kernel.secret%'Questo permette di separare la chiave di firma di Messenger dal segreto globale dell’applicazione e di effettuare una rotazione graduale. La chiave vecchia non va rimossa per impazienza: deve restare finché non sono stati consumati anche i messaggi ancora presenti nei failure transport. Se più applicazioni condividono i transport, la sequenza è ancora più delicata: prima tutte devono accettare la nuova chiave, poi possono iniziare a usarla per firmare.
È un caso concreto in cui il principio del controllo dei confini e dei contratti tra componenti conta più dell’eleganza della singola funzione. Il sistema è corretto solo se le sue parti continuano a capirsi durante la transizione, non soltanto dopo.
Una firma non sostituisce un’architettura di sicurezza
La firma HMAC tutela l’integrità e l’autenticità rispetto ai soggetti che possiedono il segreto. Non dimostra quale persona abbia inviato un messaggio quando più producer condividono la stessa chiave. Non cifra il contenuto. Non elimina la necessità di ACL sul broker, isolamento degli ambienti, gestione corretta dei segreti, idempotenza degli handler e osservabilità delle code.
Se tutti i servizi condividono indiscriminatamente lo stesso segreto, la firma attesta al massimo l’appartenenza a quel gruppo. È già utile, ma il confine di fiducia rimane largo. Se un consumer interpreta un messaggio verificato come autorizzazione a compiere qualsiasi azione, il problema si sposta semplicemente più avanti. Autenticità del messaggio e autorizzazione dell’operazione sono due controlli diversi.
Per questo la domanda da portare in una revisione tecnica non è soltanto “Abbiamo attivato la firma?”. È: “Quale rischio stiamo eliminando, quali messaggi smetteranno di funzionare e come ce ne accorgeremo?”. È una domanda meno vendibile di una nuova feature, ma infinitamente più utile quando il software deve continuare a lavorare.
La novità è il punto in cui decidiamo di fidarci
Al 21 ottobre 2026, Symfony 8.2 è ancora in sviluppo, con rilascio stabile previsto per novembre. Chi gestisce applicazioni in produzione deve quindi distinguere la documentazione della versione in preparazione dalla disponibilità stabile, e pianificare la migrazione senza raccontarsi che una novità annunciata equivalga a un aggiornamento già distribuito.
Il valore di questa evoluzione non sta nell’ennesima opzione da aggiungere alla configurazione. Sta nel rendere esplicito un principio che nello sviluppo asincrono viene troppo spesso nascosto dietro il broker: un messaggio resta un input esterno finché non ne abbiamo verificato le condizioni di fiducia. E una modifica di sicurezza non è completata quando passa il deploy, ma quando i messaggi legittimi continuano a passare e quelli illegittimi vengono respinti nel punto giusto.
Il resto è manutenzione della checklist. Il sistema, invece, deve reggere.
Domande frequenti
Che cosa cambia tra firma per handler e firma per transport in Symfony Messenger 8.2?
La firma per handler riguarda il requisito di un singolo gestore. Con sign: true sul transport, Symfony firma tutti i messaggi in uscita e verifica la firma in ingresso prima di leggerne il payload. La firma per handler è deprecata in Symfony 8.2.
Posso abilitare sign: true se nella coda ci sono già messaggi?
Non senza pianificare la migrazione: i messaggi già accodati senza firma valida saranno rifiutati. Occorre svuotare o migrare in modo controllato il backlog e coordinare producer, consumer, failure transport e outbox.
Come si ruota una chiave di firma senza perdere messaggi in attesa?
Symfony 8.2 consente una lista signing_secret: la prima chiave firma i nuovi messaggi, tutte verificano quelli ricevuti. La vecchia chiave va rimossa solo dopo che sono stati consumati anche i messaggi che la usano, inclusi quelli nei failure transport. Con più applicazioni, prima si distribuisce la nuova chiave come chiave accettata, poi come chiave di firma.
La firma HMAC cifra i messaggi o sostituisce i permessi del broker?
No. La firma serve a verificare integrità e autenticità rispetto a chi possiede il segreto condiviso; non cifra il payload e non sostituisce ACL del broker, gestione dei segreti, autorizzazione applicativa o idempotenza degli handler.
