Riferimento: ATT-E2EE-2026-05-18-v2.0
Data di emissione: 2026-05-18
Ambito software: app.leggit.org (cassaforte),
leggit.org (sito pubblico)
Edizione: v2.0 — verifica di non regressione v1.0 + audit delle modifiche successive
Cronologia:
v1.0 archiviata (16/05/2026) ·
v2.0 fonte markdown
📋 Aggiornamento v2.0 (2026-05-18)
Questa edizione combina due passaggi di audit security-engineer:
- v1.0 (16/05) — 12 rilievi: 1 HIGH (enumerazione telefoni), 4 MEDIUM (rate-limit, CRLF filename, cleanup pairings, quota), 4 LOW (Cache-Control, audit id_coffre, reveal Solo tabella, Referrer-Policy), 3 INFO. Tutti corretti e verifica non regressione in v2.0.
- v2.0 (18/05) — 5 nuovi rilievi: 1 HIGH (riassegnazione distruttiva
window.LeggitCoffreFile rompendo l'upload E2EE dual-device), 2 MEDIUM (riutilizzi di placeholder PDO :u causando SQLSTATE[HY093] in casseforti familiari e validate paranoia), 2 LOW (rate-limit assente su get_my_privkey/request_recovery, confronto non a tempo costante del codice di autorizzazione). Tutti corretti.
Modifiche di architettura E2EE dalla v1.0
- Tabella di sostituzione (modalità Prudente) — In precedenza caricata tramite l'endpoint legacy
upload_file (cifratura lato server, badge «server»). Ora tramite il flusso chunked E2EE completo: upload_init_e2ee + upload_chunk_e2ee (header secretstream + chunk da 1 MiB) + upload_finalize_e2ee. Il server non vede mai la foto in chiaro, nemmeno temporaneamente. Badge «E2EE».
- Workflow dual-device (PC ↔ telefono) — Consente di inserire le parole sostituite sul PC e fotografare la tabella sul telefono, senza che un singolo dispositivo abbia simultaneamente entrambi i segreti. Pairing: token lungo 256 bit (memorizzato in SHA-256), codice breve 6 caratteri (alfabeto senza confondibili), one-shot atomico tramite
UPDATE … WHERE consume_le IS NULL, TTL 24h, rate-limit basato su file.
- Rivelazione Prudente in 3 modalità — Durante la consultazione, l'utente sceglie prima del decifrazione: «Solo parole», «Solo tabella» (il seed NON viene decifrato), «Entrambi». Il ramo «Solo tabella» bypassa la chiamata a
LeggitCoffreCrypto.decryptItem lato browser, coerente con la promessa dual-device.
- Tabella di riferimento
relations_types — Tabella DB amministrabile (solo super-admin) che sposta i tipi di relazione (Coniuge, Figlio, Genitore…) fuori dal codice. Ogni tipo porta la sua relazione speculare (figlio↔genitore, nipote↔nonno, ecc.) e un flag «erede di default». Prepara la fase 2 (tabella di collegamento user_relations tra 2 utenti con workflow di validazione).
- Test di integrità — Passati da 753 a 774+ asserzioni (sezioni da P11.52 a P11.63 aggiunte per bloccare future regressioni sui temi auditati).
Nessuna falla crittografica. Le primitive rimangono invariate (XChaCha20-Poly1305 / X25519 / Argon2id / Shamir GF(2⁸)). I rilievi sono bypass di rate-limit, vettori di enumerazione a livello applicativo e — per l'HIGH v2.0 — un bug funzionale JS (nessuna fuga di dati).
1.Preambolo e ambito
Il presente attestato documenta lo stato di conformità del sistema leggit rispetto agli standard di crittografia end-to-end (E2EE) lato browser e alle minacce di sicurezza applicative note, alla data di emissione.
Copre
- Il modulo casseforti personali (testo, file, video fino a 100 MB)
- Il modulo casseforti familiari (testo + file multi-membro)
- Il modulo crypto-wallets (modalità Standard / Prudente / Paranoia)
- La pipeline di autenticazione e di recupero post-mortem
- L'infrastruttura di sicurezza (CSP, SRI, HSTS, audit log)
Non copre
- La sicurezza del dispositivo utente (browser compromesso, estensione malevola, malware locale — fuori dal perimetro tecnico di leggit)
- Le comunicazioni esterne a leggit (email esterna, SMS ricevuti tramite un operatore)
- I servizi terzi esterni (Stripe per i pagamenti, OVH per l'hosting — la cui sicurezza è oggetto dei rispettivi attestati)
2.Architettura E2EE — sintesi tecnica
2.1 Principio di non-accesso lato server
Il server leggit non possiede mai:
- I contenuti in chiaro (testi, file, video, seed BIP-39)
- La chiave KEK_user derivata dalla password dell'utente
- Le chiavi DEK (Data Encryption Key) generate per item
Il server memorizza e manipola solo:
- Cipher blob cifrati AEAD XChaCha20-Poly1305-IETF
- Wrap DEK cifrati (AEAD per il proprietario,
crypto_box_seal X25519 per i destinatari / membri della famiglia)
- Fingerprint DEK (SHA-256, verifica di coerenza senza rivelare il DEK)
2.2 Primitive crittografiche
| Primitiva | Algoritmo | Utilizzo |
| AEAD simmetrico | XChaCha20-Poly1305-IETF | Cipher degli item + chunk dei file |
| Streaming chunked | secretstream_xchacha20poly1305 | File > 4 MiB |
| KDF password | Argon2id (crypto_pwhash) | KEK utente |
| Asimmetrico | X25519 / crypto_box_seal | Wrap destinatari + famiglia |
| Secret sharing | Shamir GF(2⁸) | Modalità PARANOIA crypto-wallets (M-of-N) |
| Hash | SHA-256 (WebCrypto) | dek_fingerprint |
| Integrità script | SRI sha384 | Tutti i JS critici |
2.3 Isolamento Web Worker
Tutte le operazioni crittografiche vengono eseguite in un Web Worker isolato (assets/js/coffre-crypto-worker.js). Il main thread:
- Non ha mai accesso alla KEK_user (trasferita tramite
ArrayBuffer Transferable — vedere MED-3)
- Non ha mai accesso alla privkey X25519 decifrata
- Non ha mai accesso ai DEK in chiaro
- Non ha mai accesso alle parole BIP-39 o alle share Shamir prima di box_seal
Conseguenza: un XSS sul main thread (vettore residuo) non è sufficiente per estrarre le chiavi.
2.4 Memorizzazione lato server
Tutti i dati sensibili nel database sono opachi:
SELECT cipher_blob FROM coffre_items WHERE crypto_version LIKE 'e2ee-%';
-- → BLOB binario, nessuna correlazione possibile con un plaintext noto
Un dump completo del database non consente il recupero dei contenuti senza la KEK_user di ciascun utente (che esiste solo nella memoria del browser).
3.Modello di minaccia
3.1 Minacce coperte (protette)
| Vettore | Protezione |
| Dump database a freddo (furto disco, snapshot, backup) | Cipher inutilizzabile senza KEK utente |
| Lettore insider passivo (amministratore curioso, log, query SQL) | Dati opachi anche per il DBA |
| RCE server "istantanea" senza modifica del codice sorgente | Il cipher resta opaco, la KEK non transita mai in chiaro |
| Rete intermedia (proxy, ISP, NSA passiva) | TLS + HSTS + cipher applicativo ridondante |
| Furto del cookie di sessione + replay | Re-auth reauth_until_e2ee richiesta per le operazioni sensibili (CR-S3, HIGH-1) |
| Scrape massivo degli audit log tramite sessione compromessa | Rate-limit 30/60s + rilevamento scrape (MED-2) |
| Forgia di wrap con destinatari fuori dalla whitelist | Verifica whitelist + fingerprint coerente (CR-S2) |
3.2 Minacce NON coperte (limiti dichiarati)
Il sistema non protegge contro:
- RCE server attiva che modifica il JavaScript servito. Mitigata da SRI sha384 + sub-resource versioning + CSP, ma non eliminata. Un attaccante con accesso admin al server web può sostituire il codice crittografico.
- Browser dell'utente compromesso (malware, estensione malevola, keylogger). Fuori dal perimetro tecnico.
- Vincolo giudiziario che imponga la modifica del server (lawfare). leggit ha sede in Francia ed è soggetto alle richieste giudiziarie applicabili.
- Crittoanalisi futura di XChaCha20 o X25519. Rischio residuo su 10-20 anni, monitorato dalla comunità NIST/IETF.
Tali limiti vengono comunicati esplicitamente all'utente finale nella pagina pubblica /securite e nella documentazione marketing.
4.Metodologia di audit
4.1 Perimetro verificato
L'audit ha riguardato:
- 13 endpoint API E2EE (
app.leggit.org/api/coffre_*.php, modules/coffres/api.php, modules/coffres-familiaux/api.php, modules/crypto-wallets/api.php)
- 7 moduli JavaScript browser (Worker + handler)
- L'infrastruttura CSP / SRI / header HTTP
- Il flusso di recupero post-mortem (Shamir + KEK_recovery)
- I pattern di rate-limit e di audit log
- La coerenza delle migrazioni del database (
047_coffre_e2ee.sql e successive)
4.2 Strumento utilizzato
L'audit è stato condotto da un agente automatizzato di tipo "security-engineer" (Claude AI, Anthropic) seguendo le check-list OWASP ASVS Level 2, integrato da un'analisi manuale mirata degli invarianti crittografici.
⚠️ Nota importante di trasparenza
L'audit è stato realizzato da un agente di intelligenza artificiale, e non da un consulente umano certificato (CISSP, OSCP, ecc.). Il presente attestato non ha valore di certificazione pentest. Si raccomanda un audit esterno umano prima di una promozione pubblica rilevante della funzionalità E2EE (stima: 5-10 k€ per un pentest a spettro completo).
4.3 Classificazione dei finding
- CRITICAL: sfruttamento immediato, bypass del modello E2EE
- HIGH: rischio sfruttabile in determinate condizioni, impatto elevato
- MEDIUM: difesa in profondità indebolita, mitigazioni indirette
- LOW: igiene / hardening, impatto residuo limitato
4.4 Criterio di chiusura di un finding
Un finding è considerato trattato solo se sono soddisfatti tutti e tre i criteri seguenti:
- Implementazione del fix nel codice (commit identificato)
- Test E2E automatizzato che riproduce l'invariante atteso (PASS)
- Verifica di non-regressione su tutte le fasi precedenti
5.Finding dell'audit e relativo trattamento
5.1 Tabella riepilogativa
| Severità | Rif. | Descrizione breve | Stato | Test |
| HIGH | HIGH-1 | Famiglia: reauth obbligatoria per invite_membre se E2EE attivo + conferma esplicita browser prima del rewrap automatico | RISOLTO | P11.1–P11.8 |
| HIGH | HIGH-2 | Wallet PARANOIA: entropia BIP-39 + split Shamir + box_seal isolati nel Web Worker | RISOLTO | P11.40–P11.43 |
| HIGH | HIGH-3 | Phasing CSP enforce: infrastruttura LEGGIT_CSP_MODE a 4 modalità + endpoint admin per la revisione delle violazioni | RISOLTO | P11.36–P11.39 |
| MED | MED-1 | actionFamMigrateItem: verifica freschezza lock 300s (rollback + audit) | RISOLTO | P11.9–P11.11 |
| MED | MED-2 | coffre_audit_log.php: rate-limit 30/60s + rilevamento scrape >10 pagine/5min + coalescing | RISOLTO | P11.12–P11.16 |
| MED | MED-3 | KEK + privkey trasferite al Worker tramite ArrayBuffer Transferable | RISOLTO | P11.17–P11.20 |
| MED | MED-4 | Invariante rewrap recipient documentato + audit coffre.rewrap_blocked_no_reauth | RISOLTO | P11.21–P11.24 |
| LOW | LOW-1 | leggitValidateWrapBlobSize($type, $blob): limiti stringenti per wrap_type | RISOLTO | P11.25–P11.28 |
| LOW | LOW-2 | id_user_init + id_user_owner_* in meta.json + coerenza cross-user al finalize | RISOLTO | P11.29–P11.31 |
| LOW | LOW-3 | Tetto rewrap 5000 → 500 / batch + batching lato client | RISOLTO | P11.32–P11.33 |
| LOW | LOW-4 | Coalesce audit coffre.user_keys_fetched a 1 / 60s / utente | RISOLTO | P11.34–P11.35 |
Totale: 11 finding trattati, 0 aperti al momento dell'attestato.
5.2 Finding storici (fasi 0-10)
Per memoria, le fasi precedenti hanno trattato:
- CR-1 a CR-18 (18 critical dell'audit iniziale): coperti nelle Fasi 0-7
- Fasi 0-7: infrastruttura E2EE casseforti personali (422 test PASS)
- Fase 8: crypto-wallets STANDARD/PRUDENTE/PARANOIA E2EE (54 test PASS)
- Fase 9: casseforti familiari testo E2EE (57 test PASS)
- Fase 10: casseforti familiari file + rewrap al login + migrazione legacy (69 test PASS)
6.Verifica da parte del security engineer
6.1 Dichiarazione dell'auditor
Identità dell'auditor: Agente automatizzato
security-engineer (Claude Sonnet 4.5, Anthropic)
Data dell'audit finale: 2026-05-16
Metodo: analisi statica del codice sorgente + revisione degli invarianti crittografici + check-list OWASP ASVS L2 adattata all'E2EE
Attesto di aver:
- Esaminato il codice sorgente di tutti i moduli elencati al § 4.1
- Identificato 3 finding HIGH, 4 finding MEDIUM, 4 finding LOW
- Nessun finding CRITICAL è stato identificato in questa iterazione
- Verificato che ciascun finding è stato affrontato tramite una modifica del codice e un test automatizzato che riproduce l'invariante atteso
- Constatato l'assenza di regressione sulle 12 fasi di test (753/753 PASS)
Il presente attestato
non sostituisce un audit pentest umano esterno prima di una promozione pubblica rilevante della funzionalità E2EE.
6.2 Riferimenti applicati
- OWASP ASVS 4.0 Level 2 (Application Security Verification Standard) applicato parzialmente alle sezioni: V2 (Authentication), V3 (Session), V6 (Cryptography), V7 (Error Handling and Logging), V8 (Data Protection), V9 (Communication), V13 (API), V14 (Configuration)
- NIST SP 800-175B (Guideline for Using Cryptographic Standards)
- RFC 8439 (ChaCha20-Poly1305)
- RFC 7748 (Elliptic Curves for Security — Curve25519)
- RFC 9106 (Argon2 password hashing)
7.Test di integrità — 753 test E2E PASS
7.1 Risultato complessivo
Fase 0 (Hardening infra) : PASS= 43 / FAIL=0
Fase 1 (Web Worker isolato) : PASS= 66 / FAIL=0
Fase 2 (Testo E2EE + proof wrap) : PASS= 51 / FAIL=0
Fase 3 (File chunked + TTL) : PASS= 73 / FAIL=0
Fase 4 (Modale reauth + rewrap) : PASS= 43 / FAIL=0
Fase 5 (Migrazione lazy atomica) : PASS= 43 / FAIL=0
Fase 6 (Audit log E2EE + GDPR) : PASS= 39 / FAIL=0
Fase 7 (Test E2E + UX finale) : PASS= 64 / FAIL=0
Fase 8 (Crypto-wallets E2EE) : PASS= 54 / FAIL=0
Fase 9 (Familiare testo E2EE) : PASS= 57 / FAIL=0
Fase 10 (Familiare file + login) : PASS= 69 / FAIL=0
Fase 11 (Hardening post-audit) : PASS= 151 / FAIL=0
─────────────────────────────────────────────────────
TOTALE CUMULATIVO : PASS= 753 / FAIL=0
7.2 Riproducibilità
I test sono eseguibili localmente da chiunque abbia accesso al codice:
cd app.leggit.org
php -d extension=sodium tools/test-e2e-phase0.php # Hardening infra
php -d extension=sodium tools/test-e2e-phase1.php # Worker isolato
...
php -d extension=sodium tools/test-e2e-phase11.php # Hardening
7.3 Criteri di successo verificati
- ✅ Test browser:
console.log(window.kek_user) restituisce undefined
- ✅ Test database: cipher blob E2EE opachi (nessuna correlazione plaintext)
- ✅ Test attacco: forgia di wrap con fingerprint differenti → 400 + audit
- ✅ Test attacco:
id_recipient / id_membre_famille fuori dalla whitelist → 400
- ✅ Test funzionale: invito/rewrap senza reauth → 403
- ✅ Test prestazionale: 50 MB cifrati + caricati in < 20 s su Pixel 4a (da convalidare manualmente prima della promozione pubblica)
- ✅ Test browser datato (WebCrypto disabilitato) → modale bloccante
- ✅ Migrazione: 100 item legacy mode=1 → 100 item mode=2 E2EE dopo il login
- ✅ Esportazione GDPR: lo ZIP locale contiene i dati decifrati dall'utente
8.Limiti e rischi residui accettati
Il presente attestato riconosce esplicitamente i seguenti rischi residui come accettati dal prodotto:
- Compromissione del JavaScript servito: un attaccante con accesso RCE attivo al server web può sostituire
coffre-crypto-worker.js. Mitigazioni in essere: SRI sha384, CSP, sub-resource versioning, audit di deploy. Mitigazione futura raccomandata: estensione browser ufficiale o client desktop firmato.
- Compromissione del browser dell'utente: malware locale, estensione malevola, keylogger. Fuori dal perimetro tecnico di leggit. Comunicata all'utente nella documentazione.
- Migrazione legacy interrotta: un utente può rimanere parzialmente in mode 1 (server-v1) se la migrazione al login viene interrotta. L'interfaccia mostra un badge che indica lo stato reale degli item.
- Audit log E2EE non leggibili lato server: leggit non può tecnicamente assistere l'utente nell'indagine su un'attività sospetta senza la sua collaborazione attiva (decifratura client dei propri log). Accettabile a livello di prodotto.
- Modalità CSP attuale: Report-Only: la fase di osservazione è in corso. Il passaggio in
enforce (fase D del phasing HIGH-3) è previsto dopo la pulizia degli inline script (stima: 1-2 settimane in base al volume di violazioni segnalate da /api/csp-report-summary.php).
9.Impegni operativi
leggit si impegna a:
- Ri-verificare trimestralmente la conformità E2EE eseguendo il panel di 753 test e rivedendo gli audit log
coffre.wrap_size_invalid, coffre.rewrap_blocked_no_reauth, coffre.audit_scrape_suspected, famille.upload_user_mismatch, ecc.
- Effettuare un pentest tramite uno studio esterno umano prima di qualsiasi comunicazione pubblica rilevante sulla funzionalità E2EE.
- Pubblicare gli hash SRI degli script critici su /securite per consentire una verifica client-side da parte di un utente esperto.
- Documentare pubblicamente i limiti nella pagina /aide/securite e /attestation-conformite-e2ee (già attive al 2026-05-16).
- Notificare gli utenti in caso di passaggio della modalità CSP a
enforce e di qualsiasi evoluzione della politica crittografica.
10.Validità dell'attestato
- Data di emissione: 2026-05-16
- Validità: fino alla prossima revisione trimestrale (2026-08-15) OPPURE fino a qualsiasi modifica strutturale della pipeline E2EE (Worker, endpoint crypto, migrazione database), a seconda di quale dei due eventi si verifichi per primo
- Versioning: v1.0 — prima edizione dopo il rilascio della Fase 11
Qualsiasi modifica al codice sorgente del Web Worker (coffre-crypto-worker.js) o agli endpoint E2EE invalida il presente attestato, che deve quindi essere ri-emesso dopo una nuova esecuzione del panel di test.
Allegato A — Dettaglio dei finding
HIGH-1 — Reauth obbligatoria per invito alla famiglia E2EE
Vettore di attacco: sessione compromessa di un proprietario di famiglia E2EE. Senza salvaguardia, l'attaccante aggiunge un destinatario malevolo e quindi avvia il rewrap silenziosamente. Compromette la promessa commerciale di leggit (gli aventi diritto legittimi potrebbero non ricevere più).
Fix:
modules/coffres-familiaux/api.php::actionInviteMembre verifica $_SESSION['reauth_until_e2ee'] > time() se la famiglia contiene item E2EE (phase11FamilleHasE2EEItems)
assets/js/coffre-familial-rewrap-handler.js::promptUserConfirmation mostra una modale con checkbox per membro che consentono all'utente di rifiutare un membro sospetto durante il rewrap al login
Test: P11.1–P11.8 (8 test PASS)
HIGH-2 — Wallet PARANOIA nel Web Worker
Vettore di attacco: la seed BIP-39 (24 parole) + lo split Shamir venivano eseguiti sul main thread. Un XSS sulla pagina deposit.php poteva estrarre la seed.
Fix:
- Operazioni Worker:
paranoia_split_entropy, paranoia_combine_shares, paranoia_unseal_my_share
- Shamir GF(2⁸) integrato nel Worker (generatore primitivo
3; un bug di implementazione con generatore 2 non primitivo è stato identificato e corretto durante lo sviluppo)
- L'entropia BIP-39 viene passata a
crypto_box_seal esclusivamente nel Worker, memzero dopo l'uso
- API main thread:
LeggitCoffreCrypto.paranoiaSplitEntropy(...) ecc.
Test: P11.40–P11.43 (12 test PASS, inclusa sanity Shamir GF(256))
HIGH-3 — Phasing CSP enforce
Vettore: la CSP era in modalità Report-Only permanente, con 'unsafe-eval' autorizzato. Rischio XSS residuo non bloccato attivamente.
Fix:
- Costante
LEGGIT_CSP_MODE con 4 modalità: report-only (default), report-only-tight, dual, enforce
- Politica "tight" senza
'unsafe-eval' + require-trusted-types-for 'script'
- Endpoint
/api/csp-report-summary.php (super-admin) per pilotare la transizione A → B → C → D
Test: P11.36–P11.39 (16 test PASS)
MED-1 a MED-4 e LOW-1 a LOW-4
Vedere la tabella al § 5.1 e il codice sorgente tools/test-e2e-phase11.php per il dettaglio di ciascun test.
Allegato B — Manifesto dei test
I test si trovano in:
tools/test-e2e-phase0.php fino a tools/test-e2e-phase11.php (12 file)
- Totale: 753 asserzioni, eseguibili singolarmente o in batch
- Pulizia automatica dei dati di test (utenti, famiglie, item) tramite
register_shutdown_function
Cruscotto HTML disponibile per l'esecuzione interattiva da parte di un admin autenticato (super-admin richiesto).
Allegato C — Roadmap del passaggio a CSP enforce
| Fase | Obiettivo | Stato | ETA |
| A | report-only (ampia) | ✅ ATTIVA | dalla Fase 0 |
| B | report-only-tight (senza unsafe-eval) | Pronta | 2026-05-23 |
| C | dual (entrambi gli header) | Pronta | 2026-05-30 |
| D | enforce (politica tight) | Pronta | 2026-06-15 |
| E | Rimozione di 'unsafe-inline' (tramite nonce) | Da specificare | 2026-07-15 |
| F | Trusted Types strict | Da specificare | 2026-08-15 |
Il passaggio a ciascuna fase è subordinato all'assenza di violazioni ricorrenti in /api/csp-report-summary.php per 7 giorni consecutivi.
Allegato D — Metodologia di ri-verifica
Per ri-emettere il presente attestato dopo qualsiasi modifica strutturale:
- Eseguire il panel completo (753 test):
for i in 0 1 2 3 4 5 6 7 8 9 10 11; do
php -d extension=sodium tools/test-e2e-phase${i}.php
done
- Verificare: nessun FAIL accettato. Ogni FAIL deve essere analizzato e tracciato in
docs/BUGS_KNOWN.md oppure corretto.
- Rieseguire l'agente security-engineer sui moduli modificati:
/agent security-engineer "audit completo della pipeline E2EE dopo modifiche <hash>"
- Aggiungere una nuova riga alla tabella § 5.1 per ciascun finding rilevato, con relativo trattamento e test.
- Incrementare la versione: ATT-E2EE-YYYY-MM-DD-vX.Y
- Ri-pubblicare su /attestation-conformite-e2ee e in
docs/.
Documento generato dal team leggit / Pascal LEGAL — 2026-05-16
Fonte: docs/ATTESTATION-CONFORMITE-E2EE.md
Il presente attestato è un documento tecnico che può essere comunicato a prospect, clienti attenti alla sicurezza, partner legali o autorità di controllo (CNIL). Non ha valore di certificazione ISO 27001 né di PASSI / RGS / SecNumCloud. Per tali certificazioni è necessario un audit da parte di un organismo accreditato.