pub mod config;
pub mod dependencies;
pub mod history;
pub mod hooks;
pub mod network;
pub mod permissions;
pub mod secrets;
use anyhow::Result;
use rayon::prelude::*;
use crate::finding::Finding;
use crate::paths::{FrameworkHint, InstallRoot};
#[derive(Debug, Clone)]
pub struct ScanContext {
pub root: std::path::PathBuf,
#[allow(dead_code)]
pub framework: FrameworkHint,
}
impl ScanContext {
#[allow(dead_code)]
pub fn from_root(root: &InstallRoot) -> Self {
ScanContext {
root: root.path.clone(),
framework: root.framework,
}
}
#[allow(dead_code)]
pub fn subpath(&self, sub: &str) -> Option<std::path::PathBuf> {
let p = self.root.join(sub);
if p.exists() {
Some(p)
} else {
None
}
}
}
pub trait Scanner: Send + Sync {
fn name(&self) -> &'static str;
fn scan(&self, ctx: &ScanContext) -> Result<Vec<Finding>>;
}
pub fn run_all(ctx: &ScanContext) -> Vec<Finding> {
let scanners: Vec<Box<dyn Scanner>> = vec![
Box::new(secrets::SecretsScanner),
Box::new(permissions::PermissionsScanner),
Box::new(config::ConfigScanner),
Box::new(network::NetworkScanner),
Box::new(hooks::HooksScanner),
Box::new(dependencies::DependenciesScanner),
Box::new(history::HistoryScanner),
];
scanners
.par_iter()
.flat_map(|s| match s.scan(ctx) {
Ok(findings) => findings,
Err(e) => {
eprintln!("ocls: scanner '{}' error: {}", s.name(), e);
vec![]
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn scan_context_subpath_nonexistent() {
let ctx = ScanContext {
root: PathBuf::from("/tmp/__ocls_test_nonexistent__"),
framework: FrameworkHint::Unknown,
};
assert!(ctx.subpath("settings.json").is_none());
}
#[test]
fn scan_context_subpath_existing() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("settings.json"), b"{}").unwrap();
let ctx = ScanContext {
root: dir.path().to_path_buf(),
framework: FrameworkHint::Unknown,
};
assert!(ctx.subpath("settings.json").is_some());
}
}