Security & Transparency Report

Aggiornato al 2026-09-04. Ogni riga di questa pagina è o un numero, o una cosa che potete verificare da soli. Dove non c'è niente da verificare, lo scriviamo.

Il canarino

Una dichiarazione datata e firmata con la stessa chiave ML-DSA-65 degli aggiornamenti: nessuna richiesta di dati, nessun ordine di consegna chiavi, nessuna backdoor imposta, nessun ordine di silenzio. Se smette di essere aggiornata, o la firma non torna, trattatelo come una risposta.

canary.txt · firma · chiave pubblica del produttore

# una volta, nella cartella dove hai scaricato i tre file:
npm i @noble/post-quantum

node -e '
const {ml_dsa65}=await import("@noble/post-quantum/ml-dsa.js"),fs=await import("fs");
const b=fs.readFileSync("canary.txt"),s=fs.readFileSync("canary.txt.sig","utf8").trim();
const pk=fs.readFileSync("vendor-pubkey.txt","utf8").trim();
console.log(ml_dsa65.verify(Buffer.from(s,"hex"),b,Buffer.from(pk,"hex")) ? "FIRMA VALIDA" : "FIRMA NON VALIDA");'

Correzione, 5 settembre 2026. Fino a oggi questa riga aveva gli argomenti nell'ordine sbagliato (verify(pk, msg, sig)): chi seguiva le nostre istruzioni per controllare il canarino non otteneva «FIRMA NON VALIDA», otteneva un errore — e poteva ragionevolmente concludere che il canarino non tornasse. La firma era sempre stata valida; era rotta l'istruzione, cioe' l'unica cosa che serve a non doverci credere sulla parola. L'ordine giusto in @noble/post-quantum 0.6 e' verify(firma, messaggio, chiavePubblica). Ce ne siamo accorti riscrivendo il firmatario in Rust e provando l'interoperabilita fra le due implementazioni.

La firma è prodotta da un binario Rust (fips204) e verificata qui sopra da una libreria JavaScript indipendente (@noble/post-quantum): due implementazioni diverse dello stesso standard, che è l'unico modo per accorgersi se una delle due sbaglia.

Il rapporto, riga per riga

VoceStatoCome lo verificate
Richieste di dati da autorità0Dichiarato nel canarino, firmato ML-DSA-65 e datato.
Richieste soddisfatte0Idem. E se anche fossimo costretti, sotto c'è l'elenco esatto di cosa potremmo consegnare.
Ordini di consegna chiavi0Le chiavi degli utenti non sono sul server: non esiste un file da consegnare.
Backdoor o codice imposto0Verificabile: la build è riproducibile dal sorgente pubblicato.
Binari di terze parti nell'APK0 · dalla v2.2.0Il nucleo Xray si compila da sorgente a ogni build. Prima era un .aar scaricato.
Build riproducibilesì · dalla v2.1.01.662 voci su 1.662 identiche partendo dal solo archivio pubblicato (verifica automatica ogni notte).
Sorgente pubblicatosì · dalla v2.1.0Un archivio deterministico per ogni versione, con le istruzioni di verifica dentro.
Firma dell'APKRSA 4096Chiave di release nostra, mai uscita dal server. Impronta qui sotto.
Firma degli aggiornamentiML-DSA-65Post-quantistica. L'app rifiuta ogni aggiornamento la cui firma non torna.
Firma del canarinoML-DSA-65 · RustProdotta da un binario Rust con fips204: nessun interprete nel percorso.
Codice del serverRustIl servizio in produzione è Rust (Axum). Anche il canarino e questo rapporto ora lo sono.
Derivazione del PIN sul telefonoPBKDF2-HMAC-SHA512 · 600.000Nativo di Android, fuori dal thread dell'interfaccia. Prima era un solo SHA-512.
Doppio fondo (negazione plausibile)sì · dalla v2.5.0Nessun verificatore del PIN esca è salvato: non esiste un dato che provi che esista.
Audit esterno indipendentenessunoNon l'abbiamo. Continuiamo a scriverlo invece di sperare che non lo chiediate.
Google Play Services / FCMpresente, non necessarioLe notifiche funzionano senza; il file di configurazione resta nell'APK ed è pubblico.
Analytics, telemetria, crash reportingnessunoNessun SDK di terze parti che chiami casa. Verificabile nel sorgente.

Cosa il server può vedere

Cosa il server non può vedere

Cronologia della verifica riproducibile

Non pubblichiamo il risultato finale come se fosse venuto bene al primo colpo. È andata così, e il modo in cui è andata è la parte utile: ogni tentativo ha trovato un difetto vero che nessuna dichiarazione avrebbe trovato.

TentativoDifferenzeCausa trovata
1°11 voci su 1663 I compilatori nativi incorporano il percorso assoluto dei sorgenti dentro le .so: codice identico, librerie diverse. Risolto compilando sempre da un percorso canonico (/lattice-src, bind mount), noi e chi verifica.
2°3 voci su 1663 Mancava modules/app-disguise nell'archivio: un modulo nativo locale fuori da src/ e android/. Il progetto compilava comunque — senza quel modulo, nove classi in meno nel dex.
3°0 voci su 1663 Riproducibile. APK compilato solo dall'archivio pubblicato, in un'altra cartella, con un'altra chiave di firma: identico byte per byte a quello pubblicato.
v2.2.00 binari importati Il nucleo Xray non è più un .aar scaricato: si compila da sorgente con Go e gomobile a ogni build, e due compilazioni danno lo stesso file.
v2.3.1 →verifica ogni notte Un cron ricompila l'APK pubblicato in un container pulito e pubblica il risultato in verify-status.json. Non ce lo dobbiamo ricordare noi.

Ancore di fiducia

Firma APK (RSA 4096, SHA-256 del certificato)
  45:11:53:AA:02:9A:67:B9:61:29:DA:64:76:27:3B:3D:9E:6F:A4:34:25:2F:CD:2C:8E:17:2B:9D:8D:AF:F1:C3

Chiave del produttore per gli aggiornamenti e per il canarino (ML-DSA-65, pubblica)
  9d35f3390f4df55ab631567b00ed9628b93b9c2e1b988ab07e6000f18010da40…

Nucleo Xray compilato da noi (libv2ray.aar, versione 2.1.0 — importato)
  670cf11d9d10a6bb6548ac4f593acfa4339155732f6f8de4d45923f30a74deed

La chiave di firma dell'APK è cambiata con la v2.2.0: la precedente era la chiave di debug di Android, la cui password è pubblica. Chi aveva una versione precedente deve disinstallare e reinstallare: Android, giustamente, non accetta un aggiornamento firmato con una chiave diversa. Lo scriviamo qui perché è un fastidio reale, causato da un errore reale, e nasconderlo sarebbe peggio del fastidio.

Cosa questa pagina non è

Non è una certificazione. Non è un audit. È un rapporto scritto da chi gestisce il servizio, che è esattamente la persona di cui non dovreste fidarvi sulla parola. Per questo ogni riga rimanda a qualcosa che potete controllare da soli: un archivio da ricompilare, una firma da verificare, un file da confrontare. Le righe che non rimandano a niente — «nessuna richiesta ricevuta», «nessun audit esterno» — sono dichiarazioni, e come tali valgono quanto la firma che le accompagna e la data che portano. Non di più.

← Scarica e verifica l'app  ·  Torna al sito