Skip to main content

aios_core/
privacy_airgap.rs

1//! 隐私脱敏引擎 — DiPECS 的核心安全边界
2//!
3//! 所有 `RawEvent` 在此处被转化为 `SanitizedEvent`。
4//! 在此边界之后,原始敏感数据(通知正文、文件名、Binder 参数)不可访问。
5
6use aios_spec::traits::PrivacySanitizer;
7use aios_spec::{
8    AppTransitionRawEvent, BinderTxEvent, FsAccessEvent, FsActivityType, InteractionType,
9    NotificationRawEvent, ProcStateEvent, RawEvent, SanitizedEvent, SanitizedEventType, ScriptHint,
10    SourceTier, TextHint,
11};
12use uuid::Uuid;
13
14use crate::text_analysis::{analyze_text, classify_extension, extract_semantic_hints};
15
16/// 默认脱敏引擎
17pub struct DefaultPrivacyAirGap;
18
19impl PrivacySanitizer for DefaultPrivacyAirGap {
20    fn sanitize(&self, raw: RawEvent) -> SanitizedEvent {
21        match raw {
22            RawEvent::AppTransition(e) => sanitize_app_transition(e),
23            RawEvent::BinderTransaction(e) => sanitize_binder(e),
24            RawEvent::ProcStateChange(e) => sanitize_proc(e),
25            RawEvent::FileSystemAccess(e) => sanitize_fs(e),
26            RawEvent::NotificationPosted(e) => sanitize_notification(e),
27            RawEvent::NotificationInteraction(e) => SanitizedEvent {
28                event_id: new_id(),
29                timestamp_ms: e.timestamp_ms,
30                event_type: SanitizedEventType::Notification {
31                    source_package: e.package_name.clone(),
32                    category: None,
33                    channel_id: None,
34                    title_hint: TextHint {
35                        length_chars: 0,
36                        script: ScriptHint::Unknown,
37                        is_emoji_only: false,
38                    },
39                    text_hint: TextHint {
40                        length_chars: 0,
41                        script: ScriptHint::Unknown,
42                        is_emoji_only: false,
43                    },
44                    semantic_hints: vec![],
45                    is_ongoing: false,
46                    // The Android NotificationListenerService key has shape
47                    // "<userId>|<package>|<id>|<tag>|<uid>" — the *tag*
48                    // portion is user-controlled and can carry PII (contact
49                    // names, chat thread IDs). No downstream consumer uses
50                    // this key for dedup today, so the safe choice is to
51                    // drop it at the air-gap. If a future feature genuinely
52                    // needs cross-interaction correlation, replace this
53                    // with a one-way hash of the key.
54                    group_key: None,
55                },
56                source_tier: SourceTier::PublicApi,
57                app_package: Some(e.package_name),
58                uid: None,
59            },
60            RawEvent::ScreenState(e) => SanitizedEvent {
61                event_id: new_id(),
62                timestamp_ms: e.timestamp_ms,
63                event_type: SanitizedEventType::Screen { state: e.state },
64                source_tier: SourceTier::PublicApi,
65                app_package: None,
66                uid: None,
67            },
68            RawEvent::SystemState(e) => SanitizedEvent {
69                event_id: new_id(),
70                timestamp_ms: e.timestamp_ms,
71                event_type: SanitizedEventType::SystemStatus {
72                    battery_pct: e.battery_pct,
73                    is_charging: e.is_charging,
74                    network: e.network,
75                    ringer_mode: e.ringer_mode,
76                    location_type: e.location_type,
77                    headphone_connected: e.headphone_connected,
78                },
79                source_tier: SourceTier::PublicApi,
80                app_package: None,
81                uid: None,
82            },
83        }
84    }
85}
86
87// ===== 各类型脱敏逻辑 =====
88
89fn sanitize_app_transition(e: AppTransitionRawEvent) -> SanitizedEvent {
90    SanitizedEvent {
91        event_id: new_id(),
92        timestamp_ms: e.timestamp_ms,
93        event_type: SanitizedEventType::AppTransition {
94            package_name: e.package_name.clone(),
95            activity_class: e.activity_class,
96            transition: e.transition,
97        },
98        source_tier: SourceTier::PublicApi,
99        app_package: Some(e.package_name),
100        uid: None,
101    }
102}
103
104fn sanitize_binder(e: BinderTxEvent) -> SanitizedEvent {
105    let interaction_type = match e.target_method.as_str() {
106        m if m.contains("enqueueNotification") => InteractionType::NotifyPost,
107        m if m.contains("startActivity") || m.contains("startActivityAsUser") => {
108            InteractionType::ActivityLaunch
109        },
110        m if m.contains("share") || m.contains("sendIntent") => InteractionType::ShareIntent,
111        m if m.contains("bindService") || m.contains("bindIsolatedService") => {
112            InteractionType::ServiceBind
113        },
114        _ => {
115            return SanitizedEvent {
116                event_id: new_id(),
117                timestamp_ms: e.timestamp_ms,
118                event_type: SanitizedEventType::InterAppInteraction {
119                    source_package: None,
120                    target_service: e.target_service,
121                    interaction_type: InteractionType::ServiceBind,
122                },
123                source_tier: SourceTier::Daemon,
124                app_package: None,
125                uid: Some(e.source_uid),
126            };
127        },
128    };
129
130    SanitizedEvent {
131        event_id: new_id(),
132        timestamp_ms: e.timestamp_ms,
133        event_type: SanitizedEventType::InterAppInteraction {
134            source_package: None,
135            target_service: e.target_service,
136            interaction_type,
137        },
138        source_tier: SourceTier::Daemon,
139        app_package: None,
140        uid: Some(e.source_uid),
141    }
142}
143
144fn sanitize_proc(e: ProcStateEvent) -> SanitizedEvent {
145    SanitizedEvent {
146        event_id: new_id(),
147        timestamp_ms: e.timestamp_ms,
148        event_type: SanitizedEventType::ProcessResource {
149            pid: e.pid,
150            package_name: e.package_name.clone(),
151            vm_rss_mb: (e.vm_rss_kb / 1024) as u32,
152            vm_swap_mb: (e.vm_swap_kb / 1024) as u32,
153            thread_count: e.threads,
154            oom_score: e.oom_score,
155        },
156        source_tier: SourceTier::Daemon,
157        app_package: e.package_name,
158        uid: Some(e.uid),
159    }
160}
161
162fn sanitize_fs(e: FsAccessEvent) -> SanitizedEvent {
163    let ext_cat = classify_extension(&e.file_path);
164    SanitizedEvent {
165        event_id: new_id(),
166        timestamp_ms: e.timestamp_ms,
167        event_type: SanitizedEventType::FileActivity {
168            package_name: None,
169            extension_category: ext_cat,
170            activity_type: match e.access_type {
171                aios_spec::FsAccessType::OpenRead => FsActivityType::Read,
172                aios_spec::FsAccessType::OpenWrite => FsActivityType::Write,
173                aios_spec::FsAccessType::Create => FsActivityType::Create,
174                aios_spec::FsAccessType::Delete => FsActivityType::Delete,
175            },
176            is_hot_file: false,
177        },
178        source_tier: SourceTier::Daemon,
179        app_package: None,
180        uid: Some(e.uid),
181    }
182}
183
184fn sanitize_notification(e: NotificationRawEvent) -> SanitizedEvent {
185    let title_hint = analyze_text(&e.raw_title);
186    let text_hint = analyze_text(&e.raw_text);
187    let semantic_hints = extract_semantic_hints(&e.raw_title, &e.raw_text);
188
189    SanitizedEvent {
190        event_id: new_id(),
191        timestamp_ms: e.timestamp_ms,
192        event_type: SanitizedEventType::Notification {
193            source_package: e.package_name.clone(),
194            category: e.category,
195            channel_id: e.channel_id,
196            title_hint,
197            text_hint,
198            semantic_hints,
199            is_ongoing: e.is_ongoing,
200            group_key: e.group_key,
201        },
202        source_tier: SourceTier::PublicApi,
203        app_package: Some(e.package_name),
204        uid: None,
205    }
206}
207
208fn new_id() -> String {
209    Uuid::new_v4().to_string()
210}