Skip to main content

oxios_kernel/tools/kernel/
security_tool.rs

1//! Security tool — wraps `SecurityApi` audit methods behind the `AgentTool` interface.
2//!
3//! Provides agents with security audit query capabilities.
4//! Actions: verify_chain, query_audit, audit_count.
5//!
6//! ## Example
7//!
8//! ```json
9//! { "action": "verify_chain" }
10//! { "action": "query_audit", "from_seq": 0, "to_seq": 100 }
11//! { "action": "audit_count" }
12//! ```
13
14use 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
24/// Agent tool for security audit operations.
25///
26/// Wraps the audit-related methods of the `SecurityApi` domain. Allows agents
27/// to verify audit chain integrity, query audit entries, and check entry count.
28///
29/// ## Actions
30///
31/// | Action          | Description                   | Required params | Optional params           |
32/// |-----------------|-------------------------------|-----------------|---------------------------|
33/// | `verify_chain`  | Verify audit chain integrity  | —               | —                         |
34/// | `query_audit`   | Query audit entries by range  | —               | `from_seq`, `to_seq`      |
35/// | `audit_count`   | Get total audit entry count   | —               | —                         |
36pub struct SecurityTool {
37    audit_trail: Arc<AuditTrail>,
38}
39
40impl SecurityTool {
41    /// Create a new `SecurityTool` from a `KernelHandle`.
42    ///
43    /// Extracts the `AuditTrail` Arc from the kernel's Security API.
44    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}