oxios_kernel/tools/kernel/
security_tool.rs1use std::sync::Arc;
15
16use async_trait::async_trait;
17use oxi_sdk::{AgentTool, AgentToolResult, ToolContext};
18use serde_json::{json, Value};
19use tokio::sync::oneshot;
20
21use crate::audit_trail::AuditTrail;
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: {:?}",
117 e
118 ))),
119 },
120
121 "query_audit" => {
122 let from_seq = params["from_seq"].as_u64().unwrap_or(0);
123 let to_seq = params["to_seq"].as_u64().unwrap_or(u64::MAX);
124
125 let entries = self.audit_trail.entries(from_seq, to_seq);
126 if entries.is_empty() {
127 return Ok(AgentToolResult::success(
128 "No audit entries found in the specified range.",
129 ));
130 }
131
132 let display: Vec<Value> = entries
133 .iter()
134 .map(|entry| {
135 json!({
136 "seq": entry.seq,
137 "actor": entry.actor,
138 "action": format!("{:?}", entry.action),
139 "resource": entry.resource,
140 "timestamp": entry.timestamp,
141 })
142 })
143 .collect();
144
145 Ok(AgentToolResult::success(
146 serde_json::to_string_pretty(&json!({
147 "entries": display,
148 "count": display.len(),
149 }))
150 .unwrap_or_default(),
151 ))
152 }
153
154 "audit_count" => {
155 let count = self.audit_trail.len();
156 Ok(AgentToolResult::success(
157 serde_json::to_string(&json!({ "audit_entry_count": count }))
158 .unwrap_or_default(),
159 ))
160 }
161
162 other => Err(format!(
163 "Unknown security action '{}'. Valid: verify_chain, query_audit, audit_count",
164 other
165 )),
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn test_schema_structure() {
176 let schema = json!({
177 "type": "object",
178 "properties": {
179 "action": {
180 "type": "string",
181 "enum": ["verify_chain", "query_audit", "audit_count"]
182 },
183 "from_seq": { "type": "integer" },
184 "to_seq": { "type": "integer" }
185 },
186 "required": ["action"]
187 });
188
189 let actions = schema["properties"]["action"]["enum"].as_array().unwrap();
190 assert_eq!(actions.len(), 3);
191 assert!(actions.iter().any(|a| a == "verify_chain"));
192 assert!(actions.iter().any(|a| a == "query_audit"));
193 assert!(actions.iter().any(|a| a == "audit_count"));
194 }
195}