use chrono::{DateTime, Utc};
use glob::Pattern;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentPermissions {
pub agent_name: String,
#[serde(default)]
pub allowed_tools: HashSet<String>,
#[serde(default)]
pub allowed_paths: Vec<String>,
#[serde(default)]
pub denied_paths: Vec<String>,
#[serde(default)]
pub network_access: bool,
#[serde(default)]
pub max_execution_time_secs: u64,
#[serde(default)]
pub max_memory_mb: u64,
#[serde(default)]
pub can_fork: bool,
}
impl Default for AgentPermissions {
fn default() -> Self {
Self {
agent_name: String::new(),
allowed_tools: ["read", "write", "edit", "bash", "grep", "find"]
.iter()
.map(|s| s.to_string())
.collect(),
allowed_paths: vec!["/workspace/**".to_string()],
denied_paths: vec![
"/etc/**".to_string(),
"/root/**".to_string(),
"/sys/**".to_string(),
"/proc/**".to_string(),
".oxios/**".to_string(),
],
network_access: false,
max_execution_time_secs: 300,
max_memory_mb: 512,
can_fork: false,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PermissionUpdate {
#[serde(default)]
pub allowed_tools: Option<HashSet<String>>,
#[serde(default)]
pub allowed_paths: Option<Vec<String>>,
#[serde(default)]
pub denied_paths: Option<Vec<String>>,
#[serde(default)]
pub network_access: Option<bool>,
#[serde(default)]
pub max_execution_time_secs: Option<u64>,
#[serde(default)]
pub max_memory_mb: Option<u64>,
#[serde(default)]
pub can_fork: Option<bool>,
}
impl PermissionUpdate {
pub fn apply(&self, perms: &mut AgentPermissions) {
if let Some(tools) = &self.allowed_tools {
perms.allowed_tools = tools.clone();
}
if let Some(paths) = &self.allowed_paths {
perms.allowed_paths = paths.clone();
}
if let Some(paths) = &self.denied_paths {
perms.denied_paths = paths.clone();
}
if let Some(v) = self.network_access {
perms.network_access = v;
}
if let Some(v) = self.max_execution_time_secs {
perms.max_execution_time_secs = v;
}
if let Some(v) = self.max_memory_mb {
perms.max_memory_mb = v;
}
if let Some(v) = self.can_fork {
perms.can_fork = v;
}
}
}
impl AgentPermissions {
pub fn for_new_agent(agent_name: &str) -> Self {
Self {
agent_name: agent_name.to_string(),
..Default::default()
}
}
pub fn allow_tool(&mut self, tool: &str) {
self.allowed_tools.insert(tool.to_string());
}
pub fn deny_tool(&mut self, tool: &str) {
self.allowed_tools.remove(tool);
}
pub fn allow_path(&mut self, path: &str) {
if !self.allowed_paths.contains(&path.to_string()) {
self.allowed_paths.push(path.to_string());
}
}
pub fn deny_path(&mut self, path: &str) {
if !self.denied_paths.contains(&path.to_string()) {
self.denied_paths.push(path.to_string());
}
}
pub fn enable_network(&mut self) {
self.network_access = true;
}
pub fn enable_forking(&mut self) {
self.can_fork = true;
}
pub(crate) fn is_path_denied(&self, path: &str) -> bool {
for pattern in &self.denied_paths {
if let Ok(p) = Pattern::new(pattern) {
if p.matches(path) {
return true;
}
}
}
false
}
pub(crate) fn is_path_allowed(&self, path: &str) -> bool {
for pattern in &self.allowed_paths {
if let Ok(p) = Pattern::new(pattern) {
if p.matches(path) {
return true;
}
}
}
false
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEntry {
pub timestamp: DateTime<Utc>,
pub agent_name: String,
pub action: String,
pub resource: String,
pub allowed: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
impl AuditEntry {
pub(crate) fn new(
agent_name: &str,
action: &str,
resource: &str,
allowed: bool,
reason: Option<String>,
) -> Self {
Self {
timestamp: Utc::now(),
agent_name: agent_name.to_string(),
action: action.to_string(),
resource: resource.to_string(),
allowed,
reason,
}
}
}