#![allow(dead_code)]
pub mod backend;
pub mod opensandbox;
pub mod policy;
pub mod process_hardening;
#[cfg(target_os = "macos")]
pub mod seatbelt;
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
pub mod landlock;
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
pub mod seccomp;
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
pub mod bwrap;
#[cfg(target_os = "windows")]
pub mod windows;
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
pub use policy::SandboxPolicy;
#[derive(Debug, Clone)]
pub struct CommandSpec {
pub program: String,
pub args: Vec<String>,
pub cwd: PathBuf,
pub env: HashMap<String, String>,
pub timeout: Duration,
pub sandbox_policy: SandboxPolicy,
pub justification: Option<String>,
}
impl CommandSpec {
pub fn shell(command: &str, cwd: PathBuf, timeout: Duration) -> Self {
let dispatcher = crate::shell_dispatcher::global_dispatcher();
#[cfg(windows)]
let (program, args) = {
let kind = dispatcher.kind();
let cmd = if matches!(
kind,
crate::shell_dispatcher::ShellKind::Pwsh
| crate::shell_dispatcher::ShellKind::WindowsPowerShell
) {
format!("[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; {command}")
} else if matches!(kind, crate::shell_dispatcher::ShellKind::Cmd) {
format!("chcp 65001 >NUL & {command}")
} else {
command.to_string()
};
dispatcher.build_command_parts(&cmd)
};
#[cfg(not(windows))]
let (program, args) = dispatcher.build_command_parts(command);
Self {
program,
args,
cwd,
env: HashMap::new(),
timeout,
sandbox_policy: SandboxPolicy::default(),
justification: None,
}
}
pub fn program(program: &str, args: Vec<String>, cwd: PathBuf, timeout: Duration) -> Self {
Self {
program: program.to_string(),
args,
cwd,
env: HashMap::new(),
timeout,
sandbox_policy: SandboxPolicy::default(),
justification: None,
}
}
pub fn with_policy(mut self, policy: SandboxPolicy) -> Self {
self.sandbox_policy = policy;
self
}
pub fn with_env(mut self, env: HashMap<String, String>) -> Self {
self.env = env;
self
}
pub fn with_env_var(mut self, key: &str, value: &str) -> Self {
self.env.insert(key.to_string(), value.to_string());
self
}
pub fn with_justification(mut self, justification: &str) -> Self {
self.justification = Some(justification.to_string());
self
}
pub fn display_command(&self) -> String {
if self.args.len() == 2
&& self.args[0] == "-c"
&& matches!(
self.program.as_str(),
"sh" | "bash" | "/bin/sh" | "/bin/bash" | "/usr/bin/sh" | "/usr/bin/bash"
)
{
self.args[1].clone()
} else if self.args.len() == 2
&& self.args[0] == "-c"
&& !self.program.eq_ignore_ascii_case("cmd")
&& !self.program.eq_ignore_ascii_case("pwsh")
&& !self.program.eq_ignore_ascii_case("pwsh.exe")
&& !self.program.eq_ignore_ascii_case("powershell")
&& !self.program.eq_ignore_ascii_case("powershell.exe")
{
self.args[1].clone()
} else if self.program.eq_ignore_ascii_case("cmd")
&& self.args.len() == 2
&& self.args[0].eq_ignore_ascii_case("/C")
{
let raw = &self.args[1];
raw.strip_prefix("chcp 65001 >NUL & ")
.unwrap_or(raw)
.to_string()
} else if {
let program = self.program.to_ascii_lowercase();
program == "pwsh"
|| program == "pwsh.exe"
|| program == "powershell"
|| program == "powershell.exe"
} && self.args.len() >= 3
&& self.args[0].eq_ignore_ascii_case("-NoProfile")
&& self.args[1].eq_ignore_ascii_case("-Command")
{
let raw = &self.args[2];
raw.strip_prefix("[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ")
.unwrap_or(raw)
.to_string()
} else {
let mut parts = vec![self.program.clone()];
parts.extend(self.args.clone());
parts.join(" ")
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SandboxType {
#[default]
None,
#[cfg(target_os = "macos")]
MacosSeatbelt,
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
LinuxLandlock,
#[cfg(target_os = "windows")]
Windows,
}
impl std::fmt::Display for SandboxType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SandboxType::None => write!(f, "none"),
#[cfg(target_os = "macos")]
SandboxType::MacosSeatbelt => write!(f, "macos-seatbelt"),
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
SandboxType::LinuxLandlock => write!(f, "linux-landlock"),
#[cfg(target_os = "windows")]
SandboxType::Windows => write!(f, "windows-sandbox"),
}
}
}
#[derive(Debug)]
pub struct ExecEnv {
pub command: Vec<String>,
pub cwd: PathBuf,
pub env: HashMap<String, String>,
pub timeout: Duration,
pub sandbox_type: SandboxType,
pub policy: SandboxPolicy,
}
impl ExecEnv {
pub fn program(&self) -> &str {
self.command
.first()
.map_or("sh", std::string::String::as_str)
}
pub fn args(&self) -> &[String] {
if self.command.len() > 1 {
&self.command[1..]
} else {
&[]
}
}
pub fn is_sandboxed(&self) -> bool {
!matches!(self.sandbox_type, SandboxType::None)
}
}
pub fn get_platform_sandbox() -> Option<SandboxType> {
#[cfg(target_os = "macos")]
{
if seatbelt::is_available() {
return Some(SandboxType::MacosSeatbelt);
}
}
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
if landlock::is_available() {
return Some(SandboxType::LinuxLandlock);
}
}
#[cfg(target_os = "windows")]
{
if windows::is_available() {
return Some(SandboxType::Windows);
}
}
None
}
pub fn is_sandbox_available() -> bool {
get_platform_sandbox().is_some()
}
#[derive(Debug, Default)]
pub struct SandboxManager {
sandbox_available: Option<bool>,
#[allow(dead_code)]
forced_sandbox: Option<SandboxType>,
prefer_bwrap: bool,
}
impl SandboxManager {
pub fn new() -> Self {
Self::default()
}
pub fn with_bwrap_preference(prefer_bwrap: bool) -> Self {
Self {
prefer_bwrap,
..Self::default()
}
}
pub fn set_prefer_bwrap(&mut self, prefer: bool) {
self.prefer_bwrap = prefer;
}
pub fn is_available(&mut self) -> bool {
if let Some(available) = self.sandbox_available {
return available;
}
let available = is_sandbox_available();
self.sandbox_available = Some(available);
available
}
pub fn select_sandbox(&self, policy: &SandboxPolicy) -> SandboxType {
if !policy.should_sandbox() {
return SandboxType::None;
}
if let Some(forced) = self.forced_sandbox {
return forced;
}
get_platform_sandbox().unwrap_or(SandboxType::None)
}
pub fn prepare(&self, spec: &CommandSpec) -> ExecEnv {
let sandbox_type = self.select_sandbox(&spec.sandbox_policy);
match sandbox_type {
SandboxType::None => Self::prepare_unsandboxed(spec),
#[cfg(target_os = "macos")]
SandboxType::MacosSeatbelt => Self::prepare_seatbelt(spec),
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
SandboxType::LinuxLandlock => self.prepare_landlock(spec),
#[cfg(target_os = "windows")]
SandboxType::Windows => Self::prepare_windows(spec),
}
}
fn prepare_unsandboxed(spec: &CommandSpec) -> ExecEnv {
let mut command = vec![spec.program.clone()];
command.extend(spec.args.clone());
ExecEnv {
command,
cwd: spec.cwd.clone(),
env: spec.env.clone(),
timeout: spec.timeout,
sandbox_type: SandboxType::None,
policy: spec.sandbox_policy.clone(),
}
}
#[cfg(target_os = "macos")]
fn prepare_seatbelt(spec: &CommandSpec) -> ExecEnv {
let mut original_command = vec![spec.program.clone()];
original_command.extend(spec.args.clone());
let seatbelt_args =
seatbelt::create_seatbelt_args(original_command, &spec.sandbox_policy, &spec.cwd);
let mut command = vec![seatbelt::SANDBOX_EXEC_PATH.to_string()];
command.extend(seatbelt_args);
let mut env = spec.env.clone();
env.insert("DEEPSEEK_SANDBOX".to_string(), "seatbelt".to_string());
ExecEnv {
command,
cwd: spec.cwd.clone(),
env,
timeout: spec.timeout,
sandbox_type: SandboxType::MacosSeatbelt,
policy: spec.sandbox_policy.clone(),
}
}
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
fn prepare_landlock(&self, spec: &CommandSpec) -> ExecEnv {
if self.prefer_bwrap && bwrap::is_available() {
let command = bwrap::build_bwrap_command(&spec.cwd, &spec.program, &spec.args);
let mut env = spec.env.clone();
env.insert("DEEPSEEK_SANDBOX".to_string(), "bwrap".to_string());
return ExecEnv {
command,
cwd: spec.cwd.clone(),
env,
timeout: spec.timeout,
sandbox_type: SandboxType::LinuxLandlock,
policy: spec.sandbox_policy.clone(),
};
}
let mut command = vec![spec.program.clone()];
command.extend(spec.args.clone());
let mut env = spec.env.clone();
env.insert("DEEPSEEK_SANDBOX".to_string(), "landlock".to_string());
ExecEnv {
command,
cwd: spec.cwd.clone(),
env,
timeout: spec.timeout,
sandbox_type: SandboxType::LinuxLandlock,
policy: spec.sandbox_policy.clone(),
}
}
#[cfg(target_os = "windows")]
fn prepare_windows(spec: &CommandSpec) -> ExecEnv {
let mut command = vec![spec.program.clone()];
command.extend(spec.args.clone());
let mut env = spec.env.clone();
let kind = windows::select_best_kind(&spec.sandbox_policy, &spec.cwd);
env.insert("DEEPSEEK_SANDBOX".to_string(), format!("windows:{kind}"));
if !spec.sandbox_policy.has_network_access() {
env.insert(
"DEEPSEEK_SANDBOX_BLOCK_NETWORK".to_string(),
"1".to_string(),
);
}
ExecEnv {
command,
cwd: spec.cwd.clone(),
env,
timeout: spec.timeout,
sandbox_type: SandboxType::Windows,
policy: spec.sandbox_policy.clone(),
}
}
pub fn was_denied(sandbox_type: SandboxType, exit_code: i32, stderr: &str) -> bool {
#[cfg(not(any(
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos"))
)))]
let _ = (exit_code, stderr);
match sandbox_type {
SandboxType::None => false,
#[cfg(target_os = "macos")]
SandboxType::MacosSeatbelt => seatbelt::detect_denial(exit_code, stderr),
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
SandboxType::LinuxLandlock => landlock::detect_denial(exit_code, stderr),
#[cfg(target_os = "windows")]
SandboxType::Windows => windows::detect_denial(exit_code, stderr),
}
}
pub fn denial_message(sandbox_type: SandboxType, stderr: &str) -> String {
#[cfg(not(any(
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos"))
)))]
let _ = stderr;
match sandbox_type {
SandboxType::None => "Command failed (no sandbox)".to_string(),
#[cfg(target_os = "macos")]
SandboxType::MacosSeatbelt => {
if stderr.contains("file-write") {
"Sandbox blocked write access. The command tried to write to a protected location.".to_string()
} else if stderr.contains("network") {
"Sandbox blocked network access. Enable network_access in sandbox policy if needed.".to_string()
} else {
format!(
"Sandbox blocked operation: {}",
stderr.lines().next().unwrap_or("unknown")
)
}
}
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
SandboxType::LinuxLandlock => {
if stderr.contains("Bad system call")
|| stderr.contains("bad system call")
|| stderr.contains("SIGSYS")
|| stderr.contains("seccomp")
{
"Seccomp blocked a disallowed system call (e.g., ptrace, mount, kexec)."
.to_string()
} else if stderr.contains("Permission denied") {
"Landlock blocked access. The command tried to access a restricted path."
.to_string()
} else {
format!(
"Landlock blocked operation: {}",
stderr.lines().next().unwrap_or("unknown")
)
}
}
#[cfg(target_os = "windows")]
SandboxType::Windows => {
if stderr.contains("Access is denied") {
"Windows sandbox blocked access. The command lacked required privileges."
.to_string()
} else if stderr.contains("network") {
"Windows sandbox blocked network access. Enable network_access in policy if needed."
.to_string()
} else {
format!(
"Windows sandbox blocked operation: {}",
stderr.lines().next().unwrap_or("unknown")
)
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_spec_shell() {
let spec = CommandSpec::shell("echo hello", PathBuf::from("/tmp"), Duration::from_secs(30));
assert!(!spec.program.is_empty(), "program must not be empty");
assert!(!spec.args.is_empty(), "args must not be empty");
assert_eq!(spec.display_command(), "echo hello");
}
#[test]
fn test_command_spec_shell_custom_posix_path_display() {
let spec = CommandSpec {
program: "/bin/zsh".to_string(),
args: vec!["-c".to_string(), "echo hello".to_string()],
cwd: PathBuf::from("/tmp"),
env: HashMap::new(),
timeout: Duration::from_secs(30),
sandbox_policy: SandboxPolicy::default(),
justification: None,
};
assert_eq!(spec.display_command(), "echo hello");
}
#[test]
fn test_command_spec_shell_quoted_arg_not_split() {
let cmd = r#"git commit -m "feat: complete sub-pages""#;
let spec = CommandSpec::shell(cmd, PathBuf::from("/tmp"), Duration::from_secs(30));
let dispatcher = crate::shell_dispatcher::global_dispatcher();
assert_eq!(spec.program, dispatcher.kind().binary());
if dispatcher.kind().is_powershell() {
assert_eq!(
spec.args,
vec![
dispatcher.kind().command_flag().to_string(),
"-Command".to_string(),
format!("[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; {cmd}")
]
);
} else {
let expected = if matches!(dispatcher.kind(), crate::shell_dispatcher::ShellKind::Cmd) {
vec!["/C".to_string(), format!("chcp 65001 >NUL & {cmd}")]
} else {
vec![
dispatcher.kind().command_flag().to_string(),
cmd.to_string(),
]
};
assert_eq!(spec.args, expected);
assert_eq!(spec.args.len(), 2);
assert!(spec.args[1].contains(r#""feat: complete sub-pages""#));
}
assert_eq!(spec.display_command(), cmd);
}
#[test]
fn test_command_spec_program() {
let spec = CommandSpec::program(
"cargo",
vec!["build".to_string(), "--release".to_string()],
PathBuf::from("/project"),
Duration::from_secs(300),
);
assert_eq!(spec.program, "cargo");
assert_eq!(spec.display_command(), "cargo build --release");
}
#[test]
fn test_command_spec_builder() {
let spec = CommandSpec::shell("test", PathBuf::from("."), Duration::from_secs(10))
.with_policy(SandboxPolicy::ReadOnly)
.with_env_var("FOO", "bar")
.with_justification("Testing");
assert!(matches!(spec.sandbox_policy, SandboxPolicy::ReadOnly));
assert_eq!(spec.env.get("FOO"), Some(&"bar".to_string()));
assert_eq!(spec.justification, Some("Testing".to_string()));
}
#[test]
fn test_sandbox_manager_new() {
let manager = SandboxManager::new();
assert!(manager.sandbox_available.is_none());
}
#[test]
fn test_sandbox_manager_select_sandbox() {
let manager = SandboxManager::new();
let no_sandbox = manager.select_sandbox(&SandboxPolicy::DangerFullAccess);
assert_eq!(no_sandbox, SandboxType::None);
let external = manager.select_sandbox(&SandboxPolicy::ExternalSandbox {
network_access: true,
});
assert_eq!(external, SandboxType::None);
}
#[test]
fn test_prepare_unsandboxed() {
let manager = SandboxManager::new();
let spec = CommandSpec::shell("echo test", PathBuf::from("/tmp"), Duration::from_secs(30))
.with_policy(SandboxPolicy::DangerFullAccess);
let env = manager.prepare(&spec);
let dispatcher = crate::shell_dispatcher::global_dispatcher();
assert_eq!(env.sandbox_type, SandboxType::None);
if dispatcher.kind().is_powershell() {
assert_eq!(
env.command,
vec![
dispatcher.kind().binary().to_string(),
dispatcher.kind().command_flag().to_string(),
"-Command".to_string(),
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; echo test"
.to_string(),
]
);
} else if matches!(dispatcher.kind(), crate::shell_dispatcher::ShellKind::Cmd) {
assert_eq!(
env.command,
vec![
dispatcher.kind().binary().to_string(),
"/C".to_string(),
"chcp 65001 >NUL & echo test".to_string(),
]
);
} else {
assert_eq!(
env.command,
vec![
dispatcher.kind().binary().to_string(),
dispatcher.kind().command_flag().to_string(),
"echo test".to_string(),
]
);
}
assert!(!env.is_sandboxed());
}
#[test]
fn test_exec_env_helpers() {
let env = ExecEnv {
command: vec![
"sandbox-exec".to_string(),
"-p".to_string(),
"policy".to_string(),
"--".to_string(),
"echo".to_string(),
"hello".to_string(),
],
cwd: PathBuf::from("/tmp"),
env: HashMap::new(),
timeout: Duration::from_secs(30),
sandbox_type: SandboxType::None,
policy: SandboxPolicy::default(),
};
assert_eq!(env.program(), "sandbox-exec");
assert_eq!(env.args().len(), 5);
}
#[test]
fn test_sandbox_type_display() {
assert_eq!(format!("{}", SandboxType::None), "none");
#[cfg(target_os = "macos")]
assert_eq!(format!("{}", SandboxType::MacosSeatbelt), "macos-seatbelt");
}
#[test]
fn test_parity_platform_sandbox_detection() {
let sandbox_type = get_platform_sandbox();
let available = is_sandbox_available();
if available {
assert!(sandbox_type.is_some());
}
}
#[test]
#[cfg(target_os = "macos")]
fn test_parity_macos_seatbelt_available() {
let st = get_platform_sandbox();
assert!(matches!(st, Some(SandboxType::MacosSeatbelt)));
}
#[test]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
fn test_parity_linux_landlock_available() {
let st = get_platform_sandbox();
assert!(matches!(st, Some(SandboxType::LinuxLandlock)));
}
#[test]
fn test_parity_denial_zero_exit_never_denied() {
assert!(!SandboxManager::was_denied(
SandboxType::None,
0,
"anything"
));
#[cfg(target_os = "macos")]
assert!(!SandboxManager::was_denied(
SandboxType::MacosSeatbelt,
0,
""
));
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
assert!(!SandboxManager::was_denied(
SandboxType::LinuxLandlock,
0,
""
));
#[cfg(target_os = "windows")]
assert!(!SandboxManager::was_denied(SandboxType::Windows, 0, ""));
}
#[test]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
fn test_parity_seccomp_sigsys_detected() {
assert!(SandboxManager::was_denied(
SandboxType::LinuxLandlock,
31,
""
));
assert!(SandboxManager::was_denied(
SandboxType::LinuxLandlock,
1,
"Bad system call"
));
}
#[test]
#[cfg(target_os = "macos")]
fn test_parity_seatbelt_file_write_detected() {
assert!(SandboxManager::was_denied(
SandboxType::MacosSeatbelt,
1,
"Sandbox: ls denied file-write*"
));
assert!(SandboxManager::was_denied(
SandboxType::MacosSeatbelt,
1,
"Operation not permitted"
));
}
#[test]
fn test_parity_manager_default_no_bwrap() {
let manager = SandboxManager::default();
let spec = CommandSpec::shell("true", PathBuf::from("/tmp"), Duration::from_secs(5))
.with_policy(SandboxPolicy::default());
let env = manager.prepare(&spec);
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
let marker = env.env.get("DEEPSEEK_SANDBOX");
assert!(marker.is_none_or(|v| v != "bwrap"));
}
let _ = env;
}
#[test]
fn test_parity_manager_with_bwrap() {
let manager = SandboxManager::with_bwrap_preference(true);
let spec = CommandSpec::shell("true", PathBuf::from("/tmp"), Duration::from_secs(5))
.with_policy(SandboxPolicy::default());
let env = manager.prepare(&spec);
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
if crate::sandbox::bwrap::is_available() {
let marker = env.env.get("DEEPSEEK_SANDBOX");
assert_eq!(marker.map(String::as_str), Some("bwrap"));
}
}
let _ = env;
}
#[test]
fn test_parity_exec_env_for_all_policies() {
let manager = SandboxManager::new();
let policies = [
SandboxPolicy::DangerFullAccess,
SandboxPolicy::ReadOnly,
SandboxPolicy::workspace_with_network(),
SandboxPolicy::default(),
];
for policy in &policies {
let spec = CommandSpec::shell("true", PathBuf::from("/tmp"), Duration::from_secs(5))
.with_policy(policy.clone());
let env = manager.prepare(&spec);
assert_eq!(env.policy, *policy);
}
}
}