1use std::collections::HashSet;
2
3use crate::redaction::Redactor;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16#[non_exhaustive]
17pub enum PrivacyProfile {
18 Observe,
21 Test,
25 #[default]
27 FullControl,
28}
29
30impl std::fmt::Display for PrivacyProfile {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 Self::Observe => write!(f, "observe"),
34 Self::Test => write!(f, "test"),
35 Self::FullControl => write!(f, "full_control"),
36 }
37 }
38}
39
40#[derive(Default)]
50#[non_exhaustive]
51pub struct PrivacyConfig {
52 pub profile: PrivacyProfile,
54 pub command_allowlist: Option<HashSet<String>>,
56 pub command_blocklist: HashSet<String>,
58 pub disabled_tools: HashSet<String>,
60 pub storage_key_blocklist: HashSet<String>,
63 pub redactor: Redactor,
65 pub redaction_enabled: bool,
67}
68
69impl PrivacyConfig {
70 #[must_use]
72 pub fn is_command_allowed(&self, command: &str) -> bool {
73 if self.command_blocklist.contains(command) {
74 return false;
75 }
76 match &self.command_allowlist {
77 Some(allow) => allow.contains(command),
78 None => true,
79 }
80 }
81
82 #[must_use]
86 pub fn is_storage_key_allowed(&self, key: &str) -> bool {
87 !self.storage_key_blocklist.contains(key)
88 }
89
90 #[must_use]
93 pub fn is_tool_enabled(&self, tool_or_action: &str) -> bool {
94 if self.is_disabled(tool_or_action) {
95 return false;
96 }
97 is_allowed_by_profile(self.profile, tool_or_action)
98 }
99
100 fn is_disabled(&self, name: &str) -> bool {
104 self.disabled_tools.contains(name)
105 || crate::mcp::authz::capability_alias(name)
106 .is_some_and(|alias| self.disabled_tools.contains(alias))
107 }
108
109 #[must_use]
125 pub fn is_call_allowed(&self, bare_tool: &str, capability: &str) -> bool {
126 if self.disabled_tools.contains(bare_tool) {
127 return false;
128 }
129 self.is_tool_enabled(capability)
130 }
131
132 #[must_use]
137 pub fn is_invoke_allowed(&self, command: &str) -> bool {
138 if self.disabled_tools.contains("invoke_command") {
139 return false;
140 }
141 match self.profile {
142 PrivacyProfile::FullControl => true,
143 PrivacyProfile::Test => self
144 .command_allowlist
145 .as_ref()
146 .is_some_and(|al| al.contains(command)),
147 PrivacyProfile::Observe => false,
148 }
149 }
150
151 #[must_use]
153 pub fn redact_output(&self, output: &str) -> String {
154 if self.redaction_enabled {
155 self.redactor.redact(output)
156 } else {
157 output.to_string()
158 }
159 }
160}
161
162#[must_use]
171fn is_allowed_by_profile(profile: PrivacyProfile, tool_or_action: &str) -> bool {
172 match profile {
173 PrivacyProfile::FullControl => true,
174 PrivacyProfile::Test => matches!(
175 tool_or_action,
176 "dom_snapshot"
178 | "find_elements"
179 | "get_registry"
180 | "get_memory_stats"
181 | "get_plugin_info"
182 | "get_diagnostics"
183 | "detect_ghost_commands"
184 | "check_ipc_integrity"
185 | "resolve_command"
186 | "wait_for"
187 | "app_state"
188 | "interact"
196 | "interact.click"
197 | "interact.double_click"
198 | "interact.hover"
199 | "interact.focus"
200 | "interact.scroll_into_view"
201 | "interact.select_option"
202 | "fill"
204 | "input"
205 | "input.fill"
206 | "type_text"
207 | "input.type_text"
208 | "input.press_key"
209 | "storage"
211 | "set_storage"
212 | "storage.set"
213 | "delete_storage"
214 | "storage.delete"
215 | "storage.get"
216 | "storage.get_cookies"
217 | "get_storage"
218 | "get_cookies"
219 | "recording"
221 | "recording.start"
222 | "recording.stop"
223 | "recording.checkpoint"
224 | "recording.list_checkpoints"
225 | "recording.get_events"
226 | "recording.events_between"
227 | "recording.get_replay"
228 | "recording.export"
229 | "recording.import"
230 | "logs"
232 | "logs.console"
233 | "logs.network"
234 | "logs.ipc"
235 | "logs.navigation"
236 | "logs.dialogs"
237 | "logs.events"
238 | "logs.slow_ipc"
239 | "logs.backend"
240 | "logs.backend_digest"
241 | "logs.stdout"
242 | "logs.clear"
245 | "inspect"
247 | "inspect.styles"
248 | "inspect.bounds"
249 | "inspect.highlight"
250 | "inspect.clear_highlights"
251 | "inspect.audit_a11y"
252 | "inspect.performance"
253 | "list_windows"
255 | "window"
256 | "window.get_state"
257 | "window.list"
258 | "window.introspectability"
259 | "get_window_state"
260 | "navigate"
265 | "navigate.go_back"
266 | "navigate.get_history"
267 | "navigate.get_dialog_log"
268 ),
269 PrivacyProfile::Observe => matches!(
270 tool_or_action,
271 "dom_snapshot"
272 | "find_elements"
273 | "get_registry"
274 | "get_memory_stats"
275 | "get_plugin_info"
276 | "get_diagnostics"
277 | "detect_ghost_commands"
278 | "check_ipc_integrity"
279 | "resolve_command"
280 | "app_state"
281 | "logs"
282 | "logs.console"
283 | "logs.network"
284 | "logs.ipc"
285 | "logs.navigation"
286 | "logs.dialogs"
287 | "logs.events"
288 | "logs.slow_ipc"
289 | "logs.backend"
290 | "logs.backend_digest"
291 | "logs.stdout"
292 | "inspect"
295 | "inspect.styles"
296 | "inspect.bounds"
297 | "inspect.audit_a11y"
300 | "inspect.performance"
301 | "list_windows"
302 | "window"
303 | "window.get_state"
304 | "window.list"
305 | "window.introspectability"
306 | "get_window_state"
307 | "wait_for"
308 ),
309 }
310}
311
312#[must_use]
314#[doc(hidden)]
315pub fn observe_privacy_config() -> PrivacyConfig {
316 PrivacyConfig {
317 profile: PrivacyProfile::Observe,
318 command_allowlist: None,
319 command_blocklist: HashSet::new(),
320 disabled_tools: HashSet::new(),
321 storage_key_blocklist: HashSet::new(),
322 redactor: Redactor::default(),
323 redaction_enabled: true,
324 }
325}
326
327#[must_use]
329#[doc(hidden)]
330pub fn test_privacy_config() -> PrivacyConfig {
331 PrivacyConfig {
332 profile: PrivacyProfile::Test,
333 command_allowlist: None,
334 command_blocklist: HashSet::new(),
335 disabled_tools: HashSet::new(),
336 storage_key_blocklist: HashSet::new(),
337 redactor: Redactor::default(),
338 redaction_enabled: true,
339 }
340}
341
342#[must_use]
347#[doc(hidden)]
348pub fn strict_privacy_config() -> PrivacyConfig {
349 observe_privacy_config()
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
359 fn storage_key_blocklist_protects_keys() {
360 let mut config = PrivacyConfig::default();
361 assert!(config.is_storage_key_allowed("auth"));
363 config.storage_key_blocklist = ["auth", "license_tier"]
365 .iter()
366 .map(ToString::to_string)
367 .collect();
368 assert!(!config.is_storage_key_allowed("auth"));
369 assert!(!config.is_storage_key_allowed("license_tier"));
370 assert!(config.is_storage_key_allowed("theme"));
371 }
372
373 #[test]
374 fn default_allows_all_commands() {
375 let config = PrivacyConfig::default();
376 assert!(config.is_command_allowed("get_settings"));
377 assert!(config.is_command_allowed("anything"));
378 }
379
380 #[test]
381 fn blocklist_blocks() {
382 let mut config = PrivacyConfig::default();
383 config.command_blocklist.insert("save_api_key".to_string());
384 assert!(!config.is_command_allowed("save_api_key"));
385 assert!(config.is_command_allowed("get_settings"));
386 }
387
388 #[test]
389 fn allowlist_restricts() {
390 let mut allow = HashSet::new();
391 allow.insert("get_settings".to_string());
392 allow.insert("get_monitoring_status".to_string());
393 let config = PrivacyConfig {
394 command_allowlist: Some(allow),
395 ..Default::default()
396 };
397 assert!(config.is_command_allowed("get_settings"));
398 assert!(!config.is_command_allowed("save_api_key"));
399 }
400
401 #[test]
402 fn blocklist_wins_over_allowlist() {
403 let mut allow = HashSet::new();
404 allow.insert("save_api_key".to_string());
405 let mut block = HashSet::new();
406 block.insert("save_api_key".to_string());
407 let config = PrivacyConfig {
408 command_allowlist: Some(allow),
409 command_blocklist: block,
410 ..Default::default()
411 };
412 assert!(!config.is_command_allowed("save_api_key"));
413 }
414
415 #[test]
418 fn full_control_allows_everything() {
419 let config = PrivacyConfig::default();
420 assert_eq!(config.profile, PrivacyProfile::FullControl);
421 assert!(config.is_tool_enabled("eval_js"));
422 assert!(config.is_tool_enabled("screenshot"));
423 assert!(config.is_tool_enabled("invoke_command"));
424 assert!(config.is_tool_enabled("interact"));
425 assert!(config.is_tool_enabled("interact.click"));
426 assert!(config.is_tool_enabled("input.fill"));
427 assert!(config.is_tool_enabled("window.manage"));
428 assert!(config.is_tool_enabled("navigate"));
429 assert!(config.is_tool_enabled("navigate.go_to"));
430 assert!(config.is_tool_enabled("css.inject"));
431 assert!(config.is_tool_enabled("recording"));
432 assert!(config.is_tool_enabled("storage.set"));
433 assert!(config.is_tool_enabled("set_dialog_response"));
434 }
435
436 #[test]
439 fn test_profile_allows_interactions() {
440 let config = test_privacy_config();
441 assert!(config.is_tool_enabled("interact"));
442 assert!(config.is_tool_enabled("interact.click"));
443 assert!(config.is_tool_enabled("interact.double_click"));
444 assert!(config.is_tool_enabled("interact.hover"));
445 assert!(config.is_tool_enabled("interact.focus"));
446 assert!(config.is_tool_enabled("interact.scroll_into_view"));
447 assert!(config.is_tool_enabled("interact.select_option"));
448 }
449
450 #[test]
451 fn test_profile_allows_input() {
452 let config = test_privacy_config();
453 assert!(config.is_tool_enabled("fill"));
454 assert!(config.is_tool_enabled("input.fill"));
455 assert!(config.is_tool_enabled("type_text"));
456 assert!(config.is_tool_enabled("input.type_text"));
457 assert!(config.is_tool_enabled("input.press_key"));
458 }
459
460 #[test]
461 fn test_profile_allows_storage_writes() {
462 let config = test_privacy_config();
463 assert!(config.is_tool_enabled("set_storage"));
464 assert!(config.is_tool_enabled("storage.set"));
465 assert!(config.is_tool_enabled("delete_storage"));
466 assert!(config.is_tool_enabled("storage.delete"));
467 }
468
469 #[test]
470 fn test_profile_allows_recording() {
471 let config = test_privacy_config();
472 assert!(config.is_tool_enabled("recording"));
473 assert!(config.is_tool_enabled("recording.start"));
474 assert!(config.is_tool_enabled("recording.stop"));
475 }
476
477 #[test]
478 fn test_profile_blocks_eval_and_screenshot() {
479 let config = test_privacy_config();
480 assert!(!config.is_tool_enabled("eval_js"));
481 assert!(!config.is_tool_enabled("screenshot"));
482 }
483
484 #[test]
485 fn test_profile_blocks_navigation_mutations() {
486 let config = test_privacy_config();
487 assert!(config.is_tool_enabled("navigate"));
491 assert!(!config.is_tool_enabled("navigate.go_to"));
492 assert!(!config.is_tool_enabled("set_dialog_response"));
493 assert!(!config.is_tool_enabled("navigate.set_dialog_response"));
494 }
495
496 #[test]
497 fn test_profile_blocks_window_mutations() {
498 let config = test_privacy_config();
499 assert!(!config.is_tool_enabled("window.manage"));
500 assert!(!config.is_tool_enabled("window.resize"));
501 assert!(!config.is_tool_enabled("window.move_to"));
502 assert!(!config.is_tool_enabled("window.set_title"));
503 }
504
505 #[test]
506 fn test_profile_blocks_css_injection() {
507 let config = test_privacy_config();
508 assert!(!config.is_tool_enabled("inject_css"));
509 assert!(!config.is_tool_enabled("css.inject"));
510 assert!(!config.is_tool_enabled("css.remove"));
511 }
512
513 #[test]
514 fn test_profile_blocks_invoke_command() {
515 let config = test_privacy_config();
516 assert!(!config.is_tool_enabled("invoke_command"));
517 }
518
519 #[test]
520 fn test_profile_allows_read_only_tools() {
521 let config = test_privacy_config();
522 assert!(config.is_tool_enabled("dom_snapshot"));
523 assert!(config.is_tool_enabled("find_elements"));
524 assert!(config.is_tool_enabled("detect_ghost_commands"));
525 assert!(config.is_tool_enabled("check_ipc_integrity"));
526 assert!(config.is_tool_enabled("get_registry"));
527 assert!(config.is_tool_enabled("get_memory_stats"));
528 assert!(config.is_tool_enabled("get_plugin_info"));
529 assert!(config.is_tool_enabled("resolve_command"));
530 assert!(config.is_tool_enabled("wait_for"));
531 }
532
533 #[test]
534 fn test_profile_blocks_arbitrary_eval_assertions() {
535 let config = test_privacy_config();
538 assert!(!config.is_tool_enabled("verify_state"));
539 assert!(!config.is_tool_enabled("assert_semantic"));
540 }
541
542 #[test]
543 fn test_profile_allows_navigation_reads_and_introspectability() {
544 let config = test_privacy_config();
547 assert!(config.is_tool_enabled("navigate.go_back"));
548 assert!(config.is_tool_enabled("navigate.get_history"));
549 assert!(config.is_tool_enabled("navigate.get_dialog_log"));
550 assert!(config.is_tool_enabled("window.introspectability"));
551 assert!(!config.is_tool_enabled("navigate.go_to"));
553 assert!(!config.is_tool_enabled("navigate.set_dialog_response"));
554 }
555
556 #[test]
559 fn observe_blocks_all_interactions() {
560 let config = observe_privacy_config();
561 assert!(!config.is_tool_enabled("interact"));
562 assert!(!config.is_tool_enabled("interact.click"));
563 assert!(!config.is_tool_enabled("interact.double_click"));
564 assert!(!config.is_tool_enabled("interact.hover"));
565 assert!(!config.is_tool_enabled("interact.focus"));
566 assert!(!config.is_tool_enabled("interact.scroll_into_view"));
567 assert!(!config.is_tool_enabled("interact.select_option"));
568 }
569
570 #[test]
571 fn observe_blocks_all_input() {
572 let config = observe_privacy_config();
573 assert!(!config.is_tool_enabled("fill"));
574 assert!(!config.is_tool_enabled("input.fill"));
575 assert!(!config.is_tool_enabled("type_text"));
576 assert!(!config.is_tool_enabled("input.type_text"));
577 assert!(!config.is_tool_enabled("input.press_key"));
578 }
579
580 #[test]
581 fn observe_blocks_storage_writes() {
582 let config = observe_privacy_config();
583 assert!(!config.is_tool_enabled("set_storage"));
584 assert!(!config.is_tool_enabled("storage.set"));
585 assert!(!config.is_tool_enabled("delete_storage"));
586 assert!(!config.is_tool_enabled("storage.delete"));
587 }
588
589 #[test]
590 fn observe_blocks_recording() {
591 let config = observe_privacy_config();
592 assert!(!config.is_tool_enabled("recording"));
593 assert!(!config.is_tool_enabled("recording.start"));
594 assert!(!config.is_tool_enabled("recording.stop"));
595 }
596
597 #[test]
598 fn observe_blocks_dangerous_tools() {
599 let config = observe_privacy_config();
600 assert!(!config.is_tool_enabled("eval_js"));
601 assert!(!config.is_tool_enabled("screenshot"));
602 assert!(!config.is_tool_enabled("invoke_command"));
603 assert!(!config.is_tool_enabled("navigate"));
604 assert!(!config.is_tool_enabled("navigate.go_to"));
605 assert!(!config.is_tool_enabled("inject_css"));
606 assert!(!config.is_tool_enabled("css.inject"));
607 assert!(!config.is_tool_enabled("css.remove"));
608 assert!(!config.is_tool_enabled("window.manage"));
609 assert!(!config.is_tool_enabled("window.resize"));
610 assert!(!config.is_tool_enabled("window.move_to"));
611 assert!(!config.is_tool_enabled("window.set_title"));
612 }
613
614 #[test]
615 fn observe_allows_read_only_tools() {
616 let config = observe_privacy_config();
617 assert!(config.is_tool_enabled("dom_snapshot"));
618 assert!(config.is_tool_enabled("find_elements"));
619 assert!(config.is_tool_enabled("detect_ghost_commands"));
620 assert!(config.is_tool_enabled("check_ipc_integrity"));
621 assert!(config.is_tool_enabled("get_registry"));
622 assert!(config.is_tool_enabled("get_memory_stats"));
623 assert!(config.is_tool_enabled("get_plugin_info"));
624 assert!(config.is_tool_enabled("get_diagnostics"));
625 assert!(config.is_tool_enabled("resolve_command"));
626 assert!(config.is_tool_enabled("wait_for"));
627 assert!(config.is_tool_enabled("window")); }
629
630 #[test]
631 fn observe_blocks_eval_dependent_tools() {
632 let config = observe_privacy_config();
633 assert!(!config.is_tool_enabled("verify_state"));
636 assert!(!config.is_tool_enabled("assert_semantic"));
637 }
638
639 #[test]
640 fn observe_allows_read_actions_on_compound_tools() {
641 let config = observe_privacy_config();
642 assert!(config.is_tool_enabled("window.get_state"));
644 assert!(config.is_tool_enabled("window.list"));
645 assert!(config.is_tool_enabled("logs"));
647 assert!(config.is_tool_enabled("logs.console"));
648 assert!(config.is_tool_enabled("logs.network"));
649 assert!(config.is_tool_enabled("logs.ipc"));
650 assert!(config.is_tool_enabled("logs.navigation"));
651 assert!(config.is_tool_enabled("logs.dialogs"));
652 assert!(config.is_tool_enabled("logs.events"));
653 assert!(config.is_tool_enabled("logs.slow_ipc"));
654 assert!(config.is_tool_enabled("inspect"));
656 assert!(config.is_tool_enabled("inspect.styles"));
657 assert!(config.is_tool_enabled("inspect.bounds"));
658 assert!(config.is_tool_enabled("inspect.audit_a11y"));
659 assert!(config.is_tool_enabled("inspect.performance"));
660 assert!(!config.is_tool_enabled("inspect.highlight"));
662 assert!(!config.is_tool_enabled("inspect.clear_highlights"));
663 assert!(!config.is_tool_enabled("logs.clear"));
664 }
665
666 #[test]
667 fn observe_allows_app_state_probe_reads() {
668 assert!(observe_privacy_config().is_tool_enabled("app_state"));
670 }
671
672 #[test]
673 fn test_profile_allows_log_clearing_and_highlight() {
674 let config = test_privacy_config();
677 assert!(config.is_tool_enabled("logs.clear"));
678 assert!(config.is_tool_enabled("inspect.highlight"));
679 assert!(config.is_tool_enabled("inspect.clear_highlights"));
680 assert!(config.is_tool_enabled("app_state"));
681 }
682
683 #[test]
684 fn observe_blocks_unlisted_tools() {
685 let config = observe_privacy_config();
686 assert!(!config.is_tool_enabled("navigate.get_history"));
688 assert!(!config.is_tool_enabled("navigate.get_dialog_log"));
689 assert!(!config.is_tool_enabled("css.get_styles"));
690 assert!(!config.is_tool_enabled("css.get_computed"));
691 assert!(!config.is_tool_enabled("storage.get"));
692 assert!(!config.is_tool_enabled("storage.get_cookies"));
693 assert!(!config.is_tool_enabled("navigate.go_back"));
694 }
695
696 #[test]
697 fn observe_enables_redaction() {
698 let config = observe_privacy_config();
699 assert!(config.redaction_enabled);
700 }
701
702 #[test]
705 fn disabled_tools_override_full_control() {
706 let mut disabled = HashSet::new();
707 disabled.insert("eval_js".to_string());
708 let config = PrivacyConfig {
709 profile: PrivacyProfile::FullControl,
710 disabled_tools: disabled,
711 ..Default::default()
712 };
713 assert!(!config.is_tool_enabled("eval_js"));
714 assert!(config.is_tool_enabled("screenshot"));
715 }
716
717 #[test]
718 fn disabled_tools_stack_with_profile() {
719 let mut disabled = HashSet::new();
720 disabled.insert("dom_snapshot".to_string());
721 let mut config = test_privacy_config();
722 config.disabled_tools = disabled;
723 assert!(!config.is_tool_enabled("dom_snapshot"));
725 assert!(!config.is_tool_enabled("eval_js"));
727 }
728
729 #[test]
734 fn inspect_action_spelling_and_capability_id_both_disable() {
735 for (action_spelling, capability) in [
736 ("inspect.get_styles", "inspect.styles"),
737 ("inspect.get_bounding_boxes", "inspect.bounds"),
738 ("inspect.audit_accessibility", "inspect.audit_a11y"),
739 ("inspect.get_performance", "inspect.performance"),
740 ] {
741 for disabled in [action_spelling, capability] {
742 let config = PrivacyConfig {
743 disabled_tools: HashSet::from([disabled.to_string()]),
744 ..Default::default()
745 };
746 assert!(
747 !config.is_call_allowed("inspect", capability),
748 "disabling {disabled:?} must block {capability}"
749 );
750 assert!(
751 !config.is_tool_enabled(action_spelling),
752 "disabling {disabled:?} must block {action_spelling}"
753 );
754 }
755 }
756 let config = PrivacyConfig {
758 disabled_tools: HashSet::from(["inspect.get_styles".to_string()]),
759 ..Default::default()
760 };
761 assert!(config.is_call_allowed("inspect", "inspect.bounds"));
762 assert!(config.is_call_allowed("inspect", "inspect.highlight"));
763 }
764
765 #[test]
768 fn invoke_allowed_in_full_control() {
769 let config = PrivacyConfig::default();
770 assert!(config.is_invoke_allowed("any_command"));
771 }
772
773 #[test]
774 fn invoke_blocked_in_observe() {
775 let config = observe_privacy_config();
776 assert!(!config.is_invoke_allowed("any_command"));
777 }
778
779 #[test]
780 fn invoke_allowed_in_test_with_allowlist() {
781 let mut allow = HashSet::new();
782 allow.insert("greet".to_string());
783 let mut config = test_privacy_config();
784 config.command_allowlist = Some(allow);
785 assert!(config.is_invoke_allowed("greet"));
786 assert!(!config.is_invoke_allowed("delete_user"));
787 }
788
789 #[test]
790 fn invoke_blocked_in_test_without_allowlist() {
791 let config = test_privacy_config();
792 assert!(!config.is_invoke_allowed("greet"));
793 }
794
795 #[test]
798 fn strict_privacy_is_observe_profile() {
799 let config = strict_privacy_config();
800 assert_eq!(config.profile, PrivacyProfile::Observe);
801 assert!(config.redaction_enabled);
802 }
803
804 #[test]
807 fn strict_mode_disables_dangerous_tools() {
808 let config = strict_privacy_config();
809 assert!(!config.is_tool_enabled("eval_js"));
810 assert!(!config.is_tool_enabled("screenshot"));
811 assert!(!config.is_tool_enabled("inject_css"));
812 assert!(!config.is_tool_enabled("navigate"));
813 assert!(!config.is_tool_enabled("invoke_command"));
814 assert!(config.is_tool_enabled("dom_snapshot"));
815 assert!(config.is_tool_enabled("get_memory_stats"));
816 assert!(config.redaction_enabled);
817 }
818
819 #[test]
820 fn strict_mode_blocks_window_mutations() {
821 let config = strict_privacy_config();
822 assert!(!config.is_tool_enabled("window.manage"));
823 assert!(!config.is_tool_enabled("window.resize"));
824 assert!(!config.is_tool_enabled("window.move_to"));
825 assert!(!config.is_tool_enabled("window.set_title"));
826 assert!(config.is_tool_enabled("window"));
827 }
828
829 #[test]
830 fn default_allows_all_actions() {
831 let config = PrivacyConfig::default();
832 assert!(config.is_tool_enabled("invoke_command"));
833 assert!(config.is_tool_enabled("window.manage"));
834 assert!(config.is_tool_enabled("window.resize"));
835 assert!(config.is_tool_enabled("window.move_to"));
836 assert!(config.is_tool_enabled("window.set_title"));
837 }
838
839 #[test]
842 fn redaction_when_enabled() {
843 let config = PrivacyConfig {
844 redaction_enabled: true,
845 ..Default::default()
846 };
847 let output = config.redact_output("key is sk-abc123def456ghi789jkl012mno");
848 assert!(output.contains("[REDACTED]"));
849 }
850
851 #[test]
852 fn no_redaction_when_disabled() {
853 let config = PrivacyConfig::default();
854 let input = "key is sk-abc123def456ghi789jkl012mno";
855 assert_eq!(config.redact_output(input), input);
856 }
857
858 #[test]
861 fn profile_display() {
862 assert_eq!(PrivacyProfile::Observe.to_string(), "observe");
863 assert_eq!(PrivacyProfile::Test.to_string(), "test");
864 assert_eq!(PrivacyProfile::FullControl.to_string(), "full_control");
865 }
866}