mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
linux-sandbox: switch helper plumbing to PermissionProfile (#20106)
## Why `PermissionProfile` is the canonical runtime permission model in the Rust workspace, but the Linux sandbox helper still accepted a legacy `SandboxPolicy` plus separate filesystem and network policy flags. That translation layer made the helper interface harder to reason about and left `linux-sandbox`-specific callers and tests coupled to the legacy policy representation. This change moves the helper onto `PermissionProfile` directly so the Linux sandbox plumbing matches the rest of the permission stack. ## What changed - changed `codex-linux-sandbox` to accept `--permission-profile` and derive the runtime filesystem and network policies internally - updated the in-process seccomp and legacy Landlock path in `codex-rs/linux-sandbox` to operate on `PermissionProfile` - updated Linux sandbox argv construction in `codex-rs/sandboxing`, `codex-rs/core`, and the CLI debug sandbox path to pass the canonical profile instead of serializing compatibility policy projections - simplified the Linux sandbox tests to build the exact permission profile under test, including the managed-proxy path and direct-runtime-enforcement carveout coverage - removed helper-local `SandboxPolicy` usage from `bwrap` tests where `FileSystemSandboxPolicy` is already the value being exercised ## Testing - `cargo test -p codex-sandboxing` - `cargo test -p codex-linux-sandbox` (on this macOS host, the crate compiled cleanly and its Linux-only tests were cfg-gated) - `cargo test -p codex-core --no-run` - `cargo test -p codex-cli --no-run`
This commit is contained in:
committed by
GitHub
Unverified
parent
80fb0704ee
commit
e6db1a9442
@@ -16,7 +16,8 @@ use codex_core::spawn::CODEX_SANDBOX_ENV_VAR;
|
||||
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_policies;
|
||||
use codex_sandboxing::landlock::allow_network_for_proxy;
|
||||
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_permission_profile;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_sandboxing::seatbelt::CreateSeatbeltCommandArgsParams;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -222,19 +223,14 @@ async fn run_command_under_sandbox(
|
||||
.codex_linux_sandbox_exe
|
||||
.expect("codex-linux-sandbox executable not found");
|
||||
let use_legacy_landlock = config.features.use_legacy_landlock();
|
||||
let file_system_sandbox_policy = config.permissions.file_system_sandbox_policy();
|
||||
let network_sandbox_policy = config.permissions.network_sandbox_policy();
|
||||
let args = create_linux_sandbox_command_args_for_policies(
|
||||
let args = create_linux_sandbox_command_args_for_permission_profile(
|
||||
command,
|
||||
cwd.as_path(),
|
||||
&config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(sandbox_policy_cwd.as_path()),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
&config.permissions.permission_profile(),
|
||||
sandbox_policy_cwd.as_path(),
|
||||
use_legacy_landlock,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
allow_network_for_proxy(managed_network_requirements_enabled),
|
||||
);
|
||||
spawn_debug_sandbox_child(
|
||||
codex_linux_sandbox_exe,
|
||||
|
||||
@@ -3,10 +3,9 @@ use crate::spawn::StdioPolicy;
|
||||
use crate::spawn::spawn_child_async;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
|
||||
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
|
||||
use codex_sandboxing::landlock::allow_network_for_proxy;
|
||||
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_policies;
|
||||
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_permission_profile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
@@ -17,9 +16,8 @@ use tokio::process::Child;
|
||||
/// isolation plus seccomp for network restrictions.
|
||||
///
|
||||
/// Unlike macOS Seatbelt where we directly embed the policy text, the Linux
|
||||
/// helper is a separate executable. We pass both the canonical split
|
||||
/// filesystem/network policies and a compatibility legacy projection as JSON
|
||||
/// until the helper protocol no longer needs the legacy field.
|
||||
/// helper is a separate executable. We pass the canonical permission profile
|
||||
/// as JSON and let the helper derive the runtime filesystem/network policies.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn spawn_command_under_linux_sandbox<P>(
|
||||
codex_linux_sandbox_exe: P,
|
||||
@@ -35,20 +33,11 @@ pub async fn spawn_command_under_linux_sandbox<P>(
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
sandbox_policy_cwd.as_path(),
|
||||
);
|
||||
let args = create_linux_sandbox_command_args_for_policies(
|
||||
let network_sandbox_policy = permission_profile.network_sandbox_policy();
|
||||
let args = create_linux_sandbox_command_args_for_permission_profile(
|
||||
command,
|
||||
command_cwd.as_path(),
|
||||
&sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
permission_profile,
|
||||
sandbox_policy_cwd,
|
||||
use_legacy_landlock,
|
||||
allow_network_for_proxy(/*enforce_managed_network*/ false),
|
||||
|
||||
@@ -1036,7 +1036,6 @@ mod tests {
|
||||
use codex_protocol::protocol::FileSystemSandboxEntry;
|
||||
use codex_protocol::protocol::FileSystemSandboxPolicy;
|
||||
use codex_protocol::protocol::FileSystemSpecialPath;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
@@ -1066,7 +1065,7 @@ mod tests {
|
||||
let command = vec!["/bin/true".to_string()];
|
||||
let args = create_bwrap_command_args(
|
||||
command.clone(),
|
||||
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
Path::new("/"),
|
||||
Path::new("/"),
|
||||
BwrapOptions {
|
||||
@@ -1085,7 +1084,7 @@ mod tests {
|
||||
let command = vec!["/bin/true".to_string()];
|
||||
let args = create_bwrap_command_args(
|
||||
command,
|
||||
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
Path::new("/"),
|
||||
Path::new("/"),
|
||||
BwrapOptions {
|
||||
@@ -1399,22 +1398,18 @@ mod tests {
|
||||
let missing_root = temp_dir.path().join("missing");
|
||||
std::fs::create_dir(&existing_root).expect("create existing root");
|
||||
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![
|
||||
let policy = FileSystemSandboxPolicy::workspace_write(
|
||||
&[
|
||||
AbsolutePathBuf::try_from(existing_root.as_path()).expect("absolute existing root"),
|
||||
AbsolutePathBuf::try_from(missing_root.as_path()).expect("absolute missing root"),
|
||||
],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
/*exclude_tmpdir_env_var*/ true,
|
||||
/*exclude_slash_tmp*/ true,
|
||||
);
|
||||
|
||||
let args = create_filesystem_args(
|
||||
&FileSystemSandboxPolicy::from(&policy),
|
||||
temp_dir.path(),
|
||||
NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH,
|
||||
)
|
||||
.expect("filesystem args");
|
||||
let args =
|
||||
create_filesystem_args(&policy, temp_dir.path(), NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH)
|
||||
.expect("filesystem args");
|
||||
let existing_root = path_to_string(&existing_root);
|
||||
let missing_root = path_to_string(&missing_root);
|
||||
|
||||
@@ -1532,15 +1527,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn mounts_dev_before_writable_dev_binds() {
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from(Path::new("/dev")).expect("/dev path")],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let sandbox_policy = FileSystemSandboxPolicy::workspace_write(
|
||||
&[AbsolutePathBuf::try_from(Path::new("/dev")).expect("/dev path")],
|
||||
/*exclude_tmpdir_env_var*/ true,
|
||||
/*exclude_slash_tmp*/ true,
|
||||
);
|
||||
|
||||
let args = create_filesystem_args(
|
||||
&FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
&sandbox_policy,
|
||||
Path::new("/"),
|
||||
NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH,
|
||||
)
|
||||
|
||||
@@ -8,8 +8,8 @@ use std::path::Path;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use landlock::ABI;
|
||||
@@ -39,14 +39,15 @@ use seccompiler::apply_filter;
|
||||
/// - installing the network seccomp filter when network access is disabled.
|
||||
///
|
||||
/// Filesystem restrictions are intentionally handled by bubblewrap.
|
||||
pub(crate) fn apply_sandbox_policy_to_current_thread(
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
pub(crate) fn apply_permission_profile_to_current_thread(
|
||||
permission_profile: &PermissionProfile,
|
||||
cwd: &Path,
|
||||
apply_landlock_fs: bool,
|
||||
allow_network_for_proxy: bool,
|
||||
proxy_routed_network: bool,
|
||||
) -> Result<()> {
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let network_seccomp_mode = network_seccomp_mode(
|
||||
network_sandbox_policy,
|
||||
allow_network_for_proxy,
|
||||
@@ -58,7 +59,7 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
|
||||
// we avoid this unless we need seccomp or we are explicitly using the
|
||||
// legacy Landlock filesystem pipeline.
|
||||
if network_seccomp_mode.is_some()
|
||||
|| (apply_landlock_fs && !sandbox_policy.has_full_disk_write_access())
|
||||
|| (apply_landlock_fs && !file_system_sandbox_policy.has_full_disk_write_access())
|
||||
{
|
||||
set_no_new_privs()?;
|
||||
}
|
||||
@@ -67,15 +68,15 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
|
||||
install_network_seccomp_filter_on_current_thread(mode)?;
|
||||
}
|
||||
|
||||
if apply_landlock_fs && !sandbox_policy.has_full_disk_write_access() {
|
||||
if !sandbox_policy.has_full_disk_read_access() {
|
||||
if apply_landlock_fs && !file_system_sandbox_policy.has_full_disk_write_access() {
|
||||
if !file_system_sandbox_policy.has_full_disk_read_access() {
|
||||
return Err(CodexErr::UnsupportedOperation(
|
||||
"Restricted read-only access is not supported by the legacy Linux Landlock filesystem backend."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let writable_roots = sandbox_policy
|
||||
let writable_roots = file_system_sandbox_policy
|
||||
.get_writable_roots_with_cwd(cwd)
|
||||
.into_iter()
|
||||
.map(|writable_root| writable_root.root)
|
||||
|
||||
@@ -10,14 +10,14 @@ use std::path::PathBuf;
|
||||
use crate::bwrap::BwrapNetworkMode;
|
||||
use crate::bwrap::BwrapOptions;
|
||||
use crate::bwrap::create_bwrap_command_args;
|
||||
use crate::landlock::apply_sandbox_policy_to_current_thread;
|
||||
use crate::landlock::apply_permission_profile_to_current_thread;
|
||||
use crate::launcher::exec_bwrap;
|
||||
use crate::launcher::preferred_bwrap_supports_argv0;
|
||||
use crate::proxy_routing::activate_proxy_routes_in_netns;
|
||||
use crate::proxy_routing::prepare_host_proxy_route_spec;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::FileSystemSandboxPolicy;
|
||||
use codex_protocol::protocol::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -41,18 +41,13 @@ pub struct LandlockCommand {
|
||||
#[arg(long = "command-cwd", hide = true)]
|
||||
pub command_cwd: Option<PathBuf>,
|
||||
|
||||
/// Legacy compatibility policy.
|
||||
///
|
||||
/// Newer callers pass split filesystem/network policies as well so the
|
||||
/// helper can migrate incrementally without breaking older invocations.
|
||||
#[arg(long = "sandbox-policy", hide = true)]
|
||||
pub sandbox_policy: Option<SandboxPolicy>,
|
||||
|
||||
#[arg(long = "file-system-sandbox-policy", hide = true)]
|
||||
pub file_system_sandbox_policy: Option<FileSystemSandboxPolicy>,
|
||||
|
||||
#[arg(long = "network-sandbox-policy", hide = true)]
|
||||
pub network_sandbox_policy: Option<NetworkSandboxPolicy>,
|
||||
/// Canonical runtime permissions for the command.
|
||||
#[arg(
|
||||
long = "permission-profile",
|
||||
hide = true,
|
||||
value_parser = parse_permission_profile
|
||||
)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
|
||||
/// Opt-in: use the legacy Landlock Linux sandbox fallback.
|
||||
///
|
||||
@@ -102,9 +97,7 @@ pub fn run_main() -> ! {
|
||||
let LandlockCommand {
|
||||
sandbox_policy_cwd,
|
||||
command_cwd,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
permission_profile,
|
||||
use_legacy_landlock,
|
||||
apply_seccomp_then_exec,
|
||||
allow_network_for_proxy,
|
||||
@@ -117,17 +110,11 @@ pub fn run_main() -> ! {
|
||||
panic!("No command specified to execute.");
|
||||
}
|
||||
ensure_inner_stage_mode_is_valid(apply_seccomp_then_exec, use_legacy_landlock);
|
||||
let EffectiveSandboxPolicies {
|
||||
sandbox_policy,
|
||||
let EffectivePermissions {
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
} = resolve_sandbox_policies(
|
||||
sandbox_policy_cwd.as_path(),
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
)
|
||||
.unwrap_or_else(|err| panic!("{err}"));
|
||||
} = resolve_permission_profile(permission_profile).unwrap_or_else(|err| panic!("{err}"));
|
||||
ensure_legacy_landlock_mode_supports_policy(
|
||||
use_legacy_landlock,
|
||||
&file_system_sandbox_policy,
|
||||
@@ -147,9 +134,8 @@ pub fn run_main() -> ! {
|
||||
}
|
||||
}
|
||||
let proxy_routing_active = allow_network_for_proxy;
|
||||
if let Err(e) = apply_sandbox_policy_to_current_thread(
|
||||
&sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
if let Err(e) = apply_permission_profile_to_current_thread(
|
||||
&permission_profile,
|
||||
&sandbox_policy_cwd,
|
||||
/*apply_landlock_fs*/ false,
|
||||
allow_network_for_proxy,
|
||||
@@ -161,9 +147,8 @@ pub fn run_main() -> ! {
|
||||
}
|
||||
|
||||
if file_system_sandbox_policy.has_full_disk_write_access() && !allow_network_for_proxy {
|
||||
if let Err(e) = apply_sandbox_policy_to_current_thread(
|
||||
&sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
if let Err(e) = apply_permission_profile_to_current_thread(
|
||||
&permission_profile,
|
||||
&sandbox_policy_cwd,
|
||||
/*apply_landlock_fs*/ false,
|
||||
allow_network_for_proxy,
|
||||
@@ -189,9 +174,7 @@ pub fn run_main() -> ! {
|
||||
let inner = build_inner_seccomp_command(InnerSeccompCommandArgs {
|
||||
sandbox_policy_cwd: &sandbox_policy_cwd,
|
||||
command_cwd: command_cwd.as_deref(),
|
||||
sandbox_policy: &sandbox_policy,
|
||||
file_system_sandbox_policy: &file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
permission_profile: &permission_profile,
|
||||
allow_network_for_proxy,
|
||||
proxy_route_spec,
|
||||
command,
|
||||
@@ -208,9 +191,8 @@ pub fn run_main() -> ! {
|
||||
}
|
||||
|
||||
// Legacy path: Landlock enforcement only, when bwrap sandboxing is not enabled.
|
||||
if let Err(e) = apply_sandbox_policy_to_current_thread(
|
||||
&sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
if let Err(e) = apply_permission_profile_to_current_thread(
|
||||
&permission_profile,
|
||||
&sandbox_policy_cwd,
|
||||
/*apply_landlock_fs*/ true,
|
||||
allow_network_for_proxy,
|
||||
@@ -222,164 +204,41 @@ pub fn run_main() -> ! {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct EffectiveSandboxPolicies {
|
||||
sandbox_policy: SandboxPolicy,
|
||||
struct EffectivePermissions {
|
||||
permission_profile: PermissionProfile,
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum ResolveSandboxPoliciesError {
|
||||
PartialSplitPolicies,
|
||||
SplitPoliciesRequireDirectRuntimeEnforcement(String),
|
||||
FailedToDeriveLegacyPolicy(String),
|
||||
MismatchedLegacyPolicy {
|
||||
provided: SandboxPolicy,
|
||||
derived: SandboxPolicy,
|
||||
},
|
||||
enum ResolvePermissionProfileError {
|
||||
MissingConfiguration,
|
||||
}
|
||||
|
||||
impl fmt::Display for ResolveSandboxPoliciesError {
|
||||
impl fmt::Display for ResolvePermissionProfileError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::PartialSplitPolicies => {
|
||||
write!(
|
||||
f,
|
||||
"file-system and network sandbox policies must be provided together"
|
||||
)
|
||||
}
|
||||
Self::SplitPoliciesRequireDirectRuntimeEnforcement(err) => {
|
||||
write!(
|
||||
f,
|
||||
"split sandbox policies require direct runtime enforcement and cannot be paired with legacy sandbox policy: {err}"
|
||||
)
|
||||
}
|
||||
Self::FailedToDeriveLegacyPolicy(err) => {
|
||||
write!(
|
||||
f,
|
||||
"failed to derive legacy sandbox policy from split policies: {err}"
|
||||
)
|
||||
}
|
||||
Self::MismatchedLegacyPolicy { provided, derived } => {
|
||||
write!(
|
||||
f,
|
||||
"legacy sandbox policy must match split sandbox policies: provided={provided:?}, derived={derived:?}"
|
||||
)
|
||||
}
|
||||
Self::MissingConfiguration => write!(f, "missing sandbox policy configuration"),
|
||||
Self::MissingConfiguration => write!(f, "missing permission profile configuration"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_sandbox_policies(
|
||||
sandbox_policy_cwd: &Path,
|
||||
sandbox_policy: Option<SandboxPolicy>,
|
||||
file_system_sandbox_policy: Option<FileSystemSandboxPolicy>,
|
||||
network_sandbox_policy: Option<NetworkSandboxPolicy>,
|
||||
) -> Result<EffectiveSandboxPolicies, ResolveSandboxPoliciesError> {
|
||||
// Accept either a fully legacy policy, a fully split policy pair, or all
|
||||
// three views together. Reject partial split-policy input so the helper
|
||||
// never runs with mismatched filesystem/network state.
|
||||
let split_policies = match (file_system_sandbox_policy, network_sandbox_policy) {
|
||||
(Some(file_system_sandbox_policy), Some(network_sandbox_policy)) => {
|
||||
Some((file_system_sandbox_policy, network_sandbox_policy))
|
||||
}
|
||||
(None, None) => None,
|
||||
_ => return Err(ResolveSandboxPoliciesError::PartialSplitPolicies),
|
||||
};
|
||||
|
||||
match (sandbox_policy, split_policies) {
|
||||
(Some(sandbox_policy), Some((file_system_sandbox_policy, network_sandbox_policy))) => {
|
||||
if file_system_sandbox_policy
|
||||
.needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd)
|
||||
{
|
||||
return Ok(EffectiveSandboxPolicies {
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
});
|
||||
}
|
||||
let derived_legacy_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, sandbox_policy_cwd)
|
||||
.map_err(|err| {
|
||||
ResolveSandboxPoliciesError::SplitPoliciesRequireDirectRuntimeEnforcement(
|
||||
err.to_string(),
|
||||
)
|
||||
})?;
|
||||
if !legacy_sandbox_policies_match_semantics(
|
||||
&sandbox_policy,
|
||||
&derived_legacy_policy,
|
||||
sandbox_policy_cwd,
|
||||
) {
|
||||
return Err(ResolveSandboxPoliciesError::MismatchedLegacyPolicy {
|
||||
provided: sandbox_policy,
|
||||
derived: derived_legacy_policy,
|
||||
});
|
||||
}
|
||||
Ok(EffectiveSandboxPolicies {
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
})
|
||||
}
|
||||
(Some(sandbox_policy), None) => Ok(EffectiveSandboxPolicies {
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&sandbox_policy,
|
||||
sandbox_policy_cwd,
|
||||
),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::from(&sandbox_policy),
|
||||
sandbox_policy,
|
||||
}),
|
||||
(None, Some((file_system_sandbox_policy, network_sandbox_policy))) => {
|
||||
let sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, sandbox_policy_cwd)
|
||||
.map_err(|err| {
|
||||
ResolveSandboxPoliciesError::FailedToDeriveLegacyPolicy(err.to_string())
|
||||
})?;
|
||||
Ok(EffectiveSandboxPolicies {
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
})
|
||||
}
|
||||
(None, None) => Err(ResolveSandboxPoliciesError::MissingConfiguration),
|
||||
}
|
||||
fn parse_permission_profile(value: &str) -> std::result::Result<PermissionProfile, String> {
|
||||
serde_json::from_str(value).map_err(|err| format!("invalid permission profile JSON: {err}"))
|
||||
}
|
||||
|
||||
fn legacy_sandbox_policies_match_semantics(
|
||||
provided: &SandboxPolicy,
|
||||
derived: &SandboxPolicy,
|
||||
sandbox_policy_cwd: &Path,
|
||||
) -> bool {
|
||||
NetworkSandboxPolicy::from(provided) == NetworkSandboxPolicy::from(derived)
|
||||
&& file_system_sandbox_policies_match_semantics(
|
||||
&FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
provided,
|
||||
sandbox_policy_cwd,
|
||||
),
|
||||
&FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
derived,
|
||||
sandbox_policy_cwd,
|
||||
),
|
||||
sandbox_policy_cwd,
|
||||
)
|
||||
}
|
||||
|
||||
fn file_system_sandbox_policies_match_semantics(
|
||||
provided: &FileSystemSandboxPolicy,
|
||||
derived: &FileSystemSandboxPolicy,
|
||||
sandbox_policy_cwd: &Path,
|
||||
) -> bool {
|
||||
provided.has_full_disk_read_access() == derived.has_full_disk_read_access()
|
||||
&& provided.has_full_disk_write_access() == derived.has_full_disk_write_access()
|
||||
&& provided.include_platform_defaults() == derived.include_platform_defaults()
|
||||
&& provided.get_readable_roots_with_cwd(sandbox_policy_cwd)
|
||||
== derived.get_readable_roots_with_cwd(sandbox_policy_cwd)
|
||||
&& provided.get_writable_roots_with_cwd(sandbox_policy_cwd)
|
||||
== derived.get_writable_roots_with_cwd(sandbox_policy_cwd)
|
||||
&& provided.get_unreadable_roots_with_cwd(sandbox_policy_cwd)
|
||||
== derived.get_unreadable_roots_with_cwd(sandbox_policy_cwd)
|
||||
fn resolve_permission_profile(
|
||||
permission_profile: Option<PermissionProfile>,
|
||||
) -> Result<EffectivePermissions, ResolvePermissionProfileError> {
|
||||
let permission_profile =
|
||||
permission_profile.ok_or(ResolvePermissionProfileError::MissingConfiguration)?;
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
Ok(EffectivePermissions {
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_inner_stage_mode_is_valid(apply_seccomp_then_exec: bool, use_legacy_landlock: bool) {
|
||||
@@ -399,7 +258,7 @@ fn ensure_legacy_landlock_mode_supports_policy(
|
||||
.needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd)
|
||||
{
|
||||
panic!(
|
||||
"split sandbox policies requiring direct runtime enforcement are incompatible with --use-legacy-landlock"
|
||||
"permission profiles requiring direct runtime enforcement are incompatible with --use-legacy-landlock"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -656,9 +515,7 @@ fn is_proc_mount_failure(stderr: &str) -> bool {
|
||||
struct InnerSeccompCommandArgs<'a> {
|
||||
sandbox_policy_cwd: &'a Path,
|
||||
command_cwd: Option<&'a Path>,
|
||||
sandbox_policy: &'a SandboxPolicy,
|
||||
file_system_sandbox_policy: &'a FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
permission_profile: &'a PermissionProfile,
|
||||
allow_network_for_proxy: bool,
|
||||
proxy_route_spec: Option<String>,
|
||||
command: Vec<String>,
|
||||
@@ -669,9 +526,7 @@ fn build_inner_seccomp_command(args: InnerSeccompCommandArgs<'_>) -> Vec<String>
|
||||
let InnerSeccompCommandArgs {
|
||||
sandbox_policy_cwd,
|
||||
command_cwd,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
permission_profile,
|
||||
allow_network_for_proxy,
|
||||
proxy_route_spec,
|
||||
command,
|
||||
@@ -680,17 +535,9 @@ fn build_inner_seccomp_command(args: InnerSeccompCommandArgs<'_>) -> Vec<String>
|
||||
Ok(path) => path,
|
||||
Err(err) => panic!("failed to resolve current executable path: {err}"),
|
||||
};
|
||||
let policy_json = match serde_json::to_string(sandbox_policy) {
|
||||
let permission_profile_json = match serde_json::to_string(permission_profile) {
|
||||
Ok(json) => json,
|
||||
Err(err) => panic!("failed to serialize sandbox policy: {err}"),
|
||||
};
|
||||
let file_system_policy_json = match serde_json::to_string(file_system_sandbox_policy) {
|
||||
Ok(json) => json,
|
||||
Err(err) => panic!("failed to serialize filesystem sandbox policy: {err}"),
|
||||
};
|
||||
let network_policy_json = match serde_json::to_string(&network_sandbox_policy) {
|
||||
Ok(json) => json,
|
||||
Err(err) => panic!("failed to serialize network sandbox policy: {err}"),
|
||||
Err(err) => panic!("failed to serialize permission profile: {err}"),
|
||||
};
|
||||
|
||||
let mut inner = vec![
|
||||
@@ -703,12 +550,8 @@ fn build_inner_seccomp_command(args: InnerSeccompCommandArgs<'_>) -> Vec<String>
|
||||
inner.push(command_cwd.to_string_lossy().to_string());
|
||||
}
|
||||
inner.extend([
|
||||
"--sandbox-policy".to_string(),
|
||||
policy_json,
|
||||
"--file-system-sandbox-policy".to_string(),
|
||||
file_system_policy_json,
|
||||
"--network-sandbox-policy".to_string(),
|
||||
network_policy_json,
|
||||
"--permission-profile".to_string(),
|
||||
permission_profile_json,
|
||||
"--apply-seccomp-then-exec".to_string(),
|
||||
]);
|
||||
if allow_network_for_proxy {
|
||||
@@ -746,5 +589,6 @@ fn exec_or_panic(command: Vec<String>) -> ! {
|
||||
panic!("Failed to execvp {}: {err}", command[0].as_str());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "linux_run_main_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
#[cfg(test)]
|
||||
use super::*;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::protocol::FileSystemSandboxPolicy;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::protocol::NetworkSandboxPolicy;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
#[cfg(test)]
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
#[cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn read_only_permission_profile() -> PermissionProfile {
|
||||
PermissionProfile::read_only()
|
||||
}
|
||||
|
||||
fn read_only_file_system_policy() -> FileSystemSandboxPolicy {
|
||||
read_only_permission_profile().file_system_sandbox_policy()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_proc_mount_invalid_argument_failure() {
|
||||
let stderr = "bwrap: Can't mount proc on /newroot/proc: Invalid argument";
|
||||
@@ -37,10 +45,10 @@ fn ignores_non_proc_mount_errors() {
|
||||
|
||||
#[test]
|
||||
fn inserts_bwrap_argv0_before_command_separator() {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let file_system_sandbox_policy = read_only_file_system_policy();
|
||||
let mut argv = build_bwrap_argv(
|
||||
vec!["/bin/true".to_string()],
|
||||
&FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
Path::new("/"),
|
||||
Path::new("/"),
|
||||
BwrapOptions {
|
||||
@@ -80,10 +88,10 @@ fn inserts_bwrap_argv0_before_command_separator() {
|
||||
|
||||
#[test]
|
||||
fn rewrites_inner_command_path_when_bwrap_lacks_argv0() {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let file_system_sandbox_policy = read_only_file_system_policy();
|
||||
let mut argv = build_bwrap_argv(
|
||||
vec!["/bin/true".to_string()],
|
||||
&FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
Path::new("/"),
|
||||
Path::new("/"),
|
||||
BwrapOptions {
|
||||
@@ -148,10 +156,10 @@ fn rewrites_bwrap_helper_command_not_nested_user_command_when_current_exe_appear
|
||||
|
||||
#[test]
|
||||
fn inserts_unshare_net_when_network_isolation_requested() {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let file_system_sandbox_policy = read_only_file_system_policy();
|
||||
let argv = build_bwrap_argv(
|
||||
vec!["/bin/true".to_string()],
|
||||
&FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
Path::new("/"),
|
||||
Path::new("/"),
|
||||
BwrapOptions {
|
||||
@@ -166,10 +174,10 @@ fn inserts_unshare_net_when_network_isolation_requested() {
|
||||
|
||||
#[test]
|
||||
fn inserts_unshare_net_when_proxy_only_network_mode_requested() {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let file_system_sandbox_policy = read_only_file_system_policy();
|
||||
let argv = build_bwrap_argv(
|
||||
vec!["/bin/true".to_string()],
|
||||
&FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
Path::new("/"),
|
||||
Path::new("/"),
|
||||
BwrapOptions {
|
||||
@@ -250,7 +258,7 @@ fn managed_proxy_preflight_argv_is_wrapped_for_full_access_policy() {
|
||||
let argv = build_preflight_bwrap_argv(
|
||||
Path::new("/"),
|
||||
Path::new("/"),
|
||||
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
mode,
|
||||
)
|
||||
.args;
|
||||
@@ -259,13 +267,11 @@ fn managed_proxy_preflight_argv_is_wrapped_for_full_access_policy() {
|
||||
|
||||
#[test]
|
||||
fn managed_proxy_inner_command_includes_route_spec() {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let permission_profile = read_only_permission_profile();
|
||||
let args = build_inner_seccomp_command(InnerSeccompCommandArgs {
|
||||
sandbox_policy_cwd: Path::new("/tmp"),
|
||||
command_cwd: Some(Path::new("/tmp/link")),
|
||||
sandbox_policy: &sandbox_policy,
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
|
||||
permission_profile: &permission_profile,
|
||||
allow_network_for_proxy: true,
|
||||
proxy_route_spec: Some("{\"routes\":[]}".to_string()),
|
||||
command: vec!["/bin/true".to_string()],
|
||||
@@ -276,21 +282,18 @@ fn managed_proxy_inner_command_includes_route_spec() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_command_includes_split_policy_flags() {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
fn inner_command_includes_permission_profile_flag() {
|
||||
let permission_profile = read_only_permission_profile();
|
||||
let args = build_inner_seccomp_command(InnerSeccompCommandArgs {
|
||||
sandbox_policy_cwd: Path::new("/tmp"),
|
||||
command_cwd: Some(Path::new("/tmp/link")),
|
||||
sandbox_policy: &sandbox_policy,
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
|
||||
permission_profile: &permission_profile,
|
||||
allow_network_for_proxy: false,
|
||||
proxy_route_spec: None,
|
||||
command: vec!["/bin/true".to_string()],
|
||||
});
|
||||
|
||||
assert!(args.iter().any(|arg| arg == "--file-system-sandbox-policy"));
|
||||
assert!(args.iter().any(|arg| arg == "--network-sandbox-policy"));
|
||||
assert!(args.iter().any(|arg| arg == "--permission-profile"));
|
||||
assert!(
|
||||
args.windows(2)
|
||||
.any(|window| { window == ["--command-cwd", "/tmp/link"] })
|
||||
@@ -299,13 +302,11 @@ fn inner_command_includes_split_policy_flags() {
|
||||
|
||||
#[test]
|
||||
fn non_managed_inner_command_omits_route_spec() {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let permission_profile = read_only_permission_profile();
|
||||
let args = build_inner_seccomp_command(InnerSeccompCommandArgs {
|
||||
sandbox_policy_cwd: Path::new("/tmp"),
|
||||
command_cwd: Some(Path::new("/tmp/link")),
|
||||
sandbox_policy: &sandbox_policy,
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
|
||||
permission_profile: &permission_profile,
|
||||
allow_network_for_proxy: false,
|
||||
proxy_route_spec: None,
|
||||
command: vec!["/bin/true".to_string()],
|
||||
@@ -317,13 +318,11 @@ fn non_managed_inner_command_omits_route_spec() {
|
||||
#[test]
|
||||
fn managed_proxy_inner_command_requires_route_spec() {
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let permission_profile = read_only_permission_profile();
|
||||
build_inner_seccomp_command(InnerSeccompCommandArgs {
|
||||
sandbox_policy_cwd: Path::new("/tmp"),
|
||||
command_cwd: Some(Path::new("/tmp/link")),
|
||||
sandbox_policy: &sandbox_policy,
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
|
||||
permission_profile: &permission_profile,
|
||||
allow_network_for_proxy: true,
|
||||
proxy_route_spec: None,
|
||||
command: vec!["/bin/true".to_string()],
|
||||
@@ -333,89 +332,28 @@ fn managed_proxy_inner_command_requires_route_spec() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_sandbox_policies_derives_split_policies_from_legacy_policy() {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
fn resolve_permission_profile_derives_runtime_policies() {
|
||||
let permission_profile = read_only_permission_profile();
|
||||
let resolved = resolve_permission_profile(Some(permission_profile.clone()))
|
||||
.expect("profile should resolve");
|
||||
|
||||
let resolved = resolve_sandbox_policies(
|
||||
Path::new("/tmp"),
|
||||
Some(sandbox_policy.clone()),
|
||||
/*file_system_sandbox_policy*/ None,
|
||||
/*network_sandbox_policy*/ None,
|
||||
)
|
||||
.expect("legacy policy should resolve");
|
||||
|
||||
assert_eq!(resolved.sandbox_policy, sandbox_policy);
|
||||
assert_eq!(resolved.permission_profile, permission_profile);
|
||||
assert_eq!(
|
||||
resolved.file_system_sandbox_policy,
|
||||
FileSystemSandboxPolicy::from(&sandbox_policy)
|
||||
read_only_file_system_policy()
|
||||
);
|
||||
assert_eq!(
|
||||
resolved.network_sandbox_policy,
|
||||
NetworkSandboxPolicy::from(&sandbox_policy)
|
||||
NetworkSandboxPolicy::Restricted
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_sandbox_policies_derives_legacy_policy_from_split_policies() {
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::from(&sandbox_policy);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
|
||||
let resolved = resolve_sandbox_policies(
|
||||
Path::new("/tmp"),
|
||||
/*sandbox_policy*/ None,
|
||||
Some(file_system_sandbox_policy.clone()),
|
||||
Some(network_sandbox_policy),
|
||||
)
|
||||
.expect("split policies should resolve");
|
||||
|
||||
assert_eq!(resolved.sandbox_policy, sandbox_policy);
|
||||
assert_eq!(
|
||||
resolved.file_system_sandbox_policy,
|
||||
file_system_sandbox_policy
|
||||
);
|
||||
assert_eq!(resolved.network_sandbox_policy, network_sandbox_policy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_sandbox_policies_rejects_partial_split_policies() {
|
||||
let err = resolve_sandbox_policies(
|
||||
Path::new("/tmp"),
|
||||
Some(SandboxPolicy::new_read_only_policy()),
|
||||
Some(FileSystemSandboxPolicy::default()),
|
||||
/*network_sandbox_policy*/ None,
|
||||
)
|
||||
.expect_err("partial split policies should fail");
|
||||
|
||||
assert_eq!(err, ResolveSandboxPoliciesError::PartialSplitPolicies);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_sandbox_policies_rejects_mismatched_legacy_and_split_inputs() {
|
||||
let err = resolve_sandbox_policies(
|
||||
Path::new("/tmp"),
|
||||
Some(SandboxPolicy::new_read_only_policy()),
|
||||
Some(FileSystemSandboxPolicy::unrestricted()),
|
||||
Some(NetworkSandboxPolicy::Enabled),
|
||||
)
|
||||
.expect_err("mismatched legacy and split policies should fail");
|
||||
|
||||
assert!(
|
||||
matches!(
|
||||
err,
|
||||
ResolveSandboxPoliciesError::MismatchedLegacyPolicy { .. }
|
||||
),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_sandbox_policies_accepts_split_policies_requiring_direct_runtime_enforcement() {
|
||||
fn resolve_permission_profile_preserves_direct_runtime_profile() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
let docs = temp_dir.path().join("docs");
|
||||
std::fs::create_dir_all(&docs).expect("create docs");
|
||||
let docs = AbsolutePathBuf::from_absolute_path(&docs).expect("absolute docs");
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
@@ -428,16 +366,14 @@ fn resolve_sandbox_policies_accepts_split_policies_requiring_direct_runtime_enfo
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
let resolved = resolve_permission_profile(Some(permission_profile.clone()))
|
||||
.expect("profile should resolve");
|
||||
|
||||
let resolved = resolve_sandbox_policies(
|
||||
temp_dir.path(),
|
||||
Some(sandbox_policy.clone()),
|
||||
Some(file_system_sandbox_policy.clone()),
|
||||
Some(NetworkSandboxPolicy::Restricted),
|
||||
)
|
||||
.expect("split-only policy should preserve provided legacy fallback");
|
||||
|
||||
assert_eq!(resolved.sandbox_policy, sandbox_policy);
|
||||
assert_eq!(resolved.permission_profile, permission_profile);
|
||||
assert_eq!(
|
||||
resolved.file_system_sandbox_policy,
|
||||
file_system_sandbox_policy
|
||||
@@ -449,37 +385,11 @@ fn resolve_sandbox_policies_accepts_split_policies_requiring_direct_runtime_enfo
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_sandbox_policies_accepts_semantically_equivalent_workspace_write_inputs() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
let workspace = temp_dir.path().join("workspace");
|
||||
std::fs::create_dir_all(&workspace).expect("create workspace");
|
||||
let workspace = AbsolutePathBuf::from_absolute_path(&workspace).expect("absolute workspace");
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![workspace],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
};
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from(&SandboxPolicy::new_workspace_write_policy());
|
||||
fn resolve_permission_profile_rejects_missing_configuration() {
|
||||
let err = resolve_permission_profile(/*permission_profile*/ None)
|
||||
.expect_err("missing profile should fail");
|
||||
|
||||
let resolved = resolve_sandbox_policies(
|
||||
temp_dir.path().join("workspace").as_path(),
|
||||
Some(sandbox_policy.clone()),
|
||||
Some(file_system_sandbox_policy.clone()),
|
||||
Some(NetworkSandboxPolicy::Restricted),
|
||||
)
|
||||
.expect("semantically equivalent legacy workspace-write policy should resolve");
|
||||
|
||||
assert_eq!(resolved.sandbox_policy, sandbox_policy);
|
||||
assert_eq!(
|
||||
resolved.file_system_sandbox_policy,
|
||||
file_system_sandbox_policy
|
||||
);
|
||||
assert_eq!(
|
||||
resolved.network_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted
|
||||
);
|
||||
assert_eq!(err, ResolvePermissionProfileError::MissingConfiguration);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -11,14 +11,12 @@ use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
@@ -83,37 +81,32 @@ async fn run_cmd_result_with_writable_roots(
|
||||
use_legacy_landlock: bool,
|
||||
network_access: bool,
|
||||
) -> Result<codex_protocol::exec_output::ExecToolCallOutput> {
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: writable_roots
|
||||
.iter()
|
||||
.map(|p| AbsolutePathBuf::try_from(p.as_path()).unwrap())
|
||||
.collect(),
|
||||
network_access,
|
||||
let writable_roots = writable_roots
|
||||
.iter()
|
||||
.map(|path| AbsolutePathBuf::try_from(path.as_path()).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let permission_profile = PermissionProfile::workspace_write_with(
|
||||
&writable_roots,
|
||||
if network_access {
|
||||
NetworkSandboxPolicy::Enabled
|
||||
} else {
|
||||
NetworkSandboxPolicy::Restricted
|
||||
},
|
||||
// Exclude tmp-related folders from writable roots because we need a
|
||||
// folder that is writable by tests but that we intentionally disallow
|
||||
// writing to in the sandbox.
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::from(&sandbox_policy);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
run_cmd_result_with_policies(
|
||||
cmd,
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
timeout_ms,
|
||||
use_legacy_landlock,
|
||||
)
|
||||
.await
|
||||
/*exclude_tmpdir_env_var*/
|
||||
true,
|
||||
/*exclude_slash_tmp*/ true,
|
||||
);
|
||||
run_cmd_result_with_permission_profile(cmd, permission_profile, timeout_ms, use_legacy_landlock)
|
||||
.await
|
||||
}
|
||||
|
||||
#[expect(clippy::expect_used)]
|
||||
async fn run_cmd_result_with_policies(
|
||||
async fn run_cmd_result_with_permission_profile(
|
||||
cmd: &[&str],
|
||||
sandbox_policy: SandboxPolicy,
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
permission_profile: PermissionProfile,
|
||||
timeout_ms: u64,
|
||||
use_legacy_landlock: bool,
|
||||
) -> Result<codex_protocol::exec_output::ExecToolCallOutput> {
|
||||
@@ -134,11 +127,6 @@ async fn run_cmd_result_with_policies(
|
||||
};
|
||||
let sandbox_program = env!("CARGO_BIN_EXE_codex-linux-sandbox");
|
||||
let codex_linux_sandbox_exe = Some(PathBuf::from(sandbox_program));
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
|
||||
process_exec_tool_call(
|
||||
params,
|
||||
@@ -396,10 +384,9 @@ async fn assert_network_blocked(cmd: &[&str]) {
|
||||
arg0: None,
|
||||
};
|
||||
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let sandbox_program = env!("CARGO_BIN_EXE_codex-linux-sandbox");
|
||||
let codex_linux_sandbox_exe: Option<PathBuf> = Some(PathBuf::from(sandbox_program));
|
||||
let permission_profile = PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy);
|
||||
let permission_profile = PermissionProfile::read_only();
|
||||
let result = process_exec_tool_call(
|
||||
params,
|
||||
&permission_profile,
|
||||
@@ -561,12 +548,6 @@ async fn sandbox_blocks_explicit_split_policy_carveouts_under_bwrap() {
|
||||
.expect("sandbox helper should have a parent")
|
||||
.to_path_buf();
|
||||
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from(tmpdir.path()).expect("absolute tempdir")],
|
||||
network_access: true,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
@@ -594,16 +575,18 @@ async fn sandbox_blocks_explicit_split_policy_carveouts_under_bwrap() {
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
);
|
||||
let output = expect_denied(
|
||||
run_cmd_result_with_policies(
|
||||
run_cmd_result_with_permission_profile(
|
||||
&[
|
||||
"bash",
|
||||
"-lc",
|
||||
&format!("echo denied > {}", blocked_target.to_string_lossy()),
|
||||
],
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
permission_profile,
|
||||
LONG_TIMEOUT_MS,
|
||||
/*use_legacy_landlock*/ false,
|
||||
)
|
||||
@@ -633,12 +616,6 @@ async fn sandbox_reenables_writable_subpaths_under_unreadable_parents() {
|
||||
.expect("sandbox helper should have a parent")
|
||||
.to_path_buf();
|
||||
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from(tmpdir.path()).expect("absolute tempdir")],
|
||||
network_access: true,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
@@ -672,7 +649,11 @@ async fn sandbox_reenables_writable_subpaths_under_unreadable_parents() {
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
]);
|
||||
let output = run_cmd_result_with_policies(
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
);
|
||||
let output = run_cmd_result_with_permission_profile(
|
||||
&[
|
||||
"bash",
|
||||
"-lc",
|
||||
@@ -682,9 +663,7 @@ async fn sandbox_reenables_writable_subpaths_under_unreadable_parents() {
|
||||
allowed_target.to_string_lossy()
|
||||
),
|
||||
],
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
permission_profile,
|
||||
LONG_TIMEOUT_MS,
|
||||
/*use_legacy_landlock*/ false,
|
||||
)
|
||||
@@ -708,9 +687,6 @@ async fn sandbox_blocks_root_read_carveouts_under_bwrap() {
|
||||
let blocked_target = blocked.join("secret.txt");
|
||||
std::fs::write(&blocked_target, "secret").expect("seed blocked file");
|
||||
|
||||
let sandbox_policy = SandboxPolicy::ReadOnly {
|
||||
network_access: true,
|
||||
};
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
@@ -725,16 +701,18 @@ async fn sandbox_blocks_root_read_carveouts_under_bwrap() {
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
]);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
);
|
||||
let output = expect_denied(
|
||||
run_cmd_result_with_policies(
|
||||
run_cmd_result_with_permission_profile(
|
||||
&[
|
||||
"bash",
|
||||
"-lc",
|
||||
&format!("cat {}", blocked_target.to_string_lossy()),
|
||||
],
|
||||
sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
permission_profile,
|
||||
LONG_TIMEOUT_MS,
|
||||
/*use_legacy_landlock*/ false,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use codex_core::exec_env::create_env;
|
||||
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
@@ -65,7 +65,7 @@ async fn should_skip_bwrap_tests() -> bool {
|
||||
|
||||
let output = run_linux_sandbox_direct(
|
||||
&["bash", "-c", "true"],
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
&PermissionProfile::read_only(),
|
||||
/*allow_network_for_proxy*/ false,
|
||||
env,
|
||||
NETWORK_TIMEOUT_MS,
|
||||
@@ -91,7 +91,7 @@ async fn managed_proxy_skip_reason() -> Option<String> {
|
||||
|
||||
let output = run_linux_sandbox_direct(
|
||||
&["bash", "-c", "true"],
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
&PermissionProfile::Disabled,
|
||||
/*allow_network_for_proxy*/ true,
|
||||
env,
|
||||
NETWORK_TIMEOUT_MS,
|
||||
@@ -114,7 +114,7 @@ async fn managed_proxy_skip_reason() -> Option<String> {
|
||||
|
||||
async fn run_linux_sandbox_direct(
|
||||
command: &[&str],
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
permission_profile: &PermissionProfile,
|
||||
allow_network_for_proxy: bool,
|
||||
env: HashMap<String, String>,
|
||||
timeout_ms: u64,
|
||||
@@ -123,16 +123,16 @@ async fn run_linux_sandbox_direct(
|
||||
Ok(cwd) => cwd,
|
||||
Err(err) => panic!("cwd should exist: {err}"),
|
||||
};
|
||||
let policy_json = match serde_json::to_string(sandbox_policy) {
|
||||
Ok(policy_json) => policy_json,
|
||||
Err(err) => panic!("policy should serialize: {err}"),
|
||||
let permission_profile_json = match serde_json::to_string(permission_profile) {
|
||||
Ok(permission_profile_json) => permission_profile_json,
|
||||
Err(err) => panic!("permission profile should serialize: {err}"),
|
||||
};
|
||||
|
||||
let mut args = vec![
|
||||
"--sandbox-policy-cwd".to_string(),
|
||||
cwd.to_string_lossy().to_string(),
|
||||
"--sandbox-policy".to_string(),
|
||||
policy_json,
|
||||
"--permission-profile".to_string(),
|
||||
permission_profile_json,
|
||||
];
|
||||
if allow_network_for_proxy {
|
||||
args.push("--allow-network-for-proxy".to_string());
|
||||
@@ -170,7 +170,7 @@ async fn managed_proxy_mode_fails_closed_without_proxy_env() {
|
||||
|
||||
let output = run_linux_sandbox_direct(
|
||||
&["bash", "-c", "true"],
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
&PermissionProfile::Disabled,
|
||||
/*allow_network_for_proxy*/ true,
|
||||
env,
|
||||
NETWORK_TIMEOUT_MS,
|
||||
@@ -225,7 +225,7 @@ async fn managed_proxy_mode_routes_through_bridge_and_blocks_direct_egress() {
|
||||
"-c",
|
||||
"proxy=\"${HTTP_PROXY#*://}\"; host=\"${proxy%%:*}\"; port=\"${proxy##*:}\"; exec 3<>/dev/tcp/${host}/${port}; printf 'GET http://example.com/ HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n' >&3; IFS= read -r line <&3; printf '%s\\n' \"$line\"",
|
||||
],
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
&PermissionProfile::Disabled,
|
||||
/*allow_network_for_proxy*/ true,
|
||||
env.clone(),
|
||||
NETWORK_TIMEOUT_MS,
|
||||
@@ -256,7 +256,7 @@ async fn managed_proxy_mode_routes_through_bridge_and_blocks_direct_egress() {
|
||||
|
||||
let direct_egress_output = run_linux_sandbox_direct(
|
||||
&["bash", "-c", "echo hi > /dev/tcp/192.0.2.1/80"],
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
&PermissionProfile::Disabled,
|
||||
/*allow_network_for_proxy*/ true,
|
||||
env,
|
||||
NETWORK_TIMEOUT_MS,
|
||||
@@ -294,7 +294,7 @@ async fn managed_proxy_mode_denies_af_unix_creation_for_user_command() {
|
||||
"-c",
|
||||
"import socket,sys\ntry:\n socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\nexcept PermissionError:\n sys.exit(0)\nexcept OSError:\n sys.exit(2)\nsys.exit(1)\n",
|
||||
],
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
&PermissionProfile::Disabled,
|
||||
/*allow_network_for_proxy*/ true,
|
||||
env,
|
||||
NETWORK_TIMEOUT_MS,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use std::path::Path;
|
||||
|
||||
/// Basename used when the Codex executable self-invokes as the Linux sandbox
|
||||
@@ -14,30 +12,24 @@ pub fn allow_network_for_proxy(enforce_managed_network: bool) -> bool {
|
||||
enforce_managed_network
|
||||
}
|
||||
|
||||
/// Converts the sandbox policies into the CLI invocation for
|
||||
/// Converts the permission profile into the CLI invocation for
|
||||
/// `codex-linux-sandbox`.
|
||||
///
|
||||
/// The helper performs the actual sandboxing (bubblewrap by default + seccomp) after
|
||||
/// parsing these arguments. Policy JSON flags are emitted before helper feature
|
||||
/// flags so the argv order matches the helper's CLI shape. See
|
||||
/// The helper performs the actual sandboxing (bubblewrap by default + seccomp)
|
||||
/// after parsing these arguments. The profile JSON flag is emitted before
|
||||
/// helper feature flags so the argv order matches the helper's CLI shape. See
|
||||
/// `docs/linux_sandbox.md` for the Linux semantics.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_linux_sandbox_command_args_for_policies(
|
||||
pub fn create_linux_sandbox_command_args_for_permission_profile(
|
||||
command: Vec<String>,
|
||||
command_cwd: &Path,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
permission_profile: &PermissionProfile,
|
||||
sandbox_policy_cwd: &Path,
|
||||
use_legacy_landlock: bool,
|
||||
allow_network_for_proxy: bool,
|
||||
) -> Vec<String> {
|
||||
let sandbox_policy_json = serde_json::to_string(sandbox_policy)
|
||||
.unwrap_or_else(|err| panic!("failed to serialize sandbox policy: {err}"));
|
||||
let file_system_policy_json = serde_json::to_string(file_system_sandbox_policy)
|
||||
.unwrap_or_else(|err| panic!("failed to serialize filesystem sandbox policy: {err}"));
|
||||
let network_policy_json = serde_json::to_string(&network_sandbox_policy)
|
||||
.unwrap_or_else(|err| panic!("failed to serialize network sandbox policy: {err}"));
|
||||
let permission_profile_json = serde_json::to_string(permission_profile)
|
||||
.unwrap_or_else(|err| panic!("failed to serialize permission profile: {err}"));
|
||||
let sandbox_policy_cwd = sandbox_policy_cwd
|
||||
.to_str()
|
||||
.unwrap_or_else(|| panic!("cwd must be valid UTF-8"))
|
||||
@@ -52,12 +44,8 @@ pub fn create_linux_sandbox_command_args_for_policies(
|
||||
sandbox_policy_cwd,
|
||||
"--command-cwd".to_string(),
|
||||
command_cwd,
|
||||
"--sandbox-policy".to_string(),
|
||||
sandbox_policy_json,
|
||||
"--file-system-sandbox-policy".to_string(),
|
||||
file_system_policy_json,
|
||||
"--network-sandbox-policy".to_string(),
|
||||
network_policy_json,
|
||||
"--permission-profile".to_string(),
|
||||
permission_profile_json,
|
||||
];
|
||||
if use_legacy_landlock {
|
||||
linux_cmd.push("--use-legacy-landlock".to_string());
|
||||
|
||||
@@ -52,20 +52,16 @@ fn proxy_flag_is_included_when_requested() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_policy_flags_are_included() {
|
||||
fn permission_profile_flag_is_included() {
|
||||
let command = vec!["/bin/true".to_string()];
|
||||
let command_cwd = Path::new("/tmp/link");
|
||||
let cwd = Path::new("/tmp");
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::from(&sandbox_policy);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
let permission_profile = PermissionProfile::read_only();
|
||||
|
||||
let args = create_linux_sandbox_command_args_for_policies(
|
||||
let args = create_linux_sandbox_command_args_for_permission_profile(
|
||||
command,
|
||||
command_cwd,
|
||||
&sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
&permission_profile,
|
||||
cwd,
|
||||
/*use_legacy_landlock*/ true,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
@@ -73,12 +69,7 @@ fn split_policy_flags_are_included() {
|
||||
|
||||
assert_eq!(
|
||||
args.windows(2)
|
||||
.any(|window| { window[0] == "--file-system-sandbox-policy" && !window[1].is_empty() }),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
args.windows(2)
|
||||
.any(|window| window[0] == "--network-sandbox-policy" && window[1] == "\"restricted\""),
|
||||
.any(|window| { window[0] == "--permission-profile" && !window[1].is_empty() }),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::bwrap::WSL1_BWRAP_WARNING;
|
||||
use crate::bwrap::is_wsl1;
|
||||
use crate::landlock::CODEX_LINUX_SANDBOX_ARG0;
|
||||
use crate::landlock::allow_network_for_proxy;
|
||||
use crate::landlock::create_linux_sandbox_command_args_for_policies;
|
||||
use crate::landlock::create_linux_sandbox_command_args_for_permission_profile;
|
||||
use crate::policy_transforms::effective_permission_profile;
|
||||
use crate::policy_transforms::should_require_platform_sandbox;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
@@ -186,12 +186,6 @@ impl SandboxManager {
|
||||
effective_permission_profile(permissions, additional_permissions.as_ref());
|
||||
let (effective_file_system_policy, effective_network_policy) =
|
||||
effective_permission_profile.to_runtime_permissions();
|
||||
let effective_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&effective_permission_profile,
|
||||
&effective_file_system_policy,
|
||||
effective_network_policy,
|
||||
sandbox_policy_cwd,
|
||||
);
|
||||
let mut argv = Vec::with_capacity(1 + command.args.len());
|
||||
argv.push(command.program);
|
||||
argv.extend(command.args.into_iter().map(OsString::from));
|
||||
@@ -231,12 +225,10 @@ impl SandboxManager {
|
||||
allow_proxy_network,
|
||||
is_wsl1(),
|
||||
)?;
|
||||
let mut args = create_linux_sandbox_command_args_for_policies(
|
||||
let mut args = create_linux_sandbox_command_args_for_permission_profile(
|
||||
os_argv_to_strings(argv),
|
||||
command.cwd.as_path(),
|
||||
&effective_policy,
|
||||
&effective_file_system_policy,
|
||||
effective_network_policy,
|
||||
&effective_permission_profile,
|
||||
sandbox_policy_cwd,
|
||||
use_legacy_landlock,
|
||||
allow_proxy_network,
|
||||
|
||||
Reference in New Issue
Block a user