From dda7fca5c19eef3a8d75756d1ebf4c2555f41fef Mon Sep 17 00:00:00 2001 From: forkless Date: Mon, 1 Jun 2026 18:57:29 +0200 Subject: [PATCH] Depersonalize logged paths (sanitize_path) --- src/guard.rs | 14 ++++++++++++++ src/main.rs | 14 +++++++------- src/main.rs.replace.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 src/main.rs.replace.py diff --git a/src/guard.rs b/src/guard.rs index 8b0bf7b..29075e0 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -105,6 +105,20 @@ pub fn log_action( f.write_all(line.as_bytes())?; Ok(()) } +/// Truncate a filesystem path to start at `Subnautica2/` or `Subnautica2\` +/// for privacy-safe logging. Returns the original path if no truncation +/// is possible. +pub fn sanitize_path(p: &str) -> String { + let needle = "Subnautica2"; + let sep = if p.contains('\\') { "\\" } else { "/" }; + if let Some(pos) = p.find(needle) { + format!("...{sep}{}", &p[pos..]) + } else { + p.to_string() + } +} + +/// Check whether a path looks like a network/UNC path (for warning purposes). /// Check whether a path looks like a network/UNC path (for warning purposes). pub fn is_network_path(p: &str) -> bool { diff --git a/src/main.rs b/src/main.rs index 2013f9f..648129e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -638,7 +638,7 @@ fn action_create_backup(terminal: &mut Terminal, app: &mut App) - Err(e) => { app.set_spinner(false); let msg = format!("Backup failed: {e}"); - guard::log_action("MANUAL_BAK", &save_folder.display().to_string(), &format!("FAILED: {e}"), &app.log_path)?; + guard::log_action("MANUAL_BAK", &guard::sanitize_path(&save_folder.display().to_string()), &format!("FAILED: {e}"), &app.log_path)?; ok_dialog(terminal, app, "Backup Failed", &msg)?; } } @@ -693,11 +693,11 @@ fn action_restore_backup(terminal: &mut Terminal, app: &mut App) let accepted = confirm_modal(terminal, app, "Confirm Restore", &restore_details)?; if accepted { - guard::log_action("AUTO_BAK", &format!("pre-restore → {}", save_folder.display()), "OK", &app.log_path)?; + guard::log_action("AUTO_BAK", &format!("pre-restore → {}", guard::sanitize_path(&save_folder.display().to_string())), "OK", &app.log_path)?; match ops::restore_full_backup(chosen, &save_folder, &backup_root) { Ok(n) => { app.set_status(&format!("{n} save files restored."), tui::StatusStyle::Success); - guard::log_action("RESTORE", &format!("{} → {}", name, save_folder.display()), "OK", &app.log_path)?; + guard::log_action("RESTORE", &format!("{} → {}", name, guard::sanitize_path(&save_folder.display().to_string())), "OK", &app.log_path)?; } Err(e) => { app.set_status(&format!("Restore failed: {e}"), tui::StatusStyle::Error); @@ -786,7 +786,7 @@ fn ini_backup_action( &format!("Config backup created: {} files ({})", result.files_copied, verified), tui::StatusStyle::Success, ); - guard::log_action("CONFIG_BAK", &result.dest_dir.display().to_string(), "OK", &app.log_path)?; + guard::log_action("CONFIG_BAK", &guard::sanitize_path(&result.dest_dir.display().to_string()), "OK", &app.log_path)?; refresh_stats(&mut app.tui_state, app.save_folder.as_deref()); let verified = if result.verified { "verified" } else { "unverified" }; let msg = format!("{} .ini file(s) backed up ({verified}).", result.files_copied); @@ -842,10 +842,10 @@ fn ini_restore_action( let idx = state.selected().unwrap_or(0); let chosen = &backups[idx]; - guard::log_action("AUTO_BAK", &format!("ini pre-restore → {}", ini_path.display()), "OK", &app.log_path)?; + guard::log_action("AUTO_BAK", &format!("ini pre-restore → {}", guard::sanitize_path(&ini_path.display().to_string())), "OK", &app.log_path)?; match ops::restore_ini_files(chosen, ini_path, backup_root) { Ok(n) => { - guard::log_action("CONFIG_RESTORE", &chosen.display().to_string(), "OK", &app.log_path)?; + guard::log_action("CONFIG_RESTORE", &guard::sanitize_path(&chosen.display().to_string()), "OK", &app.log_path)?; let msg = format!("{n} .ini file(s) restored."); ok_dialog(terminal, app, ".ini Restore Complete", &msg)?; } @@ -899,7 +899,7 @@ fn ini_delete_action( match ops::delete_ini_files(ini_path, backup_root) { Ok(n) => { let msg = format!("Deleted {n} .ini file(s).\nThe game will regenerate defaults on next launch."); - guard::log_action("CONFIG_DEL", &ini_path.display().to_string(), "OK", &app.log_path)?; + guard::log_action("CONFIG_DEL", &guard::sanitize_path(&ini_path.display().to_string()), "OK", &app.log_path)?; ok_dialog(terminal, app, ".ini Delete Complete", &msg)?; } Err(e) => { diff --git a/src/main.rs.replace.py b/src/main.rs.replace.py new file mode 100644 index 0000000..6bb9851 --- /dev/null +++ b/src/main.rs.replace.py @@ -0,0 +1,31 @@ +import re +f = open('src/main.rs') +text = f.read() +f.close() + +# List of (old_snippet, new_snippet) — both must match exactly +replacements = [ + ('guard::log_action("MANUAL_BAK", &save_folder.display().to_string()', + 'guard::log_action("MANUAL_BAK", &guard::sanitize_path(&save_folder.display().to_string())'), + ('guard::log_action("AUTO_BAK", &format!("pre-restore → {}", save_folder.display())', + 'guard::log_action("AUTO_BAK", &format!("pre-restore → {}", guard::sanitize_path(&save_folder.display().to_string()))'), + ('guard::log_action("RESTORE", &format!("{} → {}", name, save_folder.display())', + 'guard::log_action("RESTORE", &format!("{} → {}", name, guard::sanitize_path(&save_folder.display().to_string()))'), + ('guard::log_action("CONFIG_BAK", &result.dest_dir.display().to_string()', + 'guard::log_action("CONFIG_BAK", &guard::sanitize_path(&result.dest_dir.display().to_string())'), + ('guard::log_action("AUTO_BAK", &format!("ini pre-restore → {}", ini_path.display())', + 'guard::log_action("AUTO_BAK", &format!("ini pre-restore → {}", guard::sanitize_path(&ini_path.display().to_string()))'), + ('guard::log_action("CONFIG_RESTORE", &chosen.display().to_string()', + 'guard::log_action("CONFIG_RESTORE", &guard::sanitize_path(&chosen.display().to_string())'), + ('guard::log_action("CONFIG_DEL", &ini_path.display().to_string()', + 'guard::log_action("CONFIG_DEL", &guard::sanitize_path(&ini_path.display().to_string())'), +] + +for old, new in replacements: + count = text.count(old) + if count != 1: + print(f'WARN: found {count} occurences of: {old[:50]}...') + text = text.replace(old, new) + +open('src/main.rs', 'w').write(text) +print('done')