feat(opsec): implement inactivity auto-lock, session lock detection, and anti-leak shield

This commit is contained in:
2026-09-08 10:14:31 +02:00
parent 38df3d3845
commit 2cb065c8c0
5 changed files with 727 additions and 7 deletions
+112
View File
@@ -498,4 +498,116 @@ async fn test_sanctum_disaster_recovery_workflow() {
let _ = std::fs::remove_file(&backup_path);
}
#[tokio::test]
async fn test_anti_leak_and_inactivity_shield() {
let temp_dir = std::env::temp_dir();
let container_path: PathBuf =
temp_dir.join(format!("test_leak_shield_{}.sanctum", std::process::id()));
if container_path.exists() {
let _ = std::fs::remove_file(&container_path);
}
let password = "ShieldedContainerPass2026!";
let salt = generate_salt();
let kdf_params = KdfParams {
memory_cost: 1024,
time_cost: 1,
parallelism: 1,
};
let kek = derive_kek(password, &salt, &kdf_params).expect("derive kek");
let dek = generate_dek();
let (wrapped_dek, header_nonce, header_tag) = wrap_dek(&kek, &dek).expect("wrap dek");
let db = Database::open(&container_path).expect("open db");
db.init_schema(&salt, &kdf_params, &wrapped_dek, &header_nonce, &header_tag)
.expect("init schema");
db.checkpoint().expect("checkpoint");
// 1. Erstelle Filesystem mit aktivem Anti-Leak Shield (Standard)
let fs = SanctumFs::with_options(db.clone(), dek.clone(), FORMAT_VERSION, true);
// 2. Windows Explorer Leck-Dateien müssen abgewehrt werden
let leak_files = [
"/Thumbs.db",
"/thumbs.db",
"/desktop.ini",
"/Desktop.ini",
"/Folder.jpg",
"/AlbumArt_{A1B2C3D4-E5F6}_Large.jpg",
"/albumartsmall.jpg",
"/ehthumbs.db",
"/autorun.inf",
];
for leak in leak_files {
let path = DavPath::new(leak).unwrap();
let mut opts = OpenOptions::default();
opts.write = true;
opts.create_new = true;
let res = fs.open(&path, opts).await;
assert!(
matches!(res, Err(dav_server::fs::FsError::Forbidden)),
"Leak-Datei '{}' muss mit FsError::Forbidden blockiert werden",
leak
);
}
// Ordnererstellung mit Leck-Namen muss ebenfalls blockiert werden
let leak_dir = DavPath::new("/Thumbs.db").unwrap();
let dir_res = fs.create_dir(&leak_dir).await;
assert!(matches!(dir_res, Err(dav_server::fs::FsError::Forbidden)));
// 3. Legitime Dateien müssen reibungslos funktionieren
let legit_path = DavPath::new("/top_secret.docx").unwrap();
let mut opts_legit = OpenOptions::default();
opts_legit.write = true;
opts_legit.create_new = true;
let mut file = fs.open(&legit_path, opts_legit).await.expect("create legit file");
file.write_bytes(Bytes::from_static(b"Sanctum OpSec Shield Test"))
.await
.expect("write data");
file.flush().await.expect("flush data");
drop(file);
// 4. Inaktivitäts-Tracking überprüfen
let activity_tracker = fs.last_activity();
let recorded_now = activity_tracker.load(std::sync::atomic::Ordering::Relaxed);
assert!(recorded_now > 0);
// Zurückdatieren um 300 Sekunden
activity_tracker.store(recorded_now - 300, std::sync::atomic::Ordering::Relaxed);
let past = activity_tracker.load(std::sync::atomic::Ordering::Relaxed);
// Datei lesen
let mut opts_read = OpenOptions::default();
opts_read.read = true;
let mut rf = fs.open(&legit_path, opts_read).await.expect("open legit");
let content = rf.read_bytes(25).await.expect("read bytes");
assert_eq!(&content[..], b"Sanctum OpSec Shield Test");
drop(rf);
let updated_time = activity_tracker.load(std::sync::atomic::Ordering::Relaxed);
assert!(
updated_time > past,
"VFS-Aktivität muss den Inaktivitäts-Timer aktualisieren"
);
// 5. Deaktivierter Shield (--no-anti-leak) erlaubt Explorer-Metadaten
let unshielded_fs = SanctumFs::with_options(db.clone(), dek.clone(), FORMAT_VERSION, false);
let mut unshielded_opts = OpenOptions::default();
unshielded_opts.write = true;
unshielded_opts.create_new = true;
let desktop_ini_path = DavPath::new("/desktop.ini").unwrap();
let desktop_res = unshielded_fs.open(&desktop_ini_path, unshielded_opts).await;
assert!(
desktop_res.is_ok(),
"Ohne Shield muss Erstellung von desktop.ini gestattet sein"
);
// Aufräumen
let _ = std::fs::remove_file(&container_path);
}