oxios_kernel/tools/builtin/
security_tool.rs1use std::sync::Arc;
15
16use async_trait::async_trait;
17use oxi_sdk::observability::AuditTrail;
18use oxi_sdk::{AgentTool, AgentToolResult, ToolContext};
19use serde_json::{json, Value};
20use tokio::sync::oneshot;
21
22use crate::kernel_handle::KernelHandle;
23
24pub struct SecurityTool {
37 audit_trail: Arc<AuditTrail>,
38}
39
40impl SecurityTool {
41 pub fn from_kernel(kernel: &KernelHandle) -> Self {
45 Self {
46 audit_trail: kernel.security.audit_trail.clone(),
47 }
48 }
49}
50
51impl std::fmt::Debug for SecurityTool {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("SecurityTool").finish()
54 }
55}
56
57#[async_trait]
58impl AgentTool for SecurityTool {
59 fn name(&self) -> &str {
60 "security"
61 }
62
63 fn label(&self) -> &str {
64 "Security"
65 }
66
67 fn description(&self) -> &'static str {
68 "Query security audit trail — verify chain integrity, list entries, check count. \
69 Actions: verify_chain, query_audit, audit_count."
70 }
71
72 fn parameters_schema(&self) -> Value {
73 json!({
74 "type": "object",
75 "properties": {
76 "action": {
77 "type": "string",
78 "enum": ["verify_chain", "query_audit", "audit_count"],
79 "description": "Security operation to perform"
80 },
81 "from_seq": {
82 "type": "integer",
83 "description": "Start sequence number for query_audit (default: 0)"
84 },
85 "to_seq": {
86 "type": "integer",
87 "description": "End sequence number for query_audit (default: latest)"
88 }
89 },
90 "required": ["action"]
91 })
92 }
93
94 async fn execute(
95 &self,
96 _tool_call_id: &str,
97 params: Value,
98 _signal: Option<oneshot::Receiver<()>>,
99 _ctx: &ToolContext,
100 ) -> Result<AgentToolResult, String> {
101 let action = params
102 .get("action")
103 .and_then(|v| v.as_str())
104 .ok_or_else(|| "Missing required parameter: action".to_string())?;
105
106 match action {
107 "verify_chain" => match self.audit_trail.verify() {
108 Ok(valid) => Ok(AgentToolResult::success(
109 serde_json::to_string(&json!({
110 "chain_integrity": valid,
111 "status": if valid { "intact" } else { "TAMPERED" },
112 }))
113 .unwrap_or_default(),
114 )),
115 Err(e) => Ok(AgentToolResult::error(format!(
116 "Chain verification failed: {e:?}"
117 ))),
118 },
119
120 "query_audit" => {
121 let from_seq = params["from_seq"].as_u64().unwrap_or(0);
122 let to_seq = params["to_seq"].as_u64().unwrap_or(u64::MAX);
123
124 let entries = self.audit_trail.entries(from_seq, to_seq);
125 if entries.is_empty() {
126 return Ok(AgentToolResult::success(
127 "No audit entries found in the specified range.",
128 ));
129 }
130
131 let display: Vec<Value> = entries
132 .iter()
133 .map(|entry| {
134 json!({
135 "seq": entry.seq,
136 "actor": entry.actor,
137 "action": format!("{:?}", entry.action),
138 "resource": entry.resource,
139 "timestamp": entry.timestamp,
140 })
141 })
142 .collect();
143
144 Ok(AgentToolResult::success(
145 serde_json::to_string_pretty(&json!({
146 "entries": display,
147 "count": display.len(),
148 }))
149 .unwrap_or_default(),
150 ))
151 }
152
153 "audit_count" => {
154 let count = self.audit_trail.len();
155 Ok(AgentToolResult::success(
156 serde_json::to_string(&json!({ "audit_entry_count": count }))
157 .unwrap_or_default(),
158 ))
159 }
160
161 other => Err(format!(
162 "Unknown security action '{other}'. Valid: verify_chain, query_audit, audit_count"
163 )),
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_schema_structure() {
174 let schema = json!({
175 "type": "object",
176 "properties": {
177 "action": {
178 "type": "string",
179 "enum": ["verify_chain", "query_audit", "audit_count"]
180 },
181 "from_seq": { "type": "integer" },
182 "to_seq": { "type": "integer" }
183 },
184 "required": ["action"]
185 });
186
187 let actions = schema["properties"]["action"]["enum"].as_array().unwrap();
188 assert_eq!(actions.len(), 3);
189 assert!(actions.iter().any(|a| a == "verify_chain"));
190 assert!(actions.iter().any(|a| a == "query_audit"));
191 assert!(actions.iter().any(|a| a == "audit_count"));
192 }
193}