Modello delle minacce
Il documento completo: che cosa si protegge, da chi, con quali mezzi e dove la protezione finisce. Dove una cosa non è verificata o non è garantita, lo diciamo.
Documento di riferimento per la versione 0.1.0 (pre-release). Non è stato verificato da terzi.
Per segnalare un problema di sicurezza scrivi a security@shelleasy.app.
Questo documento dice da chi ShellEasy protegge i tuoi dati, con quali mezzi e dove la protezione finisce. Ogni affermazione è stata controllata sul codice; dove una cosa non è verificata o non è garantita, lo si dice.
Ultimo controllo: versione 0.1.0 (pre-release). Se cambia la crittografia, i confini di fiducia o il formato dei file, questo documento va aggiornato nella stessa modifica.
1. Che cosa si protegge
| Bene | Dove sta | Quanto vale |
|---|---|---|
| Chiavi private SSH, passphrase, password dei server | colonne identities.* del database | Il più alto: danno accesso ai server |
| Elenco di host, nomi utente, cartelle, tag, snippet, regole dei tunnel | database | Alto: rivela l'infrastruttura e le abitudini |
| Master Password | solo nella testa dell'utente (opzionalmente nel portachiavi, dietro la biometria) | È l'unica chiave di tutto |
Chiavi dei server già accettate (known_hosts) | database | Impediscono l'attacco man-in-the-middle |
File di esportazione .shelleasy | dove l'utente li salva | Contengono tutto, segreti compresi |
| Contenuto delle sessioni e dei file trasferiti | memoria, rete, disco di destinazione | Fuori dal perimetro del vault (vedi §7) |
2. Chi è l'avversario
Considerato:
- Chi ottiene i file dell'app senza la Master Password — computer rubato o smarrito, backup copiato (Time Machine, cloud, disco esterno), altro utente dello stesso computer, malware che legge file ma non ha ancora la password. È lo scenario principale per cui esiste il vault.
- Chi può leggere e modificare i file mentre l'app è chiusa — stessa categoria, ma attivo: può alterare o sostituire file nella cartella dei dati.
- Un server SSH/SFTP ostile o compromesso a cui l'utente si collega: può mandare nomi di file e cartelle maligni, output di terminale con sequenze speciali, chiavi cambiate.
- Un file
.shelleasyo un~/.ssh/configcostruito ad arte e fatto importare all'utente. - Un'altra macchina della rete che prova a usare un tunnel o un proxy SOCKS lasciato in ascolto.
- Chi guarda sopra la spalla o prende un computer sbloccato e lasciato incustodito per pochi minuti — mitigato solo dal blocco automatico (§5.4).
Non considerato (fuori dal perimetro):
- Un attaccante con il computer sbloccato e l'app aperta, o con codice in esecuzione come lo stesso utente mentre il vault è sbloccato: può leggere la memoria del processo, registrare la tastiera, chiedere all'app di esportare. Nessun vault locale resiste a questo.
- Un sistema operativo o un firmware compromessi.
- L'analisi dei canali laterali (tempi, consumo, cache) su un computer condiviso con chi li può osservare.
- Un attaccante che indovina una Master Password debole con un attacco offline costoso ma non impossibile (§5.1): la forza della protezione dipende dalla password scelta.
- La sicurezza dei server a cui ci si collega e della libreria SSH di Go (
golang.org/x/crypto/ssh), di cui ci si fida.
3. Come funziona la cifratura (in breve)
Master Password --Argon2id(salt, t=3, 64 MiB, p=4)--> KEK
KEK --AES-256-GCM, AAD "easyshell/vault/dek/v1"--> DEK avvolta (in vault.json, FUORI dal database)
DEK --HKDF-SHA256, info "easyshell/db/v1"--> chiave del database
chiave del database --Adiantum, blocchi da 4 KiB--> intero file SQLite
DEK --AES-256-GCM, AAD "tabella|colonna|id"--> singoli segreti (secondo livello, dentro il database)
vault.jsoncontiene solo versione, salt, parametri Argon2id e la DEK avvolta. Sta fuori dal database perché serve per aprirlo. Scritto in modo atomico, permessi 0600.- Intero database cifrato con Adiantum (libreria
ncruces/go-sqlite3): nomi di host, username, cartelle, tag, struttura delle tabelle, indici, file di giornale di SQLite. I file temporanei usano chiavi casuali etemp_store=memoryevita di scriverne. - Secondo livello per i segreti: ogni chiave privata, passphrase e password è cifrata con AES-256-GCM e legata al proprio record dall'AAD
tabella|colonna|id. Un blob copiato in un altro record non si apre (verificato nei test). Ogni punto del codice che cifra o decifra un segreto passa davault.FieldAADcon l'iddella riga (controllato:data.go,sessions.go,sync.go). - Il frontend non riceve mai i segreti. Le identità arrivano all'interfaccia con soli indicatori (
hasKey,hasPassphrase,hasPassword); la chiave privata si indica con un percorso scelto dal dialogo nativo e non passa dalla webview. Controllato ininternal/core/data.goe nei binding diapp.go. - File
.shelleasy: intestazione (magic, versione, parametri Argon2id, salt, nonce) autenticata come AAD, poi AES-256-GCM sul JSON compresso. Il dato in chiaro dopo la decompressione è limitato a 64 MiB e il file letto a 128 MiB.
4. Che cosa resta in chiaro (metadati)
Chi ha i file ma non la password non legge i dati, ma può vedere:
- Che esiste una cartella dati di ShellEasy, il nome dei file (
vault.json, il database,backups/) e le loro dimensioni e date di modifica. La dimensione del database dà un'idea approssimativa di quanti dati contiene; le date dicono quando l'app è stata usata e quando si sono salvate modifiche. - I parametri del vault: salt, costi di Argon2id, versione del formato (
vault.json). Non sono segreti, ma dicono quanto costa un tentativo di indovinare la password. - La lunghezza dei segreti in modo approssimativo dentro il database è nascosta (cifrati in blocchi da 4 KiB), ma la dimensione complessiva no.
- Dei file
.shelleasy: la dimensione (≈ quantità di dati dopo la compressione) e i parametri dell'intestazione. - Fuori dal vault, a seconda del sistema: le ultime cartelle usate, gli elementi recenti del sistema operativo, la cronologia della shell locale, i file temporanei di «Modifica remota» mentre sono aperti (cartella temporanea dell'utente 0700, file 0600, rimossi alla chiusura).
- Le preferenze dell'interfaccia salvate in
localStoragedella webview (chiavies.*): non contengono dati dei server.
Il portachiavi di sistema, se si attiva lo sblocco con impronta, contiene la Master Password (§5.5).
5. Limiti e scelte, uno per uno
5.1 La Master Password è l'unica barriera
L'attaccante del §2 (1) può tentare le password offline. Ogni tentativo costa un Argon2id con 64 MiB, 3 passate, 4 thread: rende l'attacco lento ma una password debole resta debole. La lunghezza minima richiesta è 8 caratteri; una frase di 4-5 parole è molto meglio di una parola con simboli. Non c'è recupero: se la password si perde, i dati sono persi (scelta di progetto, «zero-knowledge»). Per questo si consiglia un export .shelleasy.
I parametri non possono scendere sotto il minimo (3 passate, 64 MiB, 4 thread) né salire oltre un tetto (16 passate, 1 GiB, 16 thread): vedi §6.1.
5.2 Cifratura del database: che cosa Adiantum dà e che cosa no
Adiantum è una cifratura da disco (il file è diviso in blocchi da 4 KiB, ognuno cifrato per intero, con lo stesso testo in chiaro nello stesso punto che dà lo stesso testo cifrato). Il suo modello di minaccia è: l'avversario vede più copie del disco e impara soltanto quali blocchi sono cambiati. Ne seguono tre limiti, che valgono anche per ShellEasy:
- È deterministica. Chi ha due copie del database (per esempio due backup di giorni diversi) vede quali blocchi da 4 KiB sono cambiati, quali no e quali sono tornati a un valore precedente, ma non che cosa c'è dentro. Un
VACUUMricostruisce il file e cancella gran parte di questa informazione; ShellEasy non lo esegue di sua iniziativa. - Non autentica le pagine. Un blocco modificato si decifra in dati casuali (l'intero blocco, non un singolo bit), cosa che SQLite di norma rileva come corruzione, ma non è una garanzia: Adiantum non dichiara protezione da manomissione. Un attaccante che modifica il file può quindi, nel caso peggiore, corrompere o far perdere dati, oppure riproporre blocchi o interi file genuini già esistiti (vedi il punto 3); senza la chiave non può costruire contenuti scelti da lui.
- Non protegge dal ripristino di una copia vecchia. Chi sostituisce il database con una sua versione precedente (stessa chiave) ottiene un database valido ma vecchio: host e identità cancellati o modificati dopo quella data ricompaiono come erano. L'app non se ne accorge.
Che cosa protegge comunque l'integrità dei segreti. Le credenziali non dipendono da Adiantum per l'autenticità: sono blob AES-GCM con AAD legato al record. Un blocco manomesso o un blob spostato fa fallire l'apertura del segreto (errore, non dato sbagliato). Un blob vecchio ma genuino dello stesso record (rollback di un singolo segreto o dell'intero file) invece si apre: è il limite del punto 3.
Conseguenze pratiche per l'utente:
- Una copia del database non è un backup sicuro da tenere dove altri possono scriverla: un backup serve a recuperare, non a fidarsi. I backup dell'app (
backups/, ultime 10 copie fatte prima di ogni importazione) sono copie cifrate e con permessi 0700/0600. - Il database non viene mai ricostruito o sostituito in automatico da una copia non verificata.
- Il supporto di recupero più solido è l'export
.shelleasy(AES-GCM, autenticato per intero).
5.3 vault.json: MAC e contatore di versione — valutati e non adottati
Si è valutato un controllo di integrità del file, per esempio un MAC su vault.json più un contatore di versione. Conclusione:
- Un MAC su
vault.jsonnon aggiunge nulla. Il file è già autenticato per quello che conta: la DEK avvolta è un blob AES-GCM con AAD; qualunque modifica a salt, parametri o blob cambia la chiave derivata o rompe l'autenticazione, e lo sblocco fallisce (ErrWrongPassword), senza mai produrre una chiave sbagliata. L'attaccante del §2 (2) può impedire lo sblocco (cancellare o corrompere il file), non dirottarlo. - Un contatore di versione non regge senza un punto fermo fuori dalla portata dell'avversario. Se il contatore sta nella stessa cartella, chi può ripristinare un file vecchio ripristina anche il contatore. Servirebbe un'àncora nel portachiavi del sistema (che esiste solo dove il portachiavi è disponibile e legato alla biometria, oggi solo macOS) e resterebbe valido solo per chi non può modificare anche quello. Non si adotta ora; resta come idea registrata nell'issue di follow-up (§9).
- Il ripristino di un
vault.jsonvecchio ha però un effetto reale: la DEK non cambia quando si cambia la Master Password (si riavvolge soltanto, scelta di progetto per non ricifrare tutto). Quindi chi conosce la vecchia password e ha una copia vecchia divault.json(per esempio in un backup) e una copia del database può ancora aprirla, anche dopo che l'utente ha cambiato password per il sospetto che fosse compromessa. Se pensi che la password sia stata compromessa, cambiarla non basta: crea un nuovo vault con una password nuova e importa un export.shelleasy. Una rotazione della DEK al cambio password è registrata nel follow-up (§9).
5.4 Blocco, sessioni aperte e memoria
- Blocco automatico dopo 15 minuti di inattività dell'interfaccia (valore fisso, non ancora configurabile) e a richiesta. Il blocco chiude il database e azzera la DEK. Le sessioni SSH già autenticate e i tunnel restano attivi dopo il blocco (decisione D3, per non interrompere un lavoro in corso); per aprirne di nuove serve sbloccare. Chi vuole chiudere tutto deve chiudere le sessioni o l'app.
- Zeroizzazione non garantita. L'app tiene i segreti come
[]bytee li azzera dopo l'uso (vault.Zero; il blocco azzera la DEK), ma Go non assicura che il garbage collector o la crescita dello stack non lascino copie, e non tutti i dati passano per[]byte:- la Master Password arriva dall'interfaccia come stringa (il ponte tra webview e Go la trasporta così) e ne restano copie nella memoria della webview e dei buffer del ponte che l'app non controlla;
- la chiave del database passa al driver SQLite come stringa esadecimale (
PRAGMA hexkey, immutabile in Go) e vive nella memoria del driver finché il processo non termina; chiudere il database non è una cancellazione verificata; - le chiavi private lette per autenticare una sessione restano nella memoria del client SSH finché la sessione esiste;
- la memoria non è bloccata (
mlock) e può finire nello swap o in un file di crash/ibernazione. Quindi chi può leggere la memoria del processo o un dump del disco/RAM può trovare segreti anche dopo il blocco. È un limite dichiarato, non un difetto isolato: vedi anche §2.
- Nei log e nei messaggi d'errore non compaiono segreti (controllato per ricerca nel codice: nessun log applicativo; l'unico
log.Fatalfè inmain.goe stampa l'errore di avvio di Wails).
5.5 Sblocco con impronta
La Master Password viene custodita dal sistema dietro la biometria e non è mai scritta su disco dall'app. Su macOS, con app firmata e profilo di provisioning, il segreto sta nel portachiavi con kSecAccessControlBiometryCurrentSet e WhenUnlockedThisDeviceOnly: il sistema chiede l'impronta alla lettura e il segreto si invalida se cambiano le impronte registrate. Una build non firmata o con firma ad-hoc usa un livello più debole (app): il segreto sta comunque nel portachiavi e un altro programma non lo legge senza il consenso del sistema, ma la richiesta dell'impronta è un controllo dell'app e non del sistema; l'interfaccia indica il livello in uso. Lo sblocco usa lo stesso Unlock: non esiste un percorso alternativo verso la chiave. Non verificato su Windows (Windows Hello) e Linux: non sono ancora implementati.
5.6 Chiavi dei server (TOFU)
Alla prima connessione l'app mostra l'impronta SHA256 e chiede conferma; la chiave si salva sul dispositivo. Se in seguito il server presenta una chiave diversa (o di un altro tipo rispetto a quelle note) la connessione è bloccata con errore esplicito, senza possibilità di accettare al volo (HostKeyChangedError). Non esiste nessun InsecureIgnoreHostKey nel codice (controllato). Limite intrinseco di TOFU: se la prima connessione è già intercettata, l'attacco non si vede; la verifica dell'impronta con un canale diverso resta all'utente. Le chiavi note non si sincronizzano tra dispositivi: la fiducia è per dispositivo.
5.7 Un server SFTP ostile
Il server sceglie i nomi dei file che elenca. Finché i nomi sono usati solo per mostrarli, il rischio è basso (l'interfaccia non usa v-html/innerHTML: controllato). Il rischio reale è nei trasferimenti: scaricando una cartella, un nome come ..\..\Startup\x.exe su Windows, dove \ separa le cartelle, usciva dalla cartella scelta dall'utente. Corretto con questa revisione (§6.2): i nomi letti da un lato remoto vengono controllati prima di costruire qualunque percorso locale; quelli pericolosi si saltano e compaiono nelle note del trasferimento. Il client SFTP scarta già . e .. e usa path.Base per i nomi. I link simbolici remoti non vengono mai seguiti nei trasferimenti.
Inoltre i file in arrivo ora nascono con permessi 0600 e prendono i permessi dell'originale solo a copia finita, così un file riservato non è leggibile da altri utenti mentre è parziale.
5.8 Un file .shelleasy ostile
L'intestazione è autenticata, ma le sue parti numeriche vengono lette prima di verificare la password. Un file costruito per far allocare molta memoria o calcolare per ore andava rifiutato in anticipo: corretto con il tetto ai parametri (§6.1). Dopo l'apertura: dimensione massima del file letto (128 MiB), della busta decompressa (64 MiB, contro le «bombe» di decompressione), numero massimo di elementi (200.000), validazione di ogni campo, nessun contenuto eseguito. L'importazione mostra un'anteprima, fa una copia di sicurezza del database ed è un'unica transazione. Un file ostile fornito da chi conosce la password può comunque inserire host o snippet con contenuto scelto da lui: gli snippet rifiutano i caratteri di controllo (ESC, C1, DEL), che nel terminale servirebbero a nascondere o alterare ciò che viene incollato; l'utente vede comunque il comando finale prima di inserirlo, se ci sono parametri.
5.9 Importazione da ~/.ssh/config
Il file viene letto e interpretato (compresi Include annidati con un limite di profondità e rilevamento dei cicli) ma non viene eseguito niente: ProxyCommand non è supportato, l'host viene segnalato come non importabile e l'app non lancia mai quel comando.
5.10 Tunnel e proxy
Di serie si ascolta solo su 127.0.0.1. Se si sceglie un indirizzo raggiungibile dalla rete, l'app mostra un avviso nel dialogo e nell'elenco (con SOCKS5 sarebbe un proxy aperto a tutti, senza autenticazione). Un tunnel locale in ascolto su 127.0.0.1 è comunque raggiungibile da qualunque programma dello stesso computer (altri utenti compresi, su sistemi multiutente): è il comportamento di ssh -L e viene documentato, non è mitigato. Bloccare il vault non ferma i tunnel.
5.11 La webview e il frontend
Il frontend è una pagina locale servita dal pacchetto dell'app, senza contenuti remoti, senza v-html/innerHTML/eval (controllato), con gli strumenti di sviluppo di Nuxt disattivati (e l'ispettore della webview, che Wails abilita solo nelle build di debug). L'output dei terminali passa da xterm.js come testo. Manca una Content-Security-Policy: oggi non sarebbe banale impostarla perché la pagina generata contiene script inline (mappa degli import e dati iniziali); renderla stretta richiede hash o nonce. È registrata come miglioria (§9): sarebbe una difesa in profondità, non la correzione di un buco noto.
5.12 Aggiornamenti e supply chain
Nessun aggiornamento automatico e nessuna telemetria: il frontend non carica risorse remote e le uniche connessioni in uscita sono quelle verso gli host SSH che l'utente apre e, solo se lo attiva, verso il server dell'assistente AI (§5.13). L'unico client HTTP dell'app è internal/ai. Le dipendenze Go e npm sono bloccate dai file di lock; appimagetool è fissato per versione e checksum. Le build di rilascio non sono ancora firmate (macOS/Windows): finché non lo sono, l'integrità del pacchetto scaricato si verifica solo con SHA256SUMS. Non c'è ancora una scansione automatica delle dipendenze vulnerabili: da aggiungere (§9).
5.13 Assistente AI locale
L'assistente (spiega, da descrizione a comando, riassumi, aiuto snippet) è spento di serie e apre un canale nuovo: porta testo del terminale a un server di modelli scelto dall'utente. Nessun modello è incluso e nessuna richiesta parte senza che l'utente l'abbia vista e inviata.
Dove può andare il testo.
- Di serie solo al computer dell'utente: sono «locali» soltanto
localhoste gli indirizzi di loopback scritti per esteso (127.x,::1). Un nome qualsiasi è considerato remoto anche se oggi punta al computer locale, perché la risoluzione dei nomi può cambiare (DNS rebinding). - Un server non locale si può usare solo dopo una conferma esplicita, legata a quel nome e quella porta: cambiare server annulla la conferma. Con un server non locale e una chiave serve
https://. - Al momento della connessione, per i server locali, l'indirizzo effettivo deve essere di loopback, altrimenti la connessione è rifiutata. I reindirizzamenti non si seguono e le variabili d'ambiente dei proxy si ignorano.
- La chiave del server, se c'è, è cifrata nel database (AES-GCM con AAD dedicato), non esce mai verso l'interfaccia e non viene inviata a un server diverso da quello configurato. La configurazione non si sincronizza.
Che cosa parte. Ogni richiesta passa da un'anteprima costruita dal backend: il testo mostrato è quello che parte, byte per byte (testato), verso la destinazione mostrata (host, porta, modello); se le impostazioni cambiano dopo l'anteprima, l'invio è rifiutato. Prima dell'anteprima il testo e la richiesta passano da un filtro che oscura chiavi private, credenziali negli indirizzi, intestazioni di autorizzazione, token noti (GitHub, AWS, Slack, JWT…), valori di variabili con nomi come password, secret, token, api_key, opzioni di riga di comando (--password, sshpass -p, mysql -p…, curl -u) e le password e passphrase salvate nel vault se compaiono nel testo. L'anteprima dice che cosa è stato oscurato. Del terminale parte al massimo la coda di 12.000 caratteri.
Limiti del filtro (dichiarati anche nell'anteprima). Riconosce i segreti per forma e per valore noto: non può riconoscere una password scritta in chiaro senza un nome che la riveli, un segreto che non è nel vault, né informazioni sensibili che non sono segreti (nomi di host, indirizzi IP, percorsi, contenuto dei file, dati personali nell'output). Con un server non locale tutto questo esce dal computer. Per questo l'anteprima va letta.
Programmi che l'app avvia (rilevamento dei server). Il pulsante Avvia fa eseguire all'app un programma esterno, quindi è un punto da tenere stretto: si eseguono solo ollama serve e lms server start|stop, cercati con nome fisso nel PATH e in percorsi noti; nessun comando, argomento o percorso viene dall'utente, dall'interfaccia o dal modello. Ollama è avviato con OLLAMA_HOST=127.0.0.1:porta imposto, così non può mettersi in ascolto sulla rete; i processi avviati dall'app si chiudono con l'app e Ferma riguarda solo quelli. Rischio residuo: chi può già mettere un eseguibile chiamato ollama nel PATH dell'utente ottiene l'esecuzione quando l'utente preme Avvia (stesso livello di chi può già modificare l'ambiente dell'utente, §2). Il pulsante Scarica apre nel browser una pagina scelta da una tabella fissa nel backend (HTTPS, tre indirizzi ufficiali): l'app non scarica né installa nulla e non fa connessioni verso Internet; quello che si scarica e si installa lo decide l'utente nel browser. I comandi di lms non sono ancora provati su LM Studio reale.
Il modello è un'entità non fidata.
- Output del terminale ostile (prompt injection): un server o un file può far stampare testo che «dà ordini» al modello. Mitigazioni: il testo è racchiuso tra delimitatori che non può chiudere (sono neutralizzati) e le istruzioni dicono di trattarlo come dato; il modello non ha strumenti né accesso al sistema, quindi può solo produrre testo. Resta possibile che la spiegazione sia fuorviante o che il comando proposto sia dannoso.
- Comando proposto: non viene mai eseguito. Si inserisce nel prompt come incolla (senza Invio) e lo lancia l'utente; l'interfaccia mostra il comando in un riquadro, avvisa se ha più righe (la shell potrebbe non supportare l'incolla protetta) e ne toglie ogni carattere di controllo, ESC compreso, perché una risposta ostile non possa chiudere l'incolla protetta e far eseguire altro. Il rischio residuo è che l'utente prema Invio senza leggere.
- Risposta eccessiva o lenta: tetto di 256 KB per risposta, interruzione dopo 120 s senza dati, al massimo 3 richieste insieme.
Blocco del vault. Le richieste in corso si interrompono e le anteprime non ancora inviate sono dimenticate.
Non verificato: la compatibilità con i server reali (Ollama, LM Studio, llama.cpp) è provata con un server finto che imita l'API di OpenAI, non con i programmi veri; i formati dello streaming possono avere varianti non previste. Il filtro non è stato valutato su output reali di grandi dimensioni: i falsi positivi (testo oscurato senza motivo) sono possibili e preferibili ai falsi negativi.
6. Correzioni fatte con questa revisione
| # | Problema | Correzione | Prova |
|---|---|---|---|
| 6.1 | I parametri Argon2id di vault.json e delle buste .shelleasy avevano solo un minimo: un'intestazione ostile poteva chiedere gigabyte o ore di calcolo prima del controllo della password | Tetto: 16 passate, 1 GiB, 16 thread (vault.MaxTime/MaxMemoryKiB/MaxThreads, errore ErrHugeParams), applicato in creazione, sblocco, export e import | TestParamsCeiling, TestHugeParamsRejectedOnOpen: il rifiuto avviene in meno di un secondo, senza derivare chiavi |
| 6.2 | Nomi di file letti da un server SFTP usati senza controllo per costruire percorsi locali (uscita dalla cartella di destinazione su Windows; nomi riservati; flussi alternativi NTFS) | unsafeName / safeReadDir / checkTopName: scartati "", ., .., /, NUL e, su Windows, \ : * ? " < > |, caratteri di controllo, nomi che finiscono con punto/spazio e nomi di dispositivo (CON, NUL, COM1…); gli scartati finiscono nelle note del trasferimento | TestUnsafeName, TestSafeReadDirSkipsHostileNames |
| 6.3 | File locali creati con permessi 0644 durante lo scaricamento | Creazione con 0600; i permessi dell'originale si applicano a copia finita | TestLocalCreateIsPrivate |
La suite Go completa passa anche con -race. Non verificato: il comportamento di 6.2 su Windows reale (le regole Windows sono provate con una tabella, non su un file system Windows).
7. Fuori dal vault: ciò che l'app non protegge
- I dati che passano nelle sessioni (output del terminale, file trasferiti, ciò che si incolla): non sono cifrati a riposo e dipendono dal computer e dal server. Se si usa l'assistente AI, il testo che si decide di inviare esce dal vault verso il server dei modelli (§5.13). Il cursore di scorrimento del terminale e la shell locale usano la memoria e i file del sistema operativo.
- I file scaricati restano sul disco di destinazione con le protezioni di quel disco.
- Gli appunti del sistema: un segreto copiato resta negli appunti finché qualcosa non lo sostituisce.
- Gli export
.shelleasysono protetti dalla loro password per tutta la loro vita: conservarli come si conserverebbero le chiavi.
8. Revisione esterna
Raccomandazione: sì, prima della versione 1.0 e prima di raccomandare l'app per credenziali di produzione, limitata a un perimetro piccolo e chiaro:
internal/vault,internal/envelope,internal/storee il punto di sblocco (internal/core/core.go): circa 1.000 righe, una revisione di pochi giorni.- La gestione delle chiavi dei server (
internal/sshconn/hostkey.go) e dei trasferimenti (§5.7). - L'integrazione biometrica su macOS (
internal/biometric).
Non c'è un budget previsto: alternative a costo zero sono una revisione tra pari da parte di chi conosce Go e crittografia, la pubblicazione del codice con questo documento per attirare segnalazioni, e l'abilitazione degli avvisi di sicurezza di GitHub. Finché non è fatta una revisione, la dicitura onesta è: crittografia con primitive standard e scelte documentate, non verificata da terzi.
9. Follow-up aperti
- Rotazione della DEK al cambio password (opzionale): ricifrare il database e i segreti con una DEK nuova, così che una vecchia copia di
vault.jsonpiù la vecchia password non aprano più nulla. È un'operazione lunga e delicata (copia atomica, rollback in caso di errore). - Rilevare il ripristino di copie vecchie con un'àncora nel portachiavi del sistema (§5.3).
- Content-Security-Policy per la webview (§5.11).
- Scansione automatica delle dipendenze (
govulncheck,pnpm audit) nei controlli (§5.12). - Firma dei pacchetti e verifica dell'AppImage/Windows.
- Windows Hello e sblocco biometrico su Linux.