diff --git a/src/agentsight/src/bin/cli/audit.rs b/src/agentsight/src/bin/cli/audit.rs index 37b09686..fccba5fc 100644 --- a/src/agentsight/src/bin/cli/audit.rs +++ b/src/agentsight/src/bin/cli/audit.rs @@ -25,6 +25,12 @@ pub struct AuditCommand { /// Show summary statistics #[structopt(long)] pub summary: bool, + + /// Hide process_action events whose command/args contain any of these + /// substrings. Repeatable. Useful for filtering shell-startup noise, e.g. + /// `--exclude "command -v" --exclude grepconf`. The hidden count is reported. + #[structopt(long)] + pub exclude: Vec, } impl AuditCommand { @@ -44,9 +50,17 @@ impl AuditCommand { } }; - let event_type = self.event_type.as_ref().and_then(|t| t.parse::().ok()); + let event_type = self + .event_type + .as_ref() + .and_then(|t| t.parse::().ok()); if self.summary { + if !self.exclude.is_empty() { + eprintln!( + "Note: --exclude is not applied to --summary (summary always reflects the full dataset)." + ); + } self.print_summary(&store); return; } @@ -75,25 +89,67 @@ impl AuditCommand { } } + fn is_excluded(&self, record: &agentsight::AuditRecord) -> bool { + use agentsight::AuditExtra; + if self.exclude.is_empty() { + return false; + } + if let AuditExtra::ProcessAction { filename, args, .. } = &record.extra { + let fname = filename.as_deref().unwrap_or(""); + let a = args.as_deref().unwrap_or(""); + return self + .exclude + .iter() + .filter(|p| !p.trim().is_empty()) + .any(|p| fname.contains(p.as_str()) || a.contains(p.as_str())); + } + false + } + fn output_records(&self, records: &[agentsight::AuditRecord], scope: &str) { + let total = records.len(); + let filtered: Vec<&agentsight::AuditRecord> = + records.iter().filter(|r| !self.is_excluded(r)).collect(); + let hidden = total - filtered.len(); + if self.json { - let json_records: Vec = records.iter().map(|r| { - serde_json::json!({ - "id": r.id, - "event_type": r.event_type.to_string(), - "timestamp_ns": r.timestamp_ns, - "pid": r.pid, - "ppid": r.ppid, - "comm": r.comm, - "duration_ns": r.duration_ns, - "extra": r.extra, + let json_records: Vec = filtered + .iter() + .map(|r| { + serde_json::json!({ + "id": r.id, + "event_type": r.event_type.to_string(), + "timestamp_ns": r.timestamp_ns, + "pid": r.pid, + "ppid": r.ppid, + "comm": r.comm, + "duration_ns": r.duration_ns, + "extra": r.extra, + }) }) - }).collect(); + .collect(); println!("{}", serde_json::to_string_pretty(&json_records).unwrap()); + if hidden > 0 { + eprintln!( + "{} events hidden by --exclude ({} shown, {} total)", + hidden, + filtered.len(), + total + ); + } } else { - println!("{}: {} audit events", scope, records.len()); + if hidden > 0 { + println!( + "{}: {} audit events ({} hidden by --exclude)", + scope, + filtered.len(), + hidden + ); + } else { + println!("{}: {} audit events", scope, filtered.len()); + } println!(); - for record in records { + for record in &filtered { let json_record = serde_json::json!({ "id": record.id, "event_type": record.event_type.to_string(),