vtcode_core/command_safety/
mod.rs1pub mod audit;
16pub mod cache;
17pub mod command_db;
18pub mod dangerous_commands;
19pub mod safe_command_registry;
20pub mod shell_parser;
21pub mod unified;
22#[cfg(windows)]
23pub mod windows;
24#[cfg(windows)]
25pub mod windows_cmdlet_db;
26#[cfg(windows)]
27pub mod windows_com_analyzer;
28#[cfg(windows)]
29pub mod windows_enhanced;
30#[cfg(windows)]
31pub mod windows_registry_filter;
32
33#[cfg(test)]
34mod integration_tests;
35
36pub use audit::{AuditEntry, SafetyAuditLogger};
37pub use cache::SafetyDecisionCache;
38pub use command_db::CommandDatabase;
39pub use dangerous_commands::command_might_be_dangerous;
40pub use safe_command_registry::{SafeCommandRegistry, SafetyDecision};
41pub use shell_parser::parse_bash_lc_commands;
42pub use unified::{
43 EvaluationReason, EvaluationResult, PolicyAwareEvaluator, UnifiedCommandEvaluator,
44};
45#[cfg(windows)]
46pub use windows_cmdlet_db::{CmdletCategory, CmdletDatabase, CmdletInfo, CmdletSeverity};
47#[cfg(windows)]
48pub use windows_com_analyzer::{ComObjectAnalyzer, ComObjectContext, ComObjectInfo, ComRiskLevel};
49#[cfg(windows)]
50pub use windows_enhanced::is_dangerous_windows_enhanced;
51#[cfg(windows)]
52pub use windows_registry_filter::{
53 RegistryAccessFilter, RegistryAccessPattern, RegistryPathInfo, RegistryRiskLevel,
54};
55
56pub fn is_safe_command(registry: &SafeCommandRegistry, command: &[String]) -> bool {
59 if command.is_empty() {
60 return false;
61 }
62
63 if command_might_be_dangerous(command) {
65 return false;
66 }
67
68 matches!(registry.is_safe(command), SafetyDecision::Allow)
70}
71
72pub fn shell_string_might_be_dangerous(command: &str) -> bool {
77 if let Ok(parsed_commands) = shell_parser::parse_shell_commands(command)
78 && parsed_commands
79 .iter()
80 .any(|cmd| !cmd.is_empty() && command_might_be_dangerous(cmd))
81 {
82 return true;
83 }
84
85 let fallback_tokens: Vec<String> = command
86 .split_whitespace()
87 .map(ToString::to_string)
88 .collect();
89 !fallback_tokens.is_empty() && command_might_be_dangerous(&fallback_tokens)
90}
91
92pub fn validate_command_safety(command: &str) -> anyhow::Result<()> {
98 use anyhow::bail;
99
100 if command.len() < 3 {
101 return Ok(());
102 }
103
104 let segments = shell_parser::split_shell_segments(command)?;
105
106 if shell_string_might_be_dangerous(command) {
107 bail!("Potential dangerous command detected");
108 }
109
110 for segment in segments {
111 if let Some(pattern) = shell_parser::additional_dangerous_pattern(&segment) {
112 bail!("Potential dangerous command: {pattern}");
113 }
114 }
115
116 Ok(())
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn empty_command_is_not_safe() {
125 let registry = SafeCommandRegistry::new();
126 assert!(!is_safe_command(®istry, &[]));
127 }
128
129 #[test]
130 fn shell_string_detects_dangerous_sequence() {
131 assert!(shell_string_might_be_dangerous(
132 "echo ok && git reset --hard HEAD~1"
133 ));
134 }
135}