use async_trait::async_trait;
use brainwires_core::ToolUse;
use brainwires_tools::{PreHookDecision, ToolContext, ToolPreHook};
use serde_json::json;
struct SafetyGuardHook {
blocked_tools: Vec<String>,
blocked_patterns: Vec<String>,
}
impl SafetyGuardHook {
fn new() -> Self {
Self {
blocked_tools: vec!["delete_file".to_string(), "deploy_production".to_string()],
blocked_patterns: vec!["rm -rf".to_string(), "DROP TABLE".to_string()],
}
}
}
#[async_trait]
impl ToolPreHook for SafetyGuardHook {
async fn before_execute(
&self,
tool_use: &ToolUse,
_context: &ToolContext,
) -> anyhow::Result<PreHookDecision> {
if self.blocked_tools.contains(&tool_use.name) {
return Ok(PreHookDecision::Reject(format!(
"Tool '{}' is blocked by safety policy.",
tool_use.name
)));
}
let input_str = tool_use.input.to_string();
for pattern in &self.blocked_patterns {
if input_str.contains(pattern) {
return Ok(PreHookDecision::Reject(format!(
"Input contains blocked pattern: '{pattern}'"
)));
}
}
Ok(PreHookDecision::Allow)
}
}
struct AuditLogHook;
#[async_trait]
impl ToolPreHook for AuditLogHook {
async fn before_execute(
&self,
tool_use: &ToolUse,
context: &ToolContext,
) -> anyhow::Result<PreHookDecision> {
println!(
"[AUDIT] Tool='{}' id='{}' cwd='{}' input={}",
tool_use.name, tool_use.id, context.working_directory, tool_use.input,
);
Ok(PreHookDecision::Allow)
}
}
fn mock_tool_use(name: &str, input: serde_json::Value) -> ToolUse {
ToolUse {
id: format!("call_{name}"),
name: name.to_string(),
input,
}
}
fn mock_context() -> ToolContext {
ToolContext {
working_directory: "/home/user/project".to_string(),
user_id: Some("demo-user".to_string()),
metadata: Default::default(),
capabilities: None,
idempotency_registry: None,
staging_backend: None,
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
println!("=== Tool PreHook Example ===\n");
let safety = SafetyGuardHook::new();
let audit = AuditLogHook;
let ctx = mock_context();
let safe_call = mock_tool_use(
"read_file",
json!({ "path": "/home/user/project/README.md" }),
);
let decision = safety.before_execute(&safe_call, &ctx).await?;
println!("Scenario A (read_file):");
println!(" Decision: {decision:?}");
assert_eq!(decision, PreHookDecision::Allow);
let _ = audit.before_execute(&safe_call, &ctx).await?;
let blocked_call = mock_tool_use("delete_file", json!({ "path": "/etc/important.conf" }));
let decision = safety.before_execute(&blocked_call, &ctx).await?;
println!("\nScenario B (delete_file):");
println!(" Decision: {decision:?}");
assert_eq!(
decision,
PreHookDecision::Reject("Tool 'delete_file' is blocked by safety policy.".to_string())
);
let dangerous_input = mock_tool_use(
"execute_command",
json!({ "command": "rm -rf /important/data" }),
);
let decision = safety.before_execute(&dangerous_input, &ctx).await?;
println!("\nScenario C (execute_command with 'rm -rf'):");
println!(" Decision: {decision:?}");
assert_eq!(
decision,
PreHookDecision::Reject("Input contains blocked pattern: 'rm -rf'".to_string())
);
let safe_cmd = mock_tool_use("execute_command", json!({ "command": "ls -la" }));
let decision = safety.before_execute(&safe_cmd, &ctx).await?;
println!("\nScenario D (execute_command with 'ls -la'):");
println!(" Decision: {decision:?}");
assert_eq!(decision, PreHookDecision::Allow);
println!("\nAll scenarios passed.");
Ok(())
}