Shell Command Sandbox
Overview
Replace the current command blocklist with a secure whitelist-based command execution system with argument validation.
Problem Statement
Current shell security is insufficient:
// Current: Simple blocklist - easily bypassed
fn is_dangerous_command(cmd: &str) -> bool {
let dangerous_patterns = ["rm -rf /", "mkfs", "dd if=/dev/zero", ...];
dangerous_patterns.iter().any(|p| cmd.contains(p))
}
Bypass Examples:
rm -rf /home (not in blocklist)
rmdir -rf / (different command)
/bin/rm -rf / (full path)
echo 'rm -rf /' | sh (indirect execution)
$(rm -rf /) (command substitution)
Proposed Solution
Whitelist-Based Command Execution
// core/src/sandbox/command.rs
pub struct CommandValidator {
allowed_commands: HashMap<String, CommandPolicy>,
default_policy: CommandPolicy,
}
pub struct CommandPolicy {
pub command: String,
pub allowed_args: Vec<AllowedArg>,
pub denied_arg_patterns: Vec<Regex>,
pub requires_permission: PermissionLevel,
pub timeout_seconds: u64,
}
pub struct AllowedArg {
pub name: String,
pub values: ArgValuePolicy,
}
pub enum ArgValuePolicy {
Any, // Any value allowed
Enum(Vec<String>), // Specific values only
Pattern(Regex), // Must match pattern
Path(PathPolicy), // Path validation
}
Command Parsing and Validation
impl CommandValidator {
pub fn validate(&self, command: &str, user: &UserContext) -> Result<ValidatedCommand, CommandError> {
// 1. Parse command safely
let parsed = self.parse_command(command)?;
// 2. Check if command is allowed
let policy = self.allowed_commands
.get(&parsed.command)
.ok_or(CommandError::CommandNotAllowed(parsed.command))?;
// 3. Check user permission
if !user.permission_level.can_execute(&policy.requires_permission) {
return Err(CommandError::InsufficientPermission);
}
// 4. Validate each argument
for arg in &parsed.args {
self.validate_argument(arg, policy)?;
}
// 5. Check for shell metacharacters (prevent injection)
if self.contains_shell_metacharacters(command) {
return Err(CommandError::ShellInjectionDetected);
}
Ok(ValidatedCommand {
command: parsed.command,
args: parsed.args,
timeout: policy.timeout_seconds,
})
}
fn contains_shell_metacharacters(&self, cmd: &str) -> bool {
// Block: |, $(), ``, ;, &&, ||, >, <,
// Allow simple commands only
let dangerous = ['|', '$', '`', ';', '&', '>', '<', '\n'];
cmd.chars().any(|c| dangerous.contains(&c))
}
}
Default Allowed Commands
{
"allowed_commands": {
"git": {
"allowed_args": ["clone", "pull", "push", "commit", "status", "log", "diff", "branch", "checkout"],
"denied_arg_patterns": ["--exec.*"],
"requires_permission": "member"
},
"npm": {
"allowed_args": ["install", "run", "test", "build"],
"requires_permission": "member"
},
"cargo": {
"allowed_args": ["build", "test", "run", "check", "clippy"],
"requires_permission": "member"
},
"python": {
"allowed_args": ["-m", "-c"],
"denied_arg_patterns": ["import os", "import subprocess", "exec", "eval"],
"requires_permission": "admin"
},
"ls": {
"allowed_args": ["*"],
"requires_permission": "guest"
},
"cat": {
"allowed_args": ["*"],
"requires_permission": "guest"
}
}
}
Safe Execution Wrapper
// core/src/tools/shell.rs
impl ShellTool {
pub async fn execute(&self, command: &str, user: &UserContext) -> Result<ShellResult> {
// 1. Validate command
let validated = self.command_validator.validate(command, user)?;
// 2. Execute in controlled environment
let output = Command::new(&validated.command)
.args(&validated.args)
.current_dir(&self.workspace_path) // Confined to workspace
.env_clear() // Clear environment
.envs(&self.safe_env_vars) // Only safe env vars
.output()
.timeout(Duration::from_secs(validated.timeout))
.await?;
// 3. Truncate output if needed
let stdout = self.truncate_output(&output.stdout);
let stderr = self.truncate_output(&output.stderr);
Ok(ShellResult { stdout, stderr, exit_code: output.status.code() })
}
}
Implementation Tasks
Security Test Cases
#[test]
fn test_command_injection_prevention() {
let validator = CommandValidator::default();
// Shell injection attempts
assert!(validator.validate("ls; rm -rf /", &user).is_err());
assert!(validator.validate("ls && rm -rf /", &user).is_err());
assert!(validator.validate("ls | cat /etc/passwd", &user).is_err());
assert!(validator.validate("$(rm -rf /)", &user).is_err());
assert!(validator.validate("`rm -rf /`", &user).is_err());
assert!(validator.validate("ls > /tmp/out", &user).is_err());
}
#[test]
fn test_whitelist_enforcement() {
let validator = CommandValidator::default();
// Only whitelisted commands
assert!(validator.validate("rm -rf /", &user).is_err());
assert!(validator.validate("sudo bash", &user).is_err());
assert!(validator.validate("chmod 777 /", &user).is_err());
// Allowed commands work
assert!(validator.validate("git status", &user).is_ok());
assert!(validator.validate("npm install", &user).is_ok());
}
Acceptance Criteria
Related
Shell Command Sandbox
Overview
Replace the current command blocklist with a secure whitelist-based command execution system with argument validation.
Problem Statement
Current shell security is insufficient:
Bypass Examples:
rm -rf /home(not in blocklist)rmdir -rf /(different command)/bin/rm -rf /(full path)echo 'rm -rf /' | sh(indirect execution)$(rm -rf /)(command substitution)Proposed Solution
Whitelist-Based Command Execution
Command Parsing and Validation
Default Allowed Commands
{ "allowed_commands": { "git": { "allowed_args": ["clone", "pull", "push", "commit", "status", "log", "diff", "branch", "checkout"], "denied_arg_patterns": ["--exec.*"], "requires_permission": "member" }, "npm": { "allowed_args": ["install", "run", "test", "build"], "requires_permission": "member" }, "cargo": { "allowed_args": ["build", "test", "run", "check", "clippy"], "requires_permission": "member" }, "python": { "allowed_args": ["-m", "-c"], "denied_arg_patterns": ["import os", "import subprocess", "exec", "eval"], "requires_permission": "admin" }, "ls": { "allowed_args": ["*"], "requires_permission": "guest" }, "cat": { "allowed_args": ["*"], "requires_permission": "guest" } } }Safe Execution Wrapper
Implementation Tasks
CommandValidatorstructSecurity Test Cases
Acceptance Criteria
Related