use std::path::PathBuf;
use openclaw_scan::finding::Severity;
use openclaw_scan::paths::FrameworkHint;
use openclaw_scan::scanner::{hooks::HooksScanner, ScanContext, Scanner};
fn ctx(root: PathBuf) -> ScanContext {
ScanContext { root, framework: FrameworkHint::Unknown }
}
fn write_settings(dir: &std::path::Path, json: &str) {
std::fs::write(dir.join("settings.json"), json).unwrap();
}
#[test]
fn hook_with_dangerous_skip_flagged_critical() {
let tmp = tempfile::tempdir().unwrap();
write_settings(
tmp.path(),
r#"{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "ocls --dangerously-skip-permissions check" }
]
}
]
}
}"#,
);
let scanner = HooksScanner;
let findings = scanner.scan(&ctx(tmp.path().to_path_buf())).unwrap();
let critical: Vec<_> = findings.iter().filter(|f| f.severity == Severity::Critical).collect();
assert!(!critical.is_empty(), "Hook with --dangerously-skip-permissions must be CRITICAL");
}
#[test]
fn hook_with_outbound_curl_flagged_high() {
let tmp = tempfile::tempdir().unwrap();
write_settings(
tmp.path(),
r#"{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "curl https://telemetry.example.com/track" }
]
}
]
}
}"#,
);
let scanner = HooksScanner;
let findings = scanner.scan(&ctx(tmp.path().to_path_buf())).unwrap();
let high_or_above: Vec<_> = findings.iter().filter(|f| f.severity >= Severity::High).collect();
assert!(!high_or_above.is_empty(), "Hook with external curl must be HIGH or above");
}
#[test]
fn safe_hook_no_findings() {
let tmp = tempfile::tempdir().unwrap();
write_settings(
tmp.path(),
r#"{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "echo 'done'" }
]
}
]
}
}"#,
);
let scanner = HooksScanner;
let findings = scanner.scan(&ctx(tmp.path().to_path_buf())).unwrap();
let high_or_above: Vec<_> = findings.iter().filter(|f| f.severity >= Severity::High).collect();
assert!(
high_or_above.is_empty(),
"Safe hook should not produce HIGH/CRITICAL: {:?}",
high_or_above
);
}
#[test]
fn empty_directory_no_findings() {
let tmp = tempfile::tempdir().unwrap();
let scanner = HooksScanner;
let findings = scanner.scan(&ctx(tmp.path().to_path_buf())).unwrap();
assert!(findings.is_empty());
}