use funera_core::security::audit::AuditBus;
use funera_core::security::policy::{ShellPolicy, ToolPolicy};
use funera_core::security::registry::GuardedToolRegistry;
use serde_json::json;
fn main() {
println!("====================================");
println!(" Funera Tool Policy — Demo");
println!("====================================\n");
demo_permissive();
demo_strict();
demo_denied_tools();
demo_allowed_tools();
demo_shell_command_whitelist();
demo_dangerous_patterns();
demo_args_size_limit();
demo_timeout_limit();
demo_workdir_restriction();
demo_guarded_registry();
demo_audit_integration();
println!("\nAll demos completed successfully.");
}
fn println_pass(msg: &str) {
println!(" [PASS] {msg}");
}
fn println_fail(msg: &str) {
println!(" [FAIL] {msg}");
}
fn demo_permissive() {
println!("--- 1. Permissive Policy (default) ---");
let policy = ToolPolicy::permissive();
assert!(policy.check_tool_allowed("any_tool").is_ok());
assert!(policy.check_tool_allowed("shell").is_ok());
assert!(policy.check_tool_allowed("random").is_ok());
println_pass("all tools allowed by default");
assert!(policy.max_args_size == 1024 * 1024);
println_pass("default max_args_size = 1 MiB");
assert!(policy.max_timeout_secs == 300.0);
println_pass("default max_timeout_secs = 300s");
println!();
}
fn demo_strict() {
println!("--- 2. Strict Policy ---");
let policy = ToolPolicy::strict();
let result = policy.check_tool_allowed("shell");
assert!(result.is_err());
println_fail(&format!("'shell' blocked by strict policy: {result:?}"));
let result = policy.check_tool_allowed("read");
assert!(result.is_err());
println_fail(&format!("'read' blocked by strict policy: {result:?}"));
let sp = policy.shell_policy.as_ref().unwrap();
assert!(sp.block_builtin_dangerous);
println_pass("strict shell_policy has block_builtin_dangerous = true");
println!();
}
fn demo_denied_tools() {
println!("--- 3. Denied Tools (blacklist) ---");
let mut policy = ToolPolicy::default();
policy.denied_tools.insert("danger".into());
policy.denied_tools.insert("shell".into());
assert!(policy.check_tool_allowed("danger").is_err());
assert!(policy.check_tool_allowed("shell").is_err());
assert!(policy.check_tool_allowed("read").is_ok());
println_fail("'danger' is denied");
println_fail("'shell' is denied");
println_pass("'read' is still allowed");
println!();
}
fn demo_allowed_tools() {
println!("--- 4. Allowed Tools (whitelist) ---");
use std::collections::HashSet;
let mut allowed = HashSet::new();
allowed.insert("read".into());
allowed.insert("write".into());
let policy = ToolPolicy {
allowed_tools: Some(allowed),
..Default::default()
};
assert!(policy.check_tool_allowed("read").is_ok());
assert!(policy.check_tool_allowed("write").is_ok());
assert!(policy.check_tool_allowed("shell").is_err());
assert!(policy.check_tool_allowed("edit").is_err());
println_pass("'read' and 'write' are allowed");
println_fail("'shell' is not in the whitelist");
println_fail("'edit' is not in the whitelist");
println!();
}
fn demo_shell_command_whitelist() {
println!("--- 5. Shell Command Whitelisting ---");
let sp = ShellPolicy::with_allowed(vec!["git".into(), "cargo".into(), "npm".into()]);
assert!(sp.check_command("git status").is_ok());
assert!(sp.check_command("cargo build").is_ok());
assert!(sp.check_command("npm install").is_ok());
println_pass("'git status' allowed");
println_pass("'cargo build' allowed");
println_pass("'npm install' allowed");
assert!(sp.check_command("ls -la").is_err());
assert!(sp.check_command("mkdir foo").is_err());
println_fail("'ls -la' not in allowed list");
println_fail("'mkdir foo' not in allowed list");
assert!(sp.check_command("rm -rf /").is_err());
println_fail("'rm -rf /' blocked as dangerous + not in allowed list");
println!();
}
fn demo_dangerous_patterns() {
println!("--- 6. Dangerous Pattern Blocking ---");
let sp = ShellPolicy::strict();
let dangerous_cmds = [
("rm -rf /", "recursive force remove"),
("sudo rm -rf /tmp", "sudo + dangerous remove"),
("diskpart", "disk partition tool"),
("format C:", "format drive"),
("reg add HKCU\\...", "registry modification"),
("shutdown /s", "system shutdown"),
];
for (cmd, desc) in &dangerous_cmds {
assert!(sp.check_command(cmd).is_err());
println_fail(&format!("\"{cmd}\" blocked ({desc})"));
}
assert!(sp.check_command("ls -la").is_ok());
assert!(sp.check_command("echo hello").is_ok());
assert!(sp.check_command("git status").is_ok());
println_pass("'ls -la' allowed");
println_pass("'echo hello' allowed");
println_pass("'git status' allowed");
println!();
}
fn demo_args_size_limit() {
println!("--- 7. Argument Size Limit ---");
let policy = ToolPolicy {
max_args_size: 20,
..Default::default()
};
let small = json!({"a": 1});
assert!(policy.check_args(&small).is_ok());
println_pass("small args (few bytes) allowed");
let large = json!({"data": "this is a large value that exceeds the size limit"});
assert!(policy.check_args(&large).is_err());
println_fail("large args rejected (exceeds max_args_size=20)");
println!();
}
fn demo_timeout_limit() {
println!("--- 8. Timeout Limit ---");
let policy = ToolPolicy {
max_timeout_secs: 10.0,
..Default::default()
};
assert!(policy.check_timeout(5.0).is_ok());
assert!(policy.check_timeout(10.0).is_ok());
println_pass("timeout 5s <= 10s max → allowed");
println_pass("timeout 10s <= 10s max → allowed");
assert!(policy.check_timeout(30.0).is_err());
assert!(policy.check_timeout(999.0).is_err());
println_fail("timeout 30s > 10s max → rejected");
println_fail("timeout 999s > 10s max → rejected");
println!();
}
fn demo_workdir_restriction() {
println!("--- 9. Workdir Restriction ---");
use std::collections::HashSet;
let mut dirs = HashSet::new();
dirs.insert("/workspace".into());
dirs.insert("/tmp".into());
let policy = ToolPolicy {
allowed_workdirs: dirs,
..Default::default()
};
assert!(policy.check_workdir("/workspace").is_ok());
assert!(policy.check_workdir("/workspace/project").is_ok());
assert!(policy.check_workdir("/tmp/build").is_ok());
println_pass("'/workspace/project' is under allowed dir");
assert!(policy.check_workdir("/etc").is_err());
assert!(policy.check_workdir("/home/user").is_err());
println_fail("'/etc' not in allowed workdirs");
println_fail("'/home/user' not in allowed workdirs");
let open = ToolPolicy::default();
assert!(open.check_workdir("/anywhere").is_ok());
println_pass("empty allowed_workdirs → anywhere allowed");
println!();
}
fn demo_guarded_registry() {
println!("--- 10. GuardedToolRegistry Integration ---");
use std::collections::HashSet;
let mut allowed = HashSet::new();
allowed.insert("read".into());
allowed.insert("write".into());
let mut denied = HashSet::new();
denied.insert("shell".into());
let policy = ToolPolicy {
allowed_tools: Some(allowed),
denied_tools: denied,
max_args_size: 50,
..Default::default()
};
let registry = GuardedToolRegistry::new_from_policy(policy);
let stored_policy = registry.policy();
assert!(stored_policy.max_args_size == 50);
println_pass("policy flows into registry");
assert!(stored_policy.check_tool_allowed("read").is_ok());
assert!(stored_policy.check_tool_allowed("shell").is_err());
println_pass("tools filtered by policy via registry");
let mut mut_registry = GuardedToolRegistry::new();
mut_registry.policy_mut().denied_tools.insert("evil".into());
mut_registry.policy_mut().max_args_size = 100;
assert!(mut_registry.policy().check_tool_allowed("evil").is_err());
assert!(mut_registry.policy().max_args_size == 100);
println_pass("policy can be mutated via policy_mut()");
let mut all_allowed = ToolPolicy::default();
all_allowed.denied_tools.insert("blocked".into());
let from_registry: GuardedToolRegistry = all_allowed.into();
assert!(
from_registry
.policy()
.check_tool_allowed("blocked")
.is_err()
);
println_pass("GuardedToolRegistry can be created via From<ToolPolicy>");
println!();
}
fn demo_audit_integration() {
println!("--- 11. Audit Bus Integration ---");
use async_trait::async_trait;
use funera_core::re_act::tool::{Tool, ToolCallError};
use serde_json::Value as JsonValue;
struct DummyTool;
#[async_trait]
impl Tool for DummyTool {
fn name(&self) -> &str {
"blocked_tool"
}
fn description(&self) -> &str {
"a tool that is blocked by policy"
}
fn schema(&self) -> JsonValue {
json!({})
}
async fn execute(&self, _args: JsonValue) -> Result<String, ToolCallError> {
Ok("ok".into())
}
}
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let mut policy = ToolPolicy::default();
policy.denied_tools.insert("blocked_tool".into());
let bus = AuditBus::new(16);
let mut rx = bus.subscribe();
let mut registry = GuardedToolRegistry::new_from_policy(policy);
registry.set_audit_bus(bus);
registry.add_tool(Box::new(DummyTool));
let result = registry.call_tool("blocked_tool", json!({})).await;
assert!(result.is_err());
println_fail("'blocked_tool' call rejected by registry");
let event = rx.try_recv().expect("expected ToolDenied audit event");
match event {
funera_core::security::audit::AuditEvent::ToolDenied {
ref tool_name,
ref reason,
..
} => {
assert_eq!(tool_name, "blocked_tool");
assert!(reason.contains("denied by policy"));
println_pass(&format!("audit bus received ToolDenied: \"{reason}\""));
}
e => panic!("expected ToolDenied, got {e:?}"),
}
});
println!();
}