Documentazione tecnica

Manuale Operativo Tecnico

UnicoWhistle — Piattaforma di whistleblowing conforme al D.Lgs. 24/2023 (attuazione della Direttiva UE 2019/1937)

Documento riservato ad uso tecnico/amministrativo interno · Generato il 19/10/2026 20:39

1. Introduzione e scopo

UnicoWhistle è un'applicazione Laravel che implementa un canale di segnalazione interno (whistleblowing) ad uso multi-azienda: ogni azienda cliente (tenant) dispone di un proprio canale pubblico anonimo per la ricezione delle segnalazioni e di un pannello di gestione riservato ai gestori incaricati (gestori delle segnalazioni).

Il sistema automatizza gli adempimenti previsti dalla normativa italiana sul whistleblowing: avviso di ricevimento entro 7 giorni, riscontro finale entro 3 mesi, conservazione limitata nel tempo dei dati con anonimizzazione automatica, e tracciabilità delle comunicazioni tra segnalante e gestore mantenendo l'anonimato del primo.

2. Architettura tecnica

LivelloTecnologiaNote
Framework applicativoLaravel 12 (PHP 8.4)Struttura file standard Laravel 11+, configurazione middleware/eccezioni in bootstrap/app.php.
Pannello amministrativoFilament v5Risorse in app/Filament/Resources, provider in app/Providers/Filament/AdminPanelProvider.php.
Form pubbliciLivewireapp/Livewire/PublicReportForm.php e PublicReportTracker.php, layout layouts.guest.
AllegatiSpatie Media LibraryCollection evidence sul modello Report, disco private, contenuto cifrato.
Audit trailSpatie ActivitylogTraccia solo i cambi di campi non sensibili (es. stato), mai il contenuto in chiaro.
PDFbarryvdh/laravel-dompdfEsportazione del fascicolo segnalazione.
2FAFilament Multi-Factor (TOTP)Filament\Auth\MultiFactor\App\AppAuthentication.

3. Modello dati

Company (azienda / tenant)

Tabella companies. Campi principali: name, slug (usato negli URL pubblici), logo_path, brand_color (white-label del pannello e delle pagine pubbliche), shared_passcode (codice opzionale condiviso per sbloccare il form pubblico) e passcode_rotated_at.

Nota di sicurezza Il valore di shared_passcode non viene mai scritto nell'audit log: viene tracciata solo la data dell'ultima rotazione (passcode_rotated_at), aggiornata automaticamente tramite un mutatore sull'attributo quando il valore cambia realmente.

User (gestore)

Tabella users. Relazione belongsToMany(Company): un gestore può essere assegnato a una o più aziende (multi-tenancy Filament tramite HasTenants). canAccessPanel() richiede almeno un'azienda assegnata. Autenticazione a due fattori TOTP tramite InteractsWithAppAuthentication.

Report (segnalazione)

CampoTipo / castNote
tracking_tokenstringPIN univoco nel formato WHSL-XXXX-XXXX, unico strumento con cui il segnalante accede in seguito alla propria pratica.
statusenum ReportStatusNuova In Lavorazione Chiusa
descriptionstring, cast encryptedContenuto cifrato a riposo nel database.
corrective_measuresstring, cast encryptedRegistro delle misure correttive adottate, cifrato.
acknowledgement_due_atdatetimeImpostata automaticamente a created_at + 7 giorni alla creazione.
feedback_due_atdatetimeImpostata automaticamente a created_at + 3 mesi alla creazione.
anonymized_atdatetime, nullableValorizzata da anonymize() quando la pratica supera il periodo di conservazione.

Gli allegati sono gestiti tramite la collection Media Library evidence, salvati sul disco private e cifrati a riposo (vedi sezione Sicurezza).

Message (messaggio di chat)

