vault: files sealed under a password that is never stored

Argon2id turns the vault password into a key that seals a random master
key; only the sealed copy is kept. Each file gets its own key, sealed
under the master key in its header with its media id, and its bytes are
AES-256-GCM STREAM records, so a swapped, cut or altered file refuses
to open. Uploads with ?encrypt=true are sealed as they stream in.

:vault, :unlock, :lock, :passwd, :encrypt, :decrypt and :upload! drive
it. The vault locks after WORKSPACE_VAULT_IDLE_MIN idle minutes, on
logout and on restart; while locked, sealed files answer 423, share
links never serve them, and :export! refuses rather than leave them out.

Sealing an existing file rewrites it under a new id and retires the old
one: the mirror deletes the plaintext from Proton Drive outright,
trash included, whatever WORKSPACE_BACKUP_PRUNE says, and drops it from
the readable files/ view. The mirror now compares on-disk sizes, so
sealed files are not re-hashed every run.
This commit is contained in:
nvrl committed 2026-10-19 16:03:59 +02:00
1 parent 364ba61963
commit a641d9b6ba
33 files changed
+1573 -54

No files matched your search

+12
View File
@@ -16,6 +16,8 @@ pub struct Config {
pub session_days: i64,
pub max_upload_bytes: usize,
pub secure_cookie: bool,
/// How long the vault stays open with nothing using it.
pub vault_idle: std::time::Duration,
pub backup: BackupConfig,
pub mail: MailConfig,
pub sftp: SftpConfig,
@@ -198,6 +200,15 @@ impl Config {
* 1024
* 1024;
let vault_idle = std::time::Duration::from_secs(
var("VAULT_IDLE_MIN")
.map(|v| v.parse::<u64>())
.transpose()
.context("WORKSPACE_VAULT_IDLE_MIN must be a number")?
.unwrap_or(15)
* 60,
);
let secure_cookie = var("SECURE_COOKIE")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
@@ -211,6 +222,7 @@ impl Config {
session_days,
max_upload_bytes,
secure_cookie,
vault_idle,
backup: BackupConfig::from_env()?,
mail: MailConfig::from_env()?,
sftp: SftpConfig::from_env()?,