Compare commits

...

1 Commits

Author SHA1 Message Date
celia-oai
254cf94e38 changes 2026-03-17 12:10:15 -07:00
4 changed files with 607 additions and 40 deletions

View File

@@ -9,8 +9,10 @@ use crate::features::Feature;
use crate::guardian::GuardianApprovalRequest;
use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::path_utils;
use crate::sandboxing::ExecRequest;
use crate::sandboxing::SandboxPermissions;
use crate::sandboxing::merge_permission_profiles;
use crate::shell::ShellType;
use crate::skills::SkillMetadata;
use crate::tools::runtimes::ExecveSessionApproval;
@@ -50,6 +52,7 @@ use codex_shell_escalation::ShellCommandExecutor;
use codex_shell_escalation::Stopwatch;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@@ -62,6 +65,14 @@ pub(crate) struct PreparedUnifiedExecZshFork {
pub(crate) escalation_session: EscalationSession,
}
enum DirectSkillScriptOverride {
Denied(ExecToolCallOutput),
RunDirect {
command: Vec<String>,
additional_permissions: PermissionProfile,
},
}
const PROMPT_CONFLICT_REASON: &str =
"approval required by policy, but AskForApproval is set to Never";
const REJECT_SANDBOX_APPROVAL_REASON: &str =
@@ -106,7 +117,103 @@ pub(super) async fn try_run_zsh_fork(
return Ok(None);
}
let spec = build_command_spec(
let workdir = AbsolutePathBuf::try_from(req.cwd.clone())
.map_err(|err| ToolError::Rejected(err.to_string()))?;
let base_spec = build_command_spec(
command,
&req.cwd,
&req.env,
req.timeout_ms.into(),
req.sandbox_permissions,
req.additional_permissions.clone(),
req.justification.clone(),
)?;
let base_sandbox_exec_request = attempt
.env_for(base_spec, req.network.as_ref())
.map_err(|err| ToolError::Codex(err.into()))?;
let ParsedShellCommand { script, login, .. } = extract_shell_script(&command)?;
let effective_timeout = Duration::from_millis(
req.timeout_ms
.unwrap_or(crate::exec::DEFAULT_EXEC_COMMAND_TIMEOUT_MS),
);
let exec_policy = Arc::new(RwLock::new(
ctx.session.services.exec_policy.current().as_ref().clone(),
));
let main_execve_wrapper_exe = ctx
.session
.services
.main_execve_wrapper_exe
.clone()
.ok_or_else(|| {
ToolError::Rejected(
"zsh fork feature enabled, but execve wrapper is not configured".to_string(),
)
})?;
let exec_params = ExecParams {
command: script.clone(),
workdir: req.cwd.to_string_lossy().to_string(),
timeout_ms: Some(effective_timeout.as_millis() as u64),
login: Some(login),
};
// Note that Stopwatch starts immediately upon creation, so currently we try
// to minimize the time between creating the Stopwatch and starting the
// escalation server.
let stopwatch = Stopwatch::new(effective_timeout);
let cancel_token = stopwatch.cancellation_token();
let base_approval_sandbox_permissions = approval_sandbox_permissions(
req.sandbox_permissions,
req.additional_permissions_preapproved,
);
let base_escalation_policy = CoreShellActionProvider {
policy: Arc::clone(&exec_policy),
session: Arc::clone(&ctx.session),
turn: Arc::clone(&ctx.turn),
call_id: ctx.call_id.clone(),
tool_name: "shell",
approval_policy: ctx.turn.approval_policy.value(),
sandbox_policy: base_sandbox_exec_request.sandbox_policy.clone(),
file_system_sandbox_policy: base_sandbox_exec_request.file_system_sandbox_policy.clone(),
network_sandbox_policy: base_sandbox_exec_request.network_sandbox_policy,
sandbox_permissions: req.sandbox_permissions,
approval_sandbox_permissions: base_approval_sandbox_permissions,
prompt_permissions: req.additional_permissions.clone(),
stopwatch: stopwatch.clone(),
};
match resolve_direct_skill_script_override(
&base_escalation_policy,
&script,
&workdir,
req.additional_permissions.as_ref(),
)
.await
.map_err(|err| ToolError::Rejected(err.to_string()))?
{
Some(DirectSkillScriptOverride::Denied(output)) => return Ok(Some(output)),
Some(DirectSkillScriptOverride::RunDirect {
command,
additional_permissions,
}) => {
let direct_spec = build_command_spec(
&command,
&req.cwd,
&req.env,
req.timeout_ms.into(),
SandboxPermissions::WithAdditionalPermissions,
Some(additional_permissions),
req.justification.clone(),
)?;
let direct_exec_request = attempt
.env_for(direct_spec, req.network.as_ref())
.map_err(|err| ToolError::Codex(err.into()))?;
let result = crate::sandboxing::execute_env(direct_exec_request, None)
.await
.map_err(|err| ToolError::Rejected(err.to_string()))?;
return Ok(Some(result));
}
None => {}
}
let final_spec = build_command_spec(
command,
&req.cwd,
&req.env,
@@ -116,7 +223,7 @@ pub(super) async fn try_run_zsh_fork(
req.justification.clone(),
)?;
let sandbox_exec_request = attempt
.env_for(spec, req.network.as_ref())
.env_for(final_spec, req.network.as_ref())
.map_err(|err| ToolError::Codex(err.into()))?;
let crate::sandboxing::ExecRequest {
command,
@@ -134,14 +241,6 @@ pub(super) async fn try_run_zsh_fork(
justification,
arg0,
} = sandbox_exec_request;
let ParsedShellCommand { script, login, .. } = extract_shell_script(&command)?;
let effective_timeout = Duration::from_millis(
req.timeout_ms
.unwrap_or(crate::exec::DEFAULT_EXEC_COMMAND_TIMEOUT_MS),
);
let exec_policy = Arc::new(RwLock::new(
ctx.session.services.exec_policy.current().as_ref().clone(),
));
let command_executor = CoreShellCommandExecutor {
command,
cwd: sandbox_cwd,
@@ -165,32 +264,6 @@ pub(super) async fn try_run_zsh_fork(
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
};
let main_execve_wrapper_exe = ctx
.session
.services
.main_execve_wrapper_exe
.clone()
.ok_or_else(|| {
ToolError::Rejected(
"zsh fork feature enabled, but execve wrapper is not configured".to_string(),
)
})?;
let exec_params = ExecParams {
command: script,
workdir: req.cwd.to_string_lossy().to_string(),
timeout_ms: Some(effective_timeout.as_millis() as u64),
login: Some(login),
};
// Note that Stopwatch starts immediately upon creation, so currently we try
// to minimize the time between creating the Stopwatch and starting the
// escalation server.
let stopwatch = Stopwatch::new(effective_timeout);
let cancel_token = stopwatch.cancellation_token();
let approval_sandbox_permissions = approval_sandbox_permissions(
req.sandbox_permissions,
req.additional_permissions_preapproved,
);
let escalation_policy = CoreShellActionProvider {
policy: Arc::clone(&exec_policy),
session: Arc::clone(&ctx.session),
@@ -202,11 +275,13 @@ pub(super) async fn try_run_zsh_fork(
file_system_sandbox_policy: command_executor.file_system_sandbox_policy.clone(),
network_sandbox_policy: command_executor.network_sandbox_policy,
sandbox_permissions: req.sandbox_permissions,
approval_sandbox_permissions,
approval_sandbox_permissions: approval_sandbox_permissions(
req.sandbox_permissions,
req.additional_permissions_preapproved,
),
prompt_permissions: req.additional_permissions.clone(),
stopwatch: stopwatch.clone(),
};
let escalate_server = EscalateServer::new(
shell_zsh_path.clone(),
main_execve_wrapper_exe,
@@ -1150,6 +1225,108 @@ fn join_program_and_argv(program: &AbsolutePathBuf, argv: &[String]) -> Vec<Stri
.collect::<Vec<_>>()
}
async fn resolve_direct_skill_script_override(
action_provider: &CoreShellActionProvider,
script: &str,
workdir: &AbsolutePathBuf,
additional_permissions: Option<&PermissionProfile>,
) -> anyhow::Result<Option<DirectSkillScriptOverride>> {
let Some((program, argv)) = resolve_direct_shell_command(script, workdir) else {
return Ok(None);
};
let Some(skill) = action_provider.find_skill(&program).await else {
return Ok(None);
};
match action_provider
.determine_action(&program, &argv, workdir)
.await?
{
EscalationDecision::Deny { reason } => Ok(Some(DirectSkillScriptOverride::Denied(
denied_exec_tool_call_output(reason.as_deref()),
))),
EscalationDecision::Escalate(EscalationExecution::Permissions(
EscalationPermissions::PermissionProfile(permission_profile),
)) => Ok(Some(DirectSkillScriptOverride::RunDirect {
command: join_program_and_argv(&program, &argv),
additional_permissions: merge_permission_profiles(
additional_permissions,
Some(&permission_profile),
)
.unwrap_or(permission_profile),
})),
EscalationDecision::Escalate(EscalationExecution::Permissions(
EscalationPermissions::Permissions(_),
))
| EscalationDecision::Escalate(EscalationExecution::TurnDefault)
| EscalationDecision::Escalate(EscalationExecution::Unsandboxed)
| EscalationDecision::Run => Ok(None),
}
}
fn resolve_direct_shell_command(
script: &str,
workdir: &AbsolutePathBuf,
) -> Option<(AbsolutePathBuf, Vec<String>)> {
let command = parse_direct_shell_command(script)?;
let (program, args) = command.split_first()?;
let program_path = Path::new(program);
if !program_path.is_absolute() && !program.contains('/') {
return None;
}
let program = if program_path.is_absolute() {
AbsolutePathBuf::from_absolute_path(program_path).ok()?
} else {
AbsolutePathBuf::resolve_path_against_base(program_path, workdir.as_path()).ok()?
};
let normalized_program = path_utils::normalize_for_path_comparison(program.as_path())
.ok()
.and_then(|path| AbsolutePathBuf::from_absolute_path(path).ok())
.unwrap_or(program);
let argv = std::iter::once(normalized_program.to_string_lossy().to_string())
.chain(args.iter().cloned())
.collect::<Vec<_>>();
Some((normalized_program, argv))
}
fn parse_direct_shell_command(script: &str) -> Option<Vec<String>> {
let shell_command = vec!["zsh".to_string(), "-c".to_string(), script.to_string()];
if let Some(commands) = parse_shell_lc_plain_commands(&shell_command) {
let [command] = commands.as_slice() else {
return None;
};
return Some(command.clone());
}
if let Some(command) = parse_shell_lc_single_command_prefix(&shell_command) {
return Some(command);
}
if script.contains("/.codex/shell_snapshots/") {
let tail = script
.rsplit("\n\n")
.find(|chunk| !chunk.trim().is_empty() && *chunk != script)?;
let tail_shell_command = vec!["zsh".to_string(), "-c".to_string(), tail.to_string()];
if let Some(commands) = parse_shell_lc_plain_commands(&tail_shell_command) {
let [command] = commands.as_slice() else {
return None;
};
return Some(command.clone());
}
}
None
}
fn denied_exec_tool_call_output(reason: Option<&str>) -> ExecToolCallOutput {
let stderr = reason
.map(|reason| format!("Execution denied: {reason}\n"))
.unwrap_or_else(|| "Execution denied\n".to_string());
ExecToolCallOutput {
exit_code: 1,
stderr: crate::exec::StreamOutput::new(stderr.clone()),
aggregated_output: crate::exec::StreamOutput::new(stderr),
..Default::default()
}
}
#[cfg(test)]
#[path = "unix_escalation_tests.rs"]
mod tests;

View File

@@ -8,6 +8,7 @@ use super::evaluate_intercepted_exec_policy;
use super::extract_shell_script;
use super::join_program_and_argv;
use super::map_exec_result;
use super::parse_direct_shell_command;
#[cfg(target_os = "macos")]
use crate::config::Constrained;
#[cfg(target_os = "macos")]
@@ -238,6 +239,21 @@ fn extract_shell_script_supports_wrapped_command_prefixes() {
);
}
#[test]
fn parse_direct_shell_command_supports_snapshot_wrapped_command() {
let script = "\
if . '/Users/celia/.codex/shell_snapshots/session.sh' >/dev/null 2>&1; then :; fi
/Users/celia/code/codex/.codex/skills/proxy-a/scripts/fetch_example.sh";
assert_eq!(
parse_direct_shell_command(script),
Some(vec![
"/Users/celia/code/codex/.codex/skills/proxy-a/scripts/fetch_example.sh".to_string(),
]),
);
}
#[test]
fn extract_shell_script_rejects_unsupported_shell_invocation() {
let err = extract_shell_script(&[

View File

@@ -23,6 +23,7 @@ impl ZshForkRuntime {
config: &mut Config,
approval_policy: AskForApproval,
sandbox_policy: SandboxPolicy,
allow_login_shell: bool,
) {
config
.features
@@ -34,7 +35,7 @@ impl ZshForkRuntime {
.expect("test config should allow feature update");
config.zsh_path = Some(self.zsh_path.clone());
config.main_execve_wrapper_exe = Some(self.main_execve_wrapper_exe.clone());
config.permissions.allow_login_shell = false;
config.permissions.allow_login_shell = allow_login_shell;
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy);
}
@@ -86,7 +87,31 @@ where
let mut builder = test_codex()
.with_pre_build_hook(pre_build_hook)
.with_config(move |config| {
runtime.apply_to_config(config, approval_policy, sandbox_policy);
runtime.apply_to_config(
config,
approval_policy,
sandbox_policy,
/*allow_login_shell*/ false,
);
});
builder.build(server).await
}
pub async fn build_zsh_fork_test_with_login_shell<F>(
server: &wiremock::MockServer,
runtime: ZshForkRuntime,
approval_policy: AskForApproval,
sandbox_policy: SandboxPolicy,
allow_login_shell: bool,
pre_build_hook: F,
) -> Result<TestCodex>
where
F: FnOnce(&Path) + Send + 'static,
{
let mut builder = test_codex()
.with_pre_build_hook(pre_build_hook)
.with_config(move |config| {
runtime.apply_to_config(config, approval_policy, sandbox_policy, allow_login_shell);
});
builder.build(server).await
}

View File

@@ -21,6 +21,7 @@ use core_test_support::test_codex::TestCodex;
use core_test_support::wait_for_event;
use core_test_support::wait_for_event_match;
use core_test_support::zsh_fork::build_zsh_fork_test;
use core_test_support::zsh_fork::build_zsh_fork_test_with_login_shell;
use core_test_support::zsh_fork::restrictive_workspace_write_policy;
use core_test_support::zsh_fork::zsh_fork_runtime;
use pretty_assertions::assert_eq;
@@ -120,6 +121,50 @@ description: {name} skill
Ok(script_path)
}
#[cfg(unix)]
fn write_project_skill_with_shell_script_contents(
workspace_root: &Path,
name: &str,
script_name: &str,
script_contents: &str,
) -> Result<PathBuf> {
use std::os::unix::fs::PermissionsExt;
let skill_dir = workspace_root.join(".codex").join("skills").join(name);
let scripts_dir = skill_dir.join("scripts");
let metadata_dir = skill_dir.join("agents");
fs::create_dir_all(&scripts_dir)?;
fs::create_dir_all(&metadata_dir)?;
fs::write(
skill_dir.join("SKILL.md"),
format!(
r#"---
name: {name}
description: {name} skill
---
"#
),
)?;
let script_path = scripts_dir.join(script_name);
fs::write(&script_path, script_contents)?;
let mut permissions = fs::metadata(&script_path)?.permissions();
permissions.set_mode(0o755);
fs::set_permissions(&script_path, permissions)?;
Ok(script_path)
}
fn write_project_skill_metadata(workspace_root: &Path, name: &str, contents: &str) -> Result<()> {
let metadata_dir = workspace_root
.join(".codex")
.join("skills")
.join(name)
.join("agents");
fs::create_dir_all(&metadata_dir)?;
fs::write(metadata_dir.join("openai.yaml"), contents)?;
Ok(())
}
fn skill_script_command(test: &TestCodex, script_name: &str) -> Result<(String, String)> {
let script_path = fs::canonicalize(
test.codex_home_path()
@@ -669,6 +714,310 @@ async fn shell_zsh_fork_skill_without_permissions_inherits_turn_sandbox() -> Res
Ok(())
}
/// Permissionless skills should inherit the turn sandbox even when the turn is
/// read-only, rather than running under a distinct skill-specific sandbox.
#[cfg(unix)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn shell_zsh_fork_skill_without_permissions_inherits_turn_sandbox_when_read_only()
-> Result<()> {
skip_if_no_network!(Ok(()));
let Some(runtime) = zsh_fork_runtime("zsh-fork read-only inherited turn sandbox test")? else {
return Ok(());
};
let approval_policy = AskForApproval::Granular(GranularApprovalConfig {
sandbox_approval: false,
rules: true,
skill_approval: true,
request_permissions: true,
mcp_elicitations: true,
});
let read_only_policy = SandboxPolicy::new_read_only_policy();
let server = start_mock_server().await;
let tool_call_id = "zsh-fork-read-only-skill";
let test = build_zsh_fork_test(
&server,
runtime,
approval_policy,
read_only_policy.clone(),
move |home| {
write_skill_with_shell_script_contents(
home,
"mbolin-test-skill",
"read-only.sh",
"#!/bin/sh\nprintf 'read-only-ok\\n'\n",
)
.unwrap();
},
)
.await?;
let (_, command) = skill_script_command(&test, "read-only.sh")?;
let arguments = shell_command_arguments(&command)?;
let mocks =
mount_function_call_agent_response(&server, tool_call_id, &arguments, "shell_command")
.await;
submit_turn_with_policies(
&test,
"use $mbolin-test-skill",
approval_policy,
read_only_policy,
)
.await?;
let approval = wait_for_exec_approval_request(&test).await;
assert!(
approval.is_none(),
"expected permissionless skill script to skip exec approval and inherit the turn sandbox"
);
wait_for_turn_complete(&test).await;
let output = mocks
.completion
.single_request()
.function_call_output(tool_call_id)["output"]
.as_str()
.unwrap_or_default()
.to_string();
assert!(
output.contains("read-only-ok"),
"expected permissionless skill script to execute while inheriting the read-only turn sandbox, got output: {output:?}"
);
Ok(())
}
/// Skills with declared permissions should still reach the approval prompt
/// under a restricted read-only turn sandbox before their script executes.
#[cfg(unix)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn shell_zsh_fork_skill_with_permissions_prompts_under_read_only_sandbox() -> Result<()> {
skip_if_no_network!(Ok(()));
let Some(runtime) = zsh_fork_runtime("zsh-fork read-only skill permissions prompt test")?
else {
return Ok(());
};
let approval_policy = AskForApproval::Granular(GranularApprovalConfig {
sandbox_approval: false,
rules: true,
skill_approval: true,
request_permissions: true,
mcp_elicitations: true,
});
let read_only_policy = SandboxPolicy::new_read_only_policy();
let server = start_mock_server().await;
let tool_call_id = "zsh-fork-read-only-skill-permissions";
let test = build_zsh_fork_test(
&server,
runtime,
approval_policy,
read_only_policy.clone(),
move |home| {
write_skill_with_shell_script_contents(
home,
"mbolin-test-skill",
"read-only.sh",
"#!/usr/bin/env bash\nprintf 'proxy-style-ok\\n'\n",
)
.unwrap();
write_skill_metadata(
home,
"mbolin-test-skill",
r#"
permissions:
network:
enabled: true
allowed_domains:
- "www.example.com"
"#,
)
.unwrap();
},
)
.await?;
let (script_path_str, command) = skill_script_command(&test, "read-only.sh")?;
let arguments = shell_command_arguments(&command)?;
let _mocks =
mount_function_call_agent_response(&server, tool_call_id, &arguments, "shell_command")
.await;
submit_turn_with_policies(
&test,
"use $mbolin-test-skill",
approval_policy,
read_only_policy,
)
.await?;
let approval = wait_for_exec_approval_request(&test)
.await
.expect("expected skill approval prompt before script execution");
assert_eq!(approval.call_id, tool_call_id);
assert_eq!(approval.command, vec![script_path_str]);
Ok(())
}
#[cfg(unix)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn shell_zsh_fork_relative_skill_path_with_permissions_prompts_under_read_only_sandbox()
-> Result<()> {
skip_if_no_network!(Ok(()));
let Some(runtime) =
zsh_fork_runtime("zsh-fork read-only relative skill permissions prompt test")?
else {
return Ok(());
};
let approval_policy = AskForApproval::Granular(GranularApprovalConfig {
sandbox_approval: false,
rules: true,
skill_approval: true,
request_permissions: true,
mcp_elicitations: true,
});
let read_only_policy = SandboxPolicy::new_read_only_policy();
let server = start_mock_server().await;
let tool_call_id = "zsh-fork-read-only-relative-skill-permissions";
let test = build_zsh_fork_test(
&server,
runtime,
approval_policy,
read_only_policy.clone(),
|_home| {},
)
.await?;
let script_path = write_project_skill_with_shell_script_contents(
test.cwd_path(),
"mbolin-test-skill",
"read-only.sh",
"#!/usr/bin/env bash\nprintf 'proxy-style-ok\\n'\n",
)?;
write_project_skill_metadata(
test.cwd_path(),
"mbolin-test-skill",
r#"
permissions:
network:
enabled: true
allowed_domains:
- "www.example.com"
"#,
)?;
let command = shlex::try_join(["./.codex/skills/mbolin-test-skill/scripts/read-only.sh"])?;
let arguments = shell_command_arguments(&command)?;
let _mocks =
mount_function_call_agent_response(&server, tool_call_id, &arguments, "shell_command")
.await;
submit_turn_with_policies(
&test,
"use $mbolin-test-skill",
approval_policy,
read_only_policy,
)
.await?;
let approval = wait_for_exec_approval_request(&test)
.await
.expect("expected skill approval prompt before relative script execution");
assert_eq!(approval.call_id, tool_call_id);
assert_eq!(
approval.command,
vec![
fs::canonicalize(script_path)?
.to_string_lossy()
.into_owned()
]
);
Ok(())
}
#[cfg(unix)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn shell_zsh_fork_skill_without_permissions_executes_under_read_only_login_shell()
-> Result<()> {
skip_if_no_network!(Ok(()));
let Some(runtime) = zsh_fork_runtime("zsh-fork read-only login shell test")? else {
return Ok(());
};
let approval_policy = AskForApproval::Granular(GranularApprovalConfig {
sandbox_approval: false,
rules: true,
skill_approval: true,
request_permissions: true,
mcp_elicitations: true,
});
let read_only_policy = SandboxPolicy::new_read_only_policy();
let server = start_mock_server().await;
let tool_call_id = "zsh-fork-read-only-login-shell";
let test = build_zsh_fork_test_with_login_shell(
&server,
runtime,
approval_policy,
read_only_policy.clone(),
true,
move |home| {
write_skill_with_shell_script_contents(
home,
"mbolin-test-skill",
"read-only-login.sh",
"#!/usr/bin/env bash\nprintf 'login-shell-ok\\n'\n",
)
.unwrap();
},
)
.await?;
let (_, command) = skill_script_command(&test, "read-only-login.sh")?;
let arguments = shell_command_arguments(&command)?;
let mocks =
mount_function_call_agent_response(&server, tool_call_id, &arguments, "shell_command")
.await;
submit_turn_with_policies(
&test,
"use $mbolin-test-skill",
approval_policy,
read_only_policy,
)
.await?;
let approval = wait_for_exec_approval_request(&test).await;
assert!(
approval.is_none(),
"expected permissionless skill script to skip exec approval in read-only login shell"
);
wait_for_turn_complete(&test).await;
let output = mocks
.completion
.single_request()
.function_call_output(tool_call_id)["output"]
.as_str()
.unwrap_or_default()
.to_string();
assert!(
output.contains("login-shell-ok"),
"expected permissionless skill script to execute under read-only login-shell zsh-fork, got output: {output:?}"
);
Ok(())
}
/// Empty skill permissions should behave like no skill override and inherit the
/// turn sandbox without prompting.
#[cfg(unix)]