pub mod docker;
pub mod k8s;
pub mod local;
pub mod manager;
pub mod policy;
use std::collections::HashMap;
use std::sync::LazyLock;
pub use docker::{DockerConfig, DockerSandbox};
pub use k8s::{K8sConfig, K8sSandbox};
pub use local::{LocalConfig, LocalSandbox};
pub use manager::SandboxManager;
pub use policy::{SandboxPolicy, SecurityLevel};
pub use echo_core::sandbox::{
CommandKind, ExecutionResult, IsolationLevel, ResourceLimits, SandboxCommand, SandboxExecutor,
};
pub static SENSITIVE_MOUNT_PATHS: LazyLock<Vec<&'static str>> = LazyLock::new(|| {
vec![
"/etc", "/proc", "/sys", "/dev", "/boot", "/run", "/var/run", "/var/log",
]
});
pub fn default_language_image(lang: &str) -> Option<&'static str> {
match lang {
"python" | "python3" => Some("python:3.12-slim"),
"node" | "javascript" | "js" | "typescript" | "ts" => Some("node:20-slim"),
"ruby" => Some("ruby:3.3-slim"),
"go" | "golang" => Some("golang:1.22-alpine"),
"rust" => Some("rust:1.77-slim"),
"perl" => Some("perl:5.38-slim"),
"php" => Some("php:8.3-cli"),
"lua" => Some("alpine:3.19"),
"r" => Some("rocker/r-base:latest"),
"julia" => Some("julia:1.10-slim"),
"swift" => Some("swift:5.9-slim"),
_ => None,
}
}
pub fn select_image_for_command(
command: &SandboxCommand,
language_images: &HashMap<String, String>,
default_image: &str,
) -> String {
match &command.kind {
CommandKind::Code { language, .. } => {
if let Some(img) = language_images.get(language) {
return img.clone();
}
if let Some(img) = default_language_image(language) {
return img.to_string();
}
default_image.to_string()
}
CommandKind::Shell(cmd) => {
let raw = cmd.split_whitespace().next().unwrap_or("");
if raw.contains('/') || raw.contains('\\') {
return default_image.to_string();
}
let base = std::path::Path::new(raw)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(raw);
if let Some(img) = language_images.get(base) {
return img.clone();
}
if let Some(img) = default_language_image(base) {
return img.to_string();
}
default_image.to_string()
}
CommandKind::Program { program, .. } => {
let name = std::path::Path::new(program)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(program);
if let Some(img) = language_images.get(name) {
return img.clone();
}
if let Some(img) = default_language_image(name) {
return img.to_string();
}
default_image.to_string()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_select_image_shell_path_uses_default_image() {
let mut language_images = HashMap::new();
language_images.insert("python3".to_string(), "python:3.12-slim".to_string());
let cmd = SandboxCommand::shell("/usr/bin/python3 script.py");
let image = select_image_for_command(&cmd, &language_images, "ubuntu:22.04");
assert_eq!(image, "ubuntu:22.04");
}
#[test]
fn test_select_image_shell_binary_name_mapping() {
let mut language_images = HashMap::new();
language_images.insert("python3".to_string(), "python:3.12-slim".to_string());
let cmd = SandboxCommand::shell("python3 script.py");
let image = select_image_for_command(&cmd, &language_images, "ubuntu:22.04");
assert_eq!(image, "python:3.12-slim");
}
}