feat(security): release v0.7.0 with comprehensive security hardening (S-01 to S-11)
Sanctum Release / Build & Release (Windows x86_64) (push) Waiting to run
Sanctum Release / Build & Release (Windows x86_64) (push) Waiting to run
This commit is contained in:
+76
-14
@@ -209,13 +209,27 @@ pub async fn mount_container(
|
||||
ui::step(3, 4, "🔓", "Notfallschlüssel erfolgreich verifiziert!");
|
||||
}
|
||||
|
||||
// Prüfe, ob dek Dateien im Hidden Vault (Root 2) entschlüsseln kann
|
||||
let is_hidden = {
|
||||
let children = db.list_children_in_vault(2, 1, &dek).unwrap_or_default();
|
||||
!children.is_empty()
|
||||
let carrier_node_id = db.find_carrier_node_id()?.unwrap_or(0);
|
||||
let (carrier_dek, final_carrier_node_id, vault_id) = if carrier_node_id > 0 {
|
||||
let slot_id = crate::recovery::detect_recovery_key_slot(&db, &dek, meta.version)?;
|
||||
if slot_id == 1 {
|
||||
if !stealth {
|
||||
println!(" [i] Der angegebene Notfallschlüssel gehört zum Hidden-Vault (Slot 1).");
|
||||
println!(" Für den Zugriff auf die Trägerdatei wird das Passwort des Standard-Vaults benötigt.");
|
||||
}
|
||||
let decoy_pass = rpassword::prompt_password("Master-Passwort für Standard-Vault eingeben: ")
|
||||
.context("Fehler beim Einlesen des Standard-Vault Passworts")?;
|
||||
let decoy_keys = meta.authenticate(&decoy_pass)
|
||||
.ok_or_else(|| anyhow::anyhow!("Ungültiges Passwort für Standard-Vault."))?;
|
||||
(Some(decoy_keys.dek().clone()), Some(carrier_node_id), 1)
|
||||
} else {
|
||||
(None, Some(carrier_node_id), 0)
|
||||
}
|
||||
} else {
|
||||
(None, None, 0)
|
||||
};
|
||||
let vault_id = if is_hidden { 1 } else { 0 };
|
||||
(dek, None, None, meta.version, vault_id)
|
||||
|
||||
(dek, carrier_dek, final_carrier_node_id, meta.version, vault_id)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -402,12 +416,12 @@ pub async fn mount_container(
|
||||
println!(" • Modus: WebDAV Userland-VFS");
|
||||
}
|
||||
}
|
||||
println!(" • WebDAV-URL: http://127.0.0.1:{}/{}/ (Session-Token geschützt)", bound_port, session_token);
|
||||
println!(" • WebDAV-URL: http://127.0.0.1:{}/<session-token>/ (lokal geschützt)", bound_port);
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
println!(" • gio Befehl: gio mount dav://127.0.0.1:{}/{}/", bound_port, session_token);
|
||||
println!(" • gio Befehl: gio mount dav://127.0.0.1:{}/<session-token>/", bound_port);
|
||||
if let Some(mp) = mount_point {
|
||||
println!(" • davfs2: mount -t davfs http://127.0.0.1:{}/{}/ {}", bound_port, session_token, mp.display());
|
||||
println!(" • davfs2: mount -t davfs http://127.0.0.1:{}/<session-token>/ {}", bound_port, mp.display());
|
||||
}
|
||||
}
|
||||
if let Some(secs) = idle_timeout {
|
||||
@@ -602,15 +616,63 @@ pub async fn mount_container(
|
||||
println!("{} Sanctum Container wurde sicher und vollständig geschlossen.", ui::green("✔"));
|
||||
println!();
|
||||
}
|
||||
|
||||
drop(db);
|
||||
// S-10: Verifikation und Bereinigung von temporären SQLite WAL- und SHM-Dateien
|
||||
let base_os = container_path.as_os_str().to_os_string();
|
||||
let mut wal = base_os.clone();
|
||||
wal.push("-wal");
|
||||
let _ = std::fs::remove_file(wal);
|
||||
let mut shm = base_os;
|
||||
shm.push("-shm");
|
||||
let _ = std::fs::remove_file(shm);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Überprüft, ob ein Host-Header auf Loopback (127.0.0.1, localhost, [::1]) zeigt (Anti-DNS-Rebinding).
|
||||
/// Überprüft, ob ein Host-Header exakt auf Loopback (127.0.0.1, localhost, [::1], ::1) zeigt (Anti-DNS-Rebinding & S-06).
|
||||
/// Striktes Parsing von Host und Port ohne anfällige starts_with-Präfixprüfungen.
|
||||
pub fn is_loopback_host(host_str: &str) -> bool {
|
||||
let host_lower = host_str.to_ascii_lowercase();
|
||||
host_lower.starts_with("127.0.0.1")
|
||||
|| host_lower.starts_with("localhost")
|
||||
|| host_lower.starts_with("[::1]")
|
||||
let host_trimmed = host_str.trim().to_ascii_lowercase();
|
||||
if host_trimmed.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if host_trimmed == "::1" {
|
||||
return true;
|
||||
}
|
||||
|
||||
let host_part = if host_trimmed.starts_with('[') {
|
||||
// IPv6 bracketed: [::1] oder [::1]:port
|
||||
if let Some(bracket_end) = host_trimmed.find(']') {
|
||||
let inside = &host_trimmed[1..bracket_end];
|
||||
let after = &host_trimmed[bracket_end + 1..];
|
||||
if !after.is_empty() {
|
||||
if !after.starts_with(':') {
|
||||
return false;
|
||||
}
|
||||
if after[1..].parse::<u16>().is_err() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
inside
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// IPv4 oder Hostname: localhost, localhost:8443, 127.0.0.1, 127.0.0.1:8443
|
||||
if let Some((h, p)) = host_trimmed.rsplit_once(':') {
|
||||
if p.parse::<u16>().is_ok() {
|
||||
h
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
&host_trimmed
|
||||
}
|
||||
};
|
||||
|
||||
matches!(host_part, "127.0.0.1" | "localhost" | "::1")
|
||||
}
|
||||
|
||||
/// Maximale Anzahl gleichzeitiger Verbindungen zum lokalen WebDAV-Endpunkt (Schutz gegen Socket-Exhaustion).
|
||||
|
||||
Reference in New Issue
Block a user