fix(core): resolve all 12 adversarial review findings

- Zeroize passwords in CLI prompts, handlers, and mount authentication
- Preserve carrier_node_id when recovering Slot 0 via recovery key
- Add --slot parameter to restore-header for targeted slot recovery
- Implement online_backup and restore_from_backup using SQLite Online Backup API
- Expose 'sanctum backup' and 'sanctum restore' CLI subcommands
- Fix inactivity auto-lock by removing touch() from PROPFIND metadata/read_dir
- Support O_APPEND by setting file cursor to file size on handle creation
- Prevent data loss by implementing Drop for CarrierFile to flush dirty blocks
- Optimize CSPRNG padding to only fill unwritten slack space
- Batch carrier initialization in 500-block transactions to prevent UI/CLI freeze
- Enforce 64-byte savings threshold for LZ4 compression
- Add WebClient service diagnostic hint for Windows net use mount errors
- Add unit and integration tests covering all new features
This commit is contained in:
2026-09-09 21:02:21 +02:00
parent 030ce6a1e5
commit 98bfac718f
10 changed files with 571 additions and 91 deletions
+31 -9
View File
@@ -178,10 +178,9 @@ pub fn write_carrier_block(
let inner_ct_len = inner_ct.len() as u32;
// 2. Äußere Nutzlast vorbereiten: Exakt CHUNK_SIZE (1 MB) mit CSPRNG-Rauschen
// 2. Äußere Nutzlast vorbereiten: Exakt CHUNK_SIZE (1 MB)
// Format: inner_nonce (12B) || inner_tag (16B) || inner_ct_len (4B LE) || inner_ct || CSPRNG-Padding
let mut outer_plaintext = vec![0u8; CHUNK_SIZE];
OsRng.fill_bytes(&mut outer_plaintext);
outer_plaintext[0..12].copy_from_slice(&inner_nonce);
outer_plaintext[12..28].copy_from_slice(&inner_tag);
@@ -197,6 +196,11 @@ pub fn write_carrier_block(
}
outer_plaintext[32..ct_end].copy_from_slice(&inner_ct);
// Nur den ungenutzten Slack-Space mit kryptografischem Zufall auffüllen (~30x schneller als 1MB OsRng)
if ct_end < CHUNK_SIZE {
OsRng.fill_bytes(&mut outer_plaintext[ct_end..]);
}
// 3. Äußere Schicht verschlüsseln (mit dek_outer = DEK_0)
let (outer_ct, outer_nonce, outer_tag) = encrypt_chunk(
dek_outer,
@@ -491,7 +495,7 @@ impl DavFileSystem for CarrierFs {
drop(inner);
let file = CarrierFile::new(inode, self.inner.clone());
let file = CarrierFile::new(inode, self.inner.clone(), options.append);
Ok(Box::new(file) as Box<dyn DavFile>)
})
}
@@ -502,7 +506,6 @@ impl DavFileSystem for CarrierFs {
_meta: ReadDirMeta,
) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
Box::pin(async move {
self.touch();
let path_str = Self::path_to_str(path);
let inner = self.inner.lock().unwrap();
@@ -537,9 +540,6 @@ impl DavFileSystem for CarrierFs {
fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
Box::pin(async move {
let path_str = Self::path_to_str(path);
if path_str != "/" && !path_str.is_empty() {
self.touch();
}
let inner = self.inner.lock().unwrap();
let node = inner.resolve_path(&path_str).ok_or(FsError::NotFound)?;
@@ -755,7 +755,7 @@ impl Debug for CarrierFile {
}
impl CarrierFile {
pub fn new(inode: CarrierInode, inner_fs: Arc<Mutex<CarrierFsInner>>) -> Self {
pub fn new(inode: CarrierInode, inner_fs: Arc<Mutex<CarrierFsInner>>, append: bool) -> Self {
let meta = SanctumMetaData {
is_dir: inode.is_dir,
size: inode.size,
@@ -763,10 +763,12 @@ impl CarrierFile {
modified_at: UNIX_EPOCH + Duration::from_secs(inode.modified_at),
};
let cursor = if append { inode.size } else { 0 };
Self {
inode_id: inode.id,
file_size: inode.size,
cursor: 0,
cursor,
blocks: inode.blocks,
inner_fs,
meta,
@@ -991,3 +993,23 @@ impl DavFile for CarrierFile {
})
}
}
impl Drop for CarrierFile {
fn drop(&mut self) {
if let Err(e) = self.flush_cached_block() {
tracing::warn!("Fehler beim automatischen Flush im CarrierFile::drop: {:?}", e);
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
if let Ok(mut inner) = self.inner_fs.lock() {
if let Some(inode) = inner.manifest.inodes.get_mut(&self.inode_id) {
inode.size = self.file_size;
inode.blocks = self.blocks.clone();
inode.modified_at = now;
}
let _ = inner.save_manifest();
}
}
}