From efddc2c4773d9cad554314edfe267ee80e829c2c Mon Sep 17 00:00:00 2001 From: harald Date: Sat, 19 Sep 2026 23:21:30 +0200 Subject: [PATCH] =?UTF-8?q?fix(storage):=20ST-02=20=E2=80=94=20aggregate?= =?UTF-8?q?=20errors=20in=20recursive=20node=20deletion=20without=20swallo?= =?UTF-8?q?wing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/storage.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/src/storage.rs b/src/storage.rs index f661b90..48a5883 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -6,7 +6,7 @@ use anyhow::{bail, Result}; use rand::rngs::OsRng; use rand::RngCore; use rusqlite::{params, Connection, OptionalExtension}; -use tracing::warn; +use tracing::{error, warn}; use crate::crypto::{ compute_metadata_mac, decrypt_node_name, derive_kek, derive_metadata_mac_key, @@ -1706,8 +1706,18 @@ impl Database { ids }; + let mut errors = Vec::new(); for child_id in child_ids { - let _ = self.delete_node(child_id); + if let Err(e) = self.delete_node(child_id) { + error!("Konnte Kindknoten {} nicht löschen: {e}", child_id); + errors.push(e); + } + } + if !errors.is_empty() { + bail!( + "Fehler beim rekursiven Löschen: {} Kindknoten konnten nicht gelöscht werden (ST-02)", + errors.len() + ); } let conn = self.conn(); @@ -3111,4 +3121,55 @@ mod tests { assert!(valid_by_id.is_some()); assert_eq!(valid_by_id.unwrap().name, "secret_valid.txt"); } + + #[test] + fn test_st02_recursive_delete_error_propagation_not_swallowed() { + let db = Database::open_in_memory().unwrap(); + let (salt, kdf, wrapped_dek, nonce, tag) = ( + [1u8; 16], + KdfParams::default(), + vec![2u8; 40], + [3u8; 12], + [4u8; 16], + ); + db.init_schema(&salt, &kdf, &wrapped_dek, &nonce, &tag) + .unwrap(); + + // Verzeichnisstruktur erstellen: + // /parent (id=3) + // /parent/normal.txt (id=4) + // /parent/protected.txt (id=5) + let parent = db.create_node(1, "parent", true).unwrap(); + let _child_normal = db.create_node(parent.id, "normal.txt", false).unwrap(); + let _child_protected = db.create_node(parent.id, "protected.txt", false).unwrap(); + + // SQLite-Trigger einrichten, der das Löschen von 'protected.txt' blockiert + { + let conn = db.conn(); + conn.execute( + "CREATE TRIGGER block_delete_protected BEFORE DELETE ON nodes + WHEN OLD.name = 'protected.txt' + BEGIN + SELECT RAISE(FAIL, 'Löschen von protected.txt blockiert'); + END;", + [], + ) + .unwrap(); + } + + // ST-02: delete_node(parent.id) darf den Fehler nicht mehr mit `let _ =` verschlucken! + let res = db.delete_node(parent.id); + assert!( + res.is_err(), + "Löschen muss fehlschlagen, wenn ein Kindknoten nicht gelöscht werden kann" + ); + let err_msg = res.unwrap_err().to_string(); + assert!( + err_msg.contains( + "Fehler beim rekursiven Löschen: 1 Kindknoten konnten nicht gelöscht werden" + ), + "Fehlermeldung muss die Anzahl fehlgeschlagener Kindknoten exakt ausweisen: {}", + err_msg + ); + } }