Skip to main content

diskforge_core/
safety.rs

1use std::path::Path;
2
3use crate::types::Risk;
4
5/// Apps whose data directories contain critical, irrecoverable user data.
6/// These should always be classified as Risk::High when found by any scanner.
7///
8/// The pattern is matched case-insensitively against directory names and bundle IDs.
9const CRITICAL_APP_PATTERNS: &[&str] = &[
10    // Messaging — sessions, chat history, media
11    "telegram",
12    "whatsapp",
13    "signal",
14    "discord",
15    "viber",
16    "line",
17    "wechat",
18    "imessage",
19    "messages",
20    // Password managers — vault data
21    "1password",
22    "bitwarden",
23    "lastpass",
24    "dashlane",
25    "enpass",
26    "keepass",
27    // Email
28    "thunderbird",
29    "airmail",
30    "spark",
31    "canary mail",
32    // Notes & knowledge
33    "obsidian",
34    "bear",
35    "notion",
36    "evernote",
37    "joplin",
38    "logseq",
39    "craft",
40    "apple notes",
41    // Crypto wallets
42    "metamask",
43    "ledger",
44    "exodus",
45    "phantom",
46    "coinbase wallet",
47    // Databases
48    "mongodb",
49    "postgres",
50    "mysql",
51    "redis",
52    // VPN / auth
53    "nordvpn",
54    "expressvpn",
55    "wireguard",
56    "tunnelblick",
57    // Development auth
58    "docker",
59    "github desktop",
60    "sourcetree",
61];
62
63/// File/directory name patterns that indicate critical user data.
64const CRITICAL_FILE_PATTERNS: &[&str] = &[
65    ".sqlite",
66    ".db",
67    ".realm",
68    "keychain",
69    "credential",
70    "session",
71    "auth",
72    "wallet",
73    "vault",
74    "accounts",
75    "login",
76];
77
78/// Check if a path involves a critical app whose data should be Risk::High.
79pub fn is_critical_app_path(path: &Path) -> bool {
80    let path_lower = path.to_string_lossy().to_lowercase();
81    CRITICAL_APP_PATTERNS
82        .iter()
83        .any(|pattern| path_lower.contains(pattern))
84}
85
86/// Check if a filename suggests critical user data.
87pub fn is_critical_file(path: &Path) -> bool {
88    let name = path
89        .file_name()
90        .map(|n| n.to_string_lossy().to_lowercase())
91        .unwrap_or_default();
92    CRITICAL_FILE_PATTERNS.iter().any(|p| name.contains(p))
93}
94
95/// Elevate risk level if the path involves critical user data.
96/// This should be called on every CleanableItem before presenting to the user.
97pub fn adjust_risk(path: &Path, current_risk: Risk) -> Risk {
98    if current_risk == Risk::High {
99        return Risk::High;
100    }
101    if is_critical_app_path(path) || is_critical_file(path) {
102        return Risk::High;
103    }
104    current_risk
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use std::path::PathBuf;
111
112    #[test]
113    fn telegram_is_critical() {
114        let path = PathBuf::from(
115            "/Users/x/Library/Group Containers/group.6N38VWS5BX.ru.keepcoder.Telegram",
116        );
117        assert!(is_critical_app_path(&path));
118    }
119
120    #[test]
121    fn whatsapp_is_critical() {
122        let path = PathBuf::from("/Users/x/Library/Application Support/WhatsApp");
123        assert!(is_critical_app_path(&path));
124    }
125
126    #[test]
127    fn onepassword_is_critical() {
128        let path = PathBuf::from("/Users/x/Library/Containers/com.1password.1password");
129        assert!(is_critical_app_path(&path));
130    }
131
132    #[test]
133    fn sqlite_file_is_critical() {
134        let path = PathBuf::from("/some/path/messages.sqlite");
135        assert!(is_critical_file(&path));
136    }
137
138    #[test]
139    fn cache_file_is_not_critical() {
140        let path = PathBuf::from("/Users/x/Library/Caches/BraveSoftware");
141        assert!(!is_critical_app_path(&path));
142        assert!(!is_critical_file(&path));
143    }
144
145    #[test]
146    fn adjust_risk_elevates() {
147        let path = PathBuf::from("/Users/x/Library/Application Support/Telegram");
148        assert_eq!(adjust_risk(&path, Risk::Low), Risk::High);
149        assert_eq!(adjust_risk(&path, Risk::None), Risk::High);
150    }
151
152    #[test]
153    fn adjust_risk_keeps_high() {
154        let path = PathBuf::from("/Users/x/Downloads");
155        assert_eq!(adjust_risk(&path, Risk::High), Risk::High);
156    }
157
158    #[test]
159    fn adjust_risk_normal_path_unchanged() {
160        let path = PathBuf::from("/Users/x/Library/Caches/com.brave.Browser");
161        assert_eq!(adjust_risk(&path, Risk::None), Risk::None);
162    }
163}