Files
sanctum/tests/mount_security_test.rs
T
harald 1cdb30147b
Sanctum Release / Build & Release (Windows x86_64) (push) Waiting to run
release: v0.7.1 — Security-Patch (R-01 bis R-06)
- R-01: Bereinigung verbliebener Restbehauptungen in Doku und Code (LEGAL.md, README.md, QUICKSTART.md, main.rs, crypto/storage/recovery/verify.rs)
- R-02: Lückenloser Carrier-Schutz in SanctumFs::copy (Quelle & Ziel) und sync (Pull-Skip & Push-Schutz)
- R-03: Shared Dateinamen-Validierung (validate_node_name) in pathutil.rs, durchgesetzt in storage.rs und vfs.rs
- R-04: Fail-closed Release-Packaging & obligatorische Minisign-Signatur in CI (.gitea/workflows/release.yaml) und Scripts
- R-05: Session-Token Beseitigung im argv: In-Process Win32 WNetAddConnection2W/WNetCancelConnection2W, kein gio argv-Token, Multi-Auth HTTP Middleware (Basic Auth, X-Sanctum-Token, Path-Fallback) mit 401 WWW-Authenticate
- R-06: Sofortiges Löschen von SANCTUM_RECOVERY_KEY aus der Prozessumgebung
2026-09-18 19:49:38 +02:00

116 lines
4.3 KiB
Rust

use std::path::PathBuf;
use sanctum::crypto::{
derive_kek, generate_dek, generate_salt, wrap_dek, KdfParams, MIN_MEMORY_COST_KIB,
MIN_TIME_COST,
};
use sanctum::mount::is_loopback_host;
use sanctum::storage::Database;
#[test]
fn test_is_loopback_host_comprehensive() {
// Gültige IPv4 Loopback Varianten
assert!(is_loopback_host("127.0.0.1"));
assert!(is_loopback_host("127.0.0.1:80"));
assert!(is_loopback_host("127.0.0.1:8080"));
assert!(is_loopback_host("127.0.0.1:65535"));
// Gültige Hostname Loopback Varianten
assert!(is_loopback_host("localhost"));
assert!(is_loopback_host("localhost:80"));
assert!(is_loopback_host("localhost:8443"));
assert!(is_loopback_host("LOCALHOST"));
assert!(is_loopback_host("LocalHost:9000"));
// Gültige IPv6 Loopback Varianten
assert!(is_loopback_host("[::1]"));
assert!(is_loopback_host("[::1]:80"));
assert!(is_loopback_host("[::1]:8443"));
assert!(is_loopback_host("::1"));
// DNS-Rebinding & Spoofing Angriffe (MÜSSEN abgewiesen werden)
assert!(!is_loopback_host("127.0.0.1.attacker.com"));
assert!(!is_loopback_host("localhost.attacker.com"));
assert!(!is_loopback_host("localhost.evil.org:8080"));
assert!(!is_loopback_host("notlocalhost"));
assert!(!is_loopback_host("attacker.com"));
assert!(!is_loopback_host("attacker.com:127001"));
assert!(!is_loopback_host("192.168.1.1"));
assert!(!is_loopback_host("10.0.0.1"));
assert!(!is_loopback_host("0.0.0.0"));
assert!(!is_loopback_host(""));
assert!(!is_loopback_host(" "));
assert!(!is_loopback_host("foo:bar:baz"));
}
#[test]
fn test_wal_and_shm_cleanup_on_close() {
let temp_dir = std::env::temp_dir();
let container_path: PathBuf = temp_dir.join(format!("test_wal_cleanup_{}.sanctum", std::process::id()));
if container_path.exists() {
let _ = std::fs::remove_file(&container_path);
}
let salt = generate_salt();
let kdf_params = KdfParams {
memory_cost: MIN_MEMORY_COST_KIB,
time_cost: MIN_TIME_COST,
parallelism: 1,
};
let kek = derive_kek("WalCleanupPassword2026!", &salt, &kdf_params).unwrap();
let dek = generate_dek();
let (wrapped_dek, nonce, tag) = wrap_dek(&kek, &dek).unwrap();
let db = Database::open(&container_path).unwrap();
db.init_schema(&salt, &kdf_params, &wrapped_dek, &nonce, &tag).unwrap();
// Erstelle Knoten, um Schreibaktivität im WAL zu erzeugen
let _ = db.create_node(1, "test_file.txt", false).unwrap();
db.checkpoint().unwrap();
drop(db);
// Bereinigungslogik (wie in mount.rs unmount) ausführen
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);
assert!(!PathBuf::from(wal).exists(), "WAL-Datei darf nach sauberem Unmount nicht zurückbleiben");
assert!(!PathBuf::from(shm).exists(), "SHM-Datei darf nach sauberem Unmount nicht zurückbleiben");
let _ = std::fs::remove_file(&container_path);
}
#[test]
fn test_mount_security_multi_auth_and_no_token_in_url() {
use sanctum::mount::{check_basic_auth, check_token_header, strip_path_prefix};
let session_token = "4f8a12bc90de45f187a23456789abcde";
let port = 8443;
let remote_url = format!("http://127.0.0.1:{}/", port);
// R-05: Die Remote-URL für Mount-Befehle darf niemals das Session-Token enthalten!
assert!(!remote_url.contains(session_token), "Remote URL darf niemals das Session-Token enthalten");
// 1. Basic Auth Prüfung
use base64::Engine;
let auth_header = format!(
"Basic {}",
base64::engine::general_purpose::STANDARD.encode(format!("sanctum:{}", session_token))
);
assert!(check_basic_auth(&auth_header, session_token));
// 2. Token Header Prüfung
let mut headers = hyper::HeaderMap::new();
headers.insert("X-Sanctum-Token", session_token.parse().unwrap());
assert!(check_token_header(&headers, session_token));
// 3. Path-Prefix Fallback
let prefix = format!("/{}", session_token);
let uri: hyper::Uri = format!("http://127.0.0.1:8443/{}/test.txt", session_token).parse().unwrap();
let stripped = strip_path_prefix(&uri, &prefix).expect("Strip prefix");
assert_eq!(stripped.path(), "/test.txt");
}