fix(storage): ST-02 — aggregate errors in recursive node deletion without swallowing

This commit is contained in:
2026-09-19 23:21:30 +02:00
parent dd6b6ff94d
commit efddc2c477
+63 -2
View File
@@ -6,7 +6,7 @@ use anyhow::{bail, Result};
use rand::rngs::OsRng; use rand::rngs::OsRng;
use rand::RngCore; use rand::RngCore;
use rusqlite::{params, Connection, OptionalExtension}; use rusqlite::{params, Connection, OptionalExtension};
use tracing::warn; use tracing::{error, warn};
use crate::crypto::{ use crate::crypto::{
compute_metadata_mac, decrypt_node_name, derive_kek, derive_metadata_mac_key, compute_metadata_mac, decrypt_node_name, derive_kek, derive_metadata_mac_key,
@@ -1706,8 +1706,18 @@ impl Database {
ids ids
}; };
let mut errors = Vec::new();
for child_id in child_ids { 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(); let conn = self.conn();
@@ -3111,4 +3121,55 @@ mod tests {
assert!(valid_by_id.is_some()); assert!(valid_by_id.is_some());
assert_eq!(valid_by_id.unwrap().name, "secret_valid.txt"); 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
);
}
} }