use std::time::Duration;
#[derive(Debug, Clone)]
pub struct SandboxConfig {
pub enabled: bool,
pub policy: SandboxPolicy,
pub allow_full_access: bool,
pub timeout: Duration,
pub memory_limit_mb: u64,
pub cpu_shares: u32,
pub network_allowlist: Vec<String>,
pub image: String,
pub auto_pull_image: bool,
pub proxy_port: u16,
}
impl Default for SandboxConfig {
fn default() -> Self {
Self {
enabled: true, policy: SandboxPolicy::ReadOnly,
allow_full_access: false,
timeout: Duration::from_secs(120),
memory_limit_mb: 2048,
cpu_shares: 1024,
network_allowlist: default_allowlist(),
image: "ironclaw-worker:latest".to_string(),
auto_pull_image: true,
proxy_port: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SandboxPolicy {
#[default]
ReadOnly,
WorkspaceWrite,
FullAccess,
}
impl SandboxPolicy {
pub fn allows_writes(&self) -> bool {
matches!(
self,
SandboxPolicy::WorkspaceWrite | SandboxPolicy::FullAccess
)
}
pub fn has_full_network(&self) -> bool {
matches!(self, SandboxPolicy::FullAccess)
}
pub fn is_sandboxed(&self) -> bool {
!matches!(self, SandboxPolicy::FullAccess)
}
}
impl std::str::FromStr for SandboxPolicy {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"readonly" | "read_only" | "ro" => Ok(SandboxPolicy::ReadOnly),
"workspacewrite" | "workspace_write" | "rw" => Ok(SandboxPolicy::WorkspaceWrite),
"fullaccess" | "full_access" | "full" | "none" => Ok(SandboxPolicy::FullAccess),
_ => Err(format!(
"invalid sandbox policy '{}', expected 'readonly', 'workspace_write', or 'full_access'",
s
)),
}
}
}
#[derive(Debug, Clone)]
pub struct ResourceLimits {
pub memory_bytes: u64,
pub cpu_shares: u32,
pub timeout: Duration,
pub max_output_bytes: usize,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
memory_bytes: 2 * 1024 * 1024 * 1024, cpu_shares: 1024,
timeout: Duration::from_secs(120),
max_output_bytes: 64 * 1024, }
}
}
pub fn default_allowlist() -> Vec<String> {
vec![
"crates.io".to_string(),
"static.crates.io".to_string(),
"index.crates.io".to_string(),
"registry.npmjs.org".to_string(),
"proxy.golang.org".to_string(),
"pypi.org".to_string(),
"files.pythonhosted.org".to_string(),
"docs.rs".to_string(),
"doc.rust-lang.org".to_string(),
"nodejs.org".to_string(),
"go.dev".to_string(),
"docs.python.org".to_string(),
"github.com".to_string(),
"raw.githubusercontent.com".to_string(),
"api.github.com".to_string(),
"codeload.github.com".to_string(),
"api.openai.com".to_string(),
"api.anthropic.com".to_string(),
"api.near.ai".to_string(),
]
}
pub fn default_credential_mappings() -> Vec<crate::secrets::CredentialMapping> {
use crate::secrets::CredentialMapping;
vec![
CredentialMapping::bearer("OPENAI_API_KEY", "api.openai.com"),
CredentialMapping::header("ANTHROPIC_API_KEY", "x-api-key", "api.anthropic.com"),
CredentialMapping::bearer("NEARAI_API_KEY", "api.near.ai"),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_policy_parsing() {
assert_eq!(
"readonly".parse::<SandboxPolicy>().unwrap(),
SandboxPolicy::ReadOnly
);
assert_eq!(
"workspace_write".parse::<SandboxPolicy>().unwrap(),
SandboxPolicy::WorkspaceWrite
);
assert_eq!(
"full_access".parse::<SandboxPolicy>().unwrap(),
SandboxPolicy::FullAccess
);
assert!("invalid".parse::<SandboxPolicy>().is_err());
}
#[test]
fn test_policy_properties() {
assert!(!SandboxPolicy::ReadOnly.allows_writes());
assert!(SandboxPolicy::WorkspaceWrite.allows_writes());
assert!(SandboxPolicy::FullAccess.allows_writes());
assert!(!SandboxPolicy::ReadOnly.has_full_network());
assert!(!SandboxPolicy::WorkspaceWrite.has_full_network());
assert!(SandboxPolicy::FullAccess.has_full_network());
assert!(SandboxPolicy::ReadOnly.is_sandboxed());
assert!(SandboxPolicy::WorkspaceWrite.is_sandboxed());
assert!(!SandboxPolicy::FullAccess.is_sandboxed());
}
#[test]
fn test_default_allowlist_has_common_registries() {
let allowlist = default_allowlist();
assert!(allowlist.contains(&"crates.io".to_string()));
assert!(allowlist.contains(&"registry.npmjs.org".to_string()));
assert!(allowlist.contains(&"github.com".to_string()));
}
}