Tabella messages, relazione hasMany su Report. Campo body cifrato, is_from_reporter (booleano) distingue i messaggi del segnalante da quelli del gestore. Non sono modificabili né eliminabili una volta creati (integrità dell'audit trail), imposto a livello di MessagePolicy.

CorrectiveMeasureTemplate (modello di misura correttiva)

Tabella corrective_measure_templates. Campo company_id nullable: se null il modello è globale e visibile/riutilizzabile da tutte le aziende, altrimenti è specifico dell'azienda proprietaria. La modifica dei template globali è riservata alla console/seeder (vedi CorrectiveMeasureTemplatePolicy).

4. Multi-tenancy

Il pannello Filament è configurato con ->tenant(Company::class, slugAttribute: 'slug') in AdminPanelProvider: ogni URL del pannello è prefissato dallo slug dell'azienda selezionata e lo scoping automatico delle query per tenant è delegato a Filament, tranne dove esplicitamente disabilitato (es. CorrectiveMeasureTemplateResource, che ha bisogno di mostrare anche i modelli globali con company_id nullo, e ricostruisce lo scoping a mano in getEloquentQuery()).

Il colore primario dell'interfaccia (brand_color) viene applicato dinamicamente in base al tenant corrente tramite la closure passata a ->colors().

5. Sicurezza e protezione dati

Autenticazione a due fattori

La 2FA tramite app authenticator (TOTP) è obbligatoria per ogni gestore al primo accesso utile, tranne in locale con APP_DEBUG=true dove resta disponibile ma non obbligatoria, per non intralciare lo sviluppo.

Cifratura dei dati

  • Contenuto testuale: description e corrective_measures del Report, e body dei Message, sono cifrati a riposo tramite cast Eloquent encrypted (basato su APP_KEY).
  • Allegati: ogni file caricato dal segnalante viene cifrato con Crypt::encryptString() prima di essere scritto sul disco private; nome file originale e mime type sono conservati solo come custom properties del record Media (nel database), mai nel filesystem in chiaro.
  • Passcode condiviso: confrontato con hash_equals() per evitare timing attack; il valore non compare mai nei log applicativi.

Isolamento tenant e protezione IDOR

La rotta /admin/media/{media}/download verifica esplicitamente che l'utente autenticato appartenga alla company proprietaria del Report a cui il media è associato, prima di decifrare e restituire il file: previene l'accesso cross-tenant agli allegati anche conoscendo l'ID del media. La rotta di esportazione PDF (/admin/reports/{report}/pdf) applica la policy view del Report. Il tracciamento pubblico (PublicReportTracker) filtra sempre le segnalazioni con lo scope forCompany(), impedendo di raggiungere segnalazioni di un'altra azienda anche indovinando un PIN valido per un tenant diverso.

Rate limiting

AzioneChiaveLimite
Verifica passcode form pubblicopasscode-verify:{company}:{ip}10 tentativi / 60s
Invio segnalazionereport-submit:{company}:{ip}5 invii / 300s
Accesso tramite PINreport-tracker:{company}:{ip}10 tentativi / 60s

Audit trail

Tramite Spatie Activitylog, Company e Report registrano solo le modifiche ai campi non sensibili (es. lo stato di una segnalazione, o la data di rotazione del passcode), mai il contenuto cifrato. Le voci di log sono consultabili dal pannello nella scheda "Log attività" della risorsa Segnalazioni.

Politica di immodificabilità Le segnalazioni e i messaggi non possono essere creati né cancellati dal pannello amministrativo (arrivano esclusivamente dal form pubblico e non vengono mai eliminate, per obbligo di conservazione legale); i messaggi non sono modificabili una volta inviati. Queste regole sono imposte a livello di Policy (ReportPolicy, MessagePolicy), non solo di interfaccia.

6. Flusso pubblico del segnalante

  1. Accesso al canale: /segnala/{company:slug}. Se l'azienda ha impostato un shared_passcode, viene richiesto prima di mostrare il form.
  2. Compilazione: oggetto, descrizione dettagliata, fino a 5 allegati (PDF, JPEG, PNG, MP3, WAV; max 10MB ciascuno) — inclusi eventuali messaggi vocali registrati e alterati nel timbro dal browser.
  3. Invio: viene generato un PIN univoco WHSL-XXXX-XXXX, la segnalazione è salvata con stato "Nuova" e i gestori dell'azienda ricevono una notifica via email priva del contenuto della segnalazione (solo invito ad accedere al pannello).
  4. Tracciamento: /traccia/{company:slug}, accesso tramite il PIN ricevuto. Il segnalante può leggere e inviare messaggi in chat con il gestore, restando anonimo, e può richiedere un incontro diretto (notifica ai gestori).

7. Flusso operativo del gestore

  1. Login sul pannello (/admin) con verifica 2FA TOTP.
  2. Selezione dell'azienda (tenant) se assegnato a più aziende.
  3. Dashboard con widget contatori (Nuove / In Lavorazione / Chiuse) scoperto per il tenant corrente.
  4. Risorsa Segnalazioni: elenco con colonne PIN, oggetto, stato, avviso/riscontro scaduti, incontro richiesto. Apertura di una pratica per: aggiornare lo stato, registrare le misure correttive (anche richiamando un CorrectiveMeasureTemplate), inviare l'avviso di ricevimento, esportare il fascicolo in PDF, rispondere in chat al segnalante.
  5. Risorsa Aziende (solo Amministrazione SaaS): anagrafica, logo, colore brand, generazione/rotazione del passcode condiviso, generazione del QR code del link pubblico di segnalazione.
  6. Risorsa Utenti (solo Amministrazione SaaS): creazione gestori e assegnazione alle aziende di competenza.
  7. Risorsa Modelli Misure Correttive: gestione dei testi predefiniti richiamabili nel registro delle misure correttive, globali o specifici per azienda.

8. Scadenziario di legge automatizzato

Alla creazione di ogni Report, il sistema calcola automaticamente due scadenze previste dal D.Lgs. 24/2023:

AdempimentoTermineCampo
Avviso di ricevimento al segnalante7 giorni dalla ricezioneacknowledgement_due_at
Riscontro finale sull'esito della segnalazione3 mesi dalla ricezionefeedback_due_at

Le scadenze superate o in avvicinamento sono evidenziate nella tabella delle Segnalazioni e generano promemoria automatici ai gestori (vedi sezione successiva).

9. Job pianificati e comandi Artisan

ComandoPianificazioneFunzione
reports:check-deadlines Giornaliera Invia promemoria ai gestori: avviso di ricevimento scaduto o in scadenza entro 2 giorni; riscontro finale scaduto o in scadenza entro 14 giorni. Evita duplicati tramite i timestamp *_reminder_sent_at.
reports:anonymize-expired Giornaliera Anonimizza le segnalazioni chiuse da più tempo del periodo di conservazione configurato (contenuto, allegati e messaggi vengono cancellati, mantenendo solo i metadati necessari a statistiche aggregate e audit).

La pianificazione è definita in routes/console.php tramite la facade Schedule; è necessario che lo scheduler Laravel (php artisan schedule:run) sia attivo via cron/supervisor in produzione.

10. Configurazione d'ambiente

VariabileDescrizioneDefault
APP_NAMENome applicazione mostrato in email e pannello.UnicoWhistle
REPORT_RETENTION_MONTHSMesi di conservazione di una segnalazione chiusa prima dell'anonimizzazione automatica.24
APP_DEBUGSe true, disabilita l'obbligo di 2FA (solo per sviluppo locale).false in produzione

Il file config/whistleblowing.php centralizza le impostazioni specifiche di dominio (attualmente retention_months).

11. Manutenzione operativa

  • Verificare periodicamente che lo scheduler (schedule:run) sia in esecuzione: da esso dipendono sia i promemoria di legge sia l'anonimizzazione automatica, entrambi requisiti di conformità.
  • Il disco private deve restare non accessibile pubblicamente: gli allegati sono cifrati, ma il percorso di storage non deve comunque essere esposto da configurazioni del webserver.
  • Eventuali interventi su APP_KEY (rotazione della chiave) rendono illeggibili i dati cifrati con la chiave precedente (descrizioni, misure correttive, messaggi, allegati): pianificare sempre una migrazione dei dati cifrati prima di sostituire la chiave.
  • Il pannello richiede almeno un'azienda assegnata per consentire l'accesso a un utente (canAccessPanel()): un gestore senza aziende assegnate non può accedere, anche con credenziali valide.