use std::path::Path;
use std::sync::Arc;
use leviath_runtime::taint::ScriptRuleChecker;
pub fn build_gate_script_checker(rules_dir: &Path) -> Arc<ScriptRuleChecker> {
let scripts: Vec<(String, String)> = std::fs::read_dir(rules_dir)
.ok()
.into_iter()
.flatten() .flatten() .filter_map(|entry| {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("rhai") {
return None;
}
let name = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("rule")
.to_string();
std::fs::read_to_string(&path)
.ok()
.map(|source| (name, source))
})
.collect();
if scripts.is_empty() {
return Arc::new(|_tool, _target, _taint| None);
}
let engine = leviath_scripting::ScriptEngine::new();
Arc::new(
move |tool: &str,
target: Option<&str>,
taint: leviath_core::TaintLevel|
-> Option<String> {
scripts.iter().find_map(|(name, source)| {
engine
.check_gate_rule(source, tool, target, taint.as_str())
.unwrap_or(false)
.then(|| name.clone())
})
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use leviath_core::TaintLevel;
fn write_rule(dir: &Path, name: &str, body: &str) {
std::fs::write(dir.join(name), body).unwrap();
}
#[test]
fn missing_or_empty_dir_yields_a_noop_checker() {
let dir = tempfile::tempdir().unwrap();
let noop = build_gate_script_checker(&dir.path().join("nope"));
assert_eq!(noop("shell", None, TaintLevel::Public), None);
write_rule(dir.path(), "notes.txt", "ignored");
let noop2 = build_gate_script_checker(dir.path());
assert_eq!(noop2("shell", None, TaintLevel::Public), None);
}
#[test]
fn a_matching_rule_allows_and_names_itself() {
let dir = tempfile::tempdir().unwrap();
write_rule(dir.path(), "company.rhai", r#"context.tool == "shell""#);
let checker = build_gate_script_checker(dir.path());
assert_eq!(
checker("shell", None, TaintLevel::Internal),
Some("company".to_string())
);
assert_eq!(checker("read_file", None, TaintLevel::Internal), None);
}
#[test]
fn a_rule_can_key_on_target_and_taint() {
let dir = tempfile::tempdir().unwrap();
write_rule(
dir.path(),
"internal_only.rhai",
r#"context.taint_level == "internal" && context.target == "ops@corp""#,
);
let checker = build_gate_script_checker(dir.path());
assert!(checker("send_email", Some("ops@corp"), TaintLevel::Internal).is_some());
assert!(checker("send_email", Some("ops@corp"), TaintLevel::Private).is_none());
}
#[test]
fn a_script_that_errors_is_treated_as_no_match() {
let dir = tempfile::tempdir().unwrap();
write_rule(dir.path(), "broken.rhai", "this is not valid rhai @@@");
let checker = build_gate_script_checker(dir.path());
assert_eq!(checker("shell", None, TaintLevel::Public), None);
}
}