use std::process::{Command, ExitCode, Stdio};
use crate::cli::{Cli, ExecCommand, RunCommand};
use crate::config::{LoadOptions, load_config};
use crate::error::SboxError;
use crate::resolve::{ExecutionPlan, ResolutionTarget, resolve_execution_plan};
pub fn execute_run(cli: &Cli, command: &RunCommand) -> Result<ExitCode, SboxError> {
execute(cli, ResolutionTarget::Run, &command.command)
}
pub fn execute_exec(cli: &Cli, command: &ExecCommand) -> Result<ExitCode, SboxError> {
execute(
cli,
ResolutionTarget::Exec {
profile: &command.profile,
},
&command.command,
)
}
fn execute(
cli: &Cli,
target: ResolutionTarget<'_>,
command: &[String],
) -> Result<ExitCode, SboxError> {
let loaded = load_config(&LoadOptions {
workspace: cli.workspace.clone(),
config: cli.config.clone(),
})?;
let plan = resolve_execution_plan(cli, &loaded, target, command)?;
validate_execution_safety(&plan, strict_security_enabled(cli, &loaded.config))?;
run_pre_run_commands(&plan)?;
execute_plan(&plan)
}
fn execute_plan(plan: &ExecutionPlan) -> Result<ExitCode, SboxError> {
match plan.mode {
crate::config::model::ExecutionMode::Host => execute_host(plan),
crate::config::model::ExecutionMode::Sandbox => execute_sandbox(plan),
}
}
pub(crate) fn execute_host(plan: &ExecutionPlan) -> Result<ExitCode, SboxError> {
let (program, args) = plan
.command
.split_first()
.expect("command vectors are validated by clap");
let mut child = Command::new(program);
child.args(args);
child.current_dir(&plan.workspace.effective_host_dir);
child.stdin(Stdio::inherit());
child.stdout(Stdio::inherit());
child.stderr(Stdio::inherit());
for denied in &plan.environment.denied {
child.env_remove(denied);
}
for variable in &plan.environment.variables {
child.env(&variable.name, &variable.value);
}
let status = child.status().map_err(|source| SboxError::CommandSpawn {
program: program.clone(),
source,
})?;
Ok(status_to_exit_code(status))
}
pub(crate) fn status_to_exit_code(status: std::process::ExitStatus) -> ExitCode {
match status.code() {
Some(code) => ExitCode::from(u8::try_from(code).unwrap_or(1)),
None => ExitCode::from(1),
}
}
pub(crate) fn execute_sandbox(plan: &ExecutionPlan) -> Result<ExitCode, SboxError> {
match plan.backend {
crate::config::BackendKind::Podman => crate::backend::podman::execute(plan),
crate::config::BackendKind::Docker => crate::backend::docker::execute(plan),
}
}
pub(crate) fn validate_execution_safety(
plan: &ExecutionPlan,
strict_security: bool,
) -> Result<(), SboxError> {
if !matches!(plan.mode, crate::config::model::ExecutionMode::Sandbox) {
return Ok(());
}
if trusted_image_required(plan, strict_security)
&& matches!(
plan.image.trust,
crate::resolve::ImageTrust::MutableReference
)
{
return Err(SboxError::UnsafeExecutionPolicy {
command: plan.command_string.clone(),
reason: "strict security requires a pinned image digest or local build for sandbox execution".to_string(),
});
}
if strict_security && !plan.audit.sensitive_pass_through_vars.is_empty() {
return Err(SboxError::UnsafeExecutionPolicy {
command: plan.command_string.clone(),
reason: format!(
"strict security forbids sensitive host pass-through vars in sandbox mode: {}",
plan.audit.sensitive_pass_through_vars.join(", ")
),
});
}
if strict_security
&& plan.audit.install_style
&& plan.audit.lockfile.applicable
&& plan.audit.lockfile.required
&& !plan.audit.lockfile.present
{
return Err(SboxError::UnsafeExecutionPolicy {
command: plan.command_string.clone(),
reason: format!(
"strict security requires a lockfile for install-style commands: expected {}",
plan.audit.lockfile.expected_files.join(" or ")
),
});
}
if plan.policy.network == "off" || !plan.audit.install_style {
return Ok(());
}
if plan.audit.sensitive_pass_through_vars.is_empty() {
return Ok(());
}
Err(SboxError::UnsafeExecutionPolicy {
command: plan.command_string.clone(),
reason: format!(
"install-style sandbox command has network enabled and sensitive pass-through vars: {}",
plan.audit.sensitive_pass_through_vars.join(", ")
),
})
}
pub(crate) fn run_pre_run_commands(plan: &ExecutionPlan) -> Result<(), SboxError> {
for argv in &plan.audit.pre_run {
let (program, args) = argv
.split_first()
.expect("pre_run commands are non-empty after parse");
let status = Command::new(program)
.args(args)
.current_dir(&plan.workspace.effective_host_dir)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map_err(|source| SboxError::CommandSpawn {
program: program.clone(),
source,
})?;
if !status.success() {
return Err(SboxError::PreRunFailed {
pre_run: argv.join(" "),
command: plan.command_string.clone(),
status: status.code().unwrap_or(1) as u8,
});
}
}
Ok(())
}
pub(crate) fn trusted_image_required(plan: &ExecutionPlan, strict_security: bool) -> bool {
matches!(plan.mode, crate::config::model::ExecutionMode::Sandbox)
&& (strict_security || plan.audit.trusted_image_required)
}
pub(crate) fn strict_security_enabled(cli: &Cli, config: &crate::config::model::Config) -> bool {
cli.strict_security
|| config
.runtime
.as_ref()
.and_then(|runtime| runtime.strict_security)
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::{strict_security_enabled, trusted_image_required, validate_execution_safety};
use crate::config::model::ExecutionMode;
use crate::resolve::{
CwdMapping, EnvVarSource, ExecutionAudit, ExecutionPlan, LockfileAudit, ModeSource,
ProfileSource, ResolvedEnvVar, ResolvedEnvironment, ResolvedImage, ResolvedImageSource,
ResolvedPolicy, ResolvedUser, ResolvedWorkspace,
};
use std::path::PathBuf;
fn sample_plan() -> ExecutionPlan {
ExecutionPlan {
command: vec!["npm".into(), "install".into()],
command_string: "npm install".into(),
backend: crate::config::BackendKind::Podman,
image: ResolvedImage {
description: "ref:node:22-bookworm-slim".into(),
source: ResolvedImageSource::Reference("node:22-bookworm-slim".into()),
trust: crate::resolve::ImageTrust::MutableReference,
verify_signature: false,
},
profile_name: "install".into(),
profile_source: ProfileSource::DefaultProfile,
mode: ExecutionMode::Sandbox,
mode_source: ModeSource::Profile,
workspace: ResolvedWorkspace {
root: PathBuf::from("/tmp/project"),
invocation_dir: PathBuf::from("/tmp/project"),
effective_host_dir: PathBuf::from("/tmp/project"),
mount: "/workspace".into(),
sandbox_cwd: "/workspace".into(),
cwd_mapping: CwdMapping::InvocationMapped,
},
policy: ResolvedPolicy {
network: "on".into(),
writable: true,
ports: Vec::new(),
no_new_privileges: true,
read_only_rootfs: false,
reuse_container: false,
reusable_session_name: None,
cap_drop: Vec::new(),
cap_add: Vec::new(),
pull_policy: None,
network_allow: Vec::new(),
network_allow_patterns: Vec::new(),
},
environment: ResolvedEnvironment {
variables: vec![ResolvedEnvVar {
name: "NPM_TOKEN".into(),
value: "secret".into(),
source: EnvVarSource::PassThrough,
}],
denied: Vec::new(),
},
mounts: Vec::new(),
caches: Vec::new(),
secrets: Vec::new(),
user: ResolvedUser::KeepId,
audit: ExecutionAudit {
install_style: true,
trusted_image_required: false,
sensitive_pass_through_vars: vec!["NPM_TOKEN".into()],
lockfile: LockfileAudit {
applicable: true,
required: true,
present: true,
expected_files: vec!["package-lock.json".into()],
},
pre_run: Vec::new(),
},
}
}
#[test]
fn rejects_networked_install_with_sensitive_pass_through_envs() {
let error =
validate_execution_safety(&sample_plan(), false).expect_err("policy should reject");
assert!(error.to_string().contains("unsafe sandbox execution"));
}
#[test]
fn allows_networked_install_without_sensitive_pass_through_envs() {
let mut plan = sample_plan();
plan.audit.sensitive_pass_through_vars.clear();
validate_execution_safety(&plan, false).expect("policy should allow");
}
#[test]
fn strict_security_rejects_sensitive_pass_through_even_without_install_pattern() {
let mut plan = sample_plan();
plan.command = vec!["node".into(), "--version".into()];
plan.command_string = "node --version".into();
plan.audit.install_style = false;
let error = validate_execution_safety(&plan, true).expect_err("strict mode should reject");
assert!(error.to_string().contains("requires a pinned image digest"));
}
#[test]
fn strict_security_requires_trusted_image() {
let error =
validate_execution_safety(&sample_plan(), true).expect_err("strict mode should reject");
assert!(error.to_string().contains("pinned image digest"));
}
#[test]
fn strict_security_allows_pinned_image() {
let mut plan = sample_plan();
plan.image.source =
ResolvedImageSource::Reference("node:22-bookworm-slim@sha256:deadbeef".into());
plan.image.trust = crate::resolve::ImageTrust::PinnedDigest;
plan.audit.sensitive_pass_through_vars.clear();
validate_execution_safety(&plan, true).expect("strict mode should allow pinned images");
}
#[test]
fn strict_security_marks_trusted_image_requirement() {
assert!(trusted_image_required(&sample_plan(), true));
assert!(!trusted_image_required(&sample_plan(), false));
}
#[test]
fn profile_policy_requires_trusted_image_without_strict_mode() {
let mut plan = sample_plan();
plan.audit.trusted_image_required = true;
let error =
validate_execution_safety(&plan, false).expect_err("profile policy should reject");
assert!(error.to_string().contains("pinned image digest"));
}
#[test]
fn strict_security_requires_lockfile_for_install_flows() {
let mut plan = sample_plan();
plan.image.source =
ResolvedImageSource::Reference("node:22-bookworm-slim@sha256:deadbeef".into());
plan.image.trust = crate::resolve::ImageTrust::PinnedDigest;
plan.audit.sensitive_pass_through_vars.clear();
plan.audit.lockfile.present = false;
let error =
validate_execution_safety(&plan, true).expect_err("missing lockfile should reject");
assert!(
error
.to_string()
.contains("requires a lockfile for install-style")
);
}
#[test]
fn cli_flag_enables_strict_security() {
let cli = crate::cli::Cli {
config: None,
workspace: None,
backend: None,
image: None,
profile: None,
mode: None,
verbose: 0,
quiet: false,
strict_security: true,
command: crate::cli::Commands::Doctor(crate::cli::DoctorCommand::default()),
};
let config = crate::config::model::Config {
version: 1,
runtime: None,
workspace: None,
identity: None,
image: None,
environment: None,
mounts: Vec::new(),
caches: Vec::new(),
secrets: Vec::new(),
profiles: Default::default(),
dispatch: Default::default(),
package_manager: None,
};
assert!(strict_security_enabled(&cli, &config));
}
}