openclaw_scan/scanner/
mod.rs1pub mod config;
4pub mod dependencies;
5pub mod history;
6pub mod hooks;
7pub mod network;
8pub mod permissions;
9pub mod secrets;
10
11use anyhow::Result;
12use rayon::prelude::*;
13
14use crate::finding::Finding;
15use crate::paths::{FrameworkHint, InstallRoot};
16
17#[derive(Debug, Clone)]
21pub struct ScanContext {
22 pub root: std::path::PathBuf,
24 #[allow(dead_code)]
26 pub framework: FrameworkHint,
27}
28
29impl ScanContext {
30 #[allow(dead_code)]
31 pub fn from_root(root: &InstallRoot) -> Self {
32 ScanContext {
33 root: root.path.clone(),
34 framework: root.framework,
35 }
36 }
37
38 #[allow(dead_code)]
40 pub fn subpath(&self, sub: &str) -> Option<std::path::PathBuf> {
41 let p = self.root.join(sub);
42 if p.exists() {
43 Some(p)
44 } else {
45 None
46 }
47 }
48}
49
50pub trait Scanner: Send + Sync {
54 fn name(&self) -> &'static str;
55 fn scan(&self, ctx: &ScanContext) -> Result<Vec<Finding>>;
56}
57
58pub fn run_all(ctx: &ScanContext) -> Vec<Finding> {
65 let scanners: Vec<Box<dyn Scanner>> = vec![
66 Box::new(secrets::SecretsScanner),
67 Box::new(permissions::PermissionsScanner),
68 Box::new(config::ConfigScanner),
69 Box::new(network::NetworkScanner),
70 Box::new(hooks::HooksScanner),
71 Box::new(dependencies::DependenciesScanner),
72 Box::new(history::HistoryScanner),
73 ];
74
75 scanners
76 .par_iter()
77 .flat_map(|s| match s.scan(ctx) {
78 Ok(findings) => findings,
79 Err(e) => {
80 eprintln!("ocls: scanner '{}' error: {}", s.name(), e);
81 vec![]
82 }
83 })
84 .collect()
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use std::path::PathBuf;
91
92 #[test]
93 fn scan_context_subpath_nonexistent() {
94 let ctx = ScanContext {
95 root: PathBuf::from("/tmp/__ocls_test_nonexistent__"),
96 framework: FrameworkHint::Unknown,
97 };
98 assert!(ctx.subpath("settings.json").is_none());
99 }
100
101 #[test]
102 fn scan_context_subpath_existing() {
103 let dir = tempfile::tempdir().unwrap();
104 std::fs::write(dir.path().join("settings.json"), b"{}").unwrap();
105 let ctx = ScanContext {
106 root: dir.path().to_path_buf(),
107 framework: FrameworkHint::Unknown,
108 };
109 assert!(ctx.subpath("settings.json").is_some());
110 }
111}