Support Unix socket allowlists in macOS sandbox#17654
Conversation
31ffeaf to
80202b0
Compare
671aa26 to
94088b2
Compare
| }; | ||
|
|
||
| let proxy = proxy_policy_inputs(network); | ||
| let proxy = proxy_policy_inputs(network).with_extra_unix_sockets(extra_allow_unix_sockets); |
There was a problem hiding this comment.
proxy_policy_inputs() has only one callsite. Why not update it to take extra_allow_unix_sockets so that we don't have to introduce with_extra_unix_sockets() which takes mut self?
| ) | ||
| } | ||
|
|
||
| pub fn create_seatbelt_command_args_for_policies_with_extra_unix_sockets( |
There was a problem hiding this comment.
Maybe this also merits using a builder pattern, but we have these three things calling each other right now, each adding one param:
create_seatbelt_command_args() ->
create_seatbelt_command_args_for_policies() -> create_seatbelt_command_args_for_policies_with_extra_unix_sockets()
This feels a bit much: can we just have one function that takes a struct with the full set of arguments?
If we feel strongly, we could make the struct impl Default so it's easier to elide the arguments with trivial default values when constructing it.
94088b2 to
4c1665c
Compare
| } | ||
|
|
||
| fn parse_absolute_path(raw: &str) -> Result<AbsolutePathBuf, String> { | ||
| AbsolutePathBuf::from_absolute_path_checked(raw) |
There was a problem hiding this comment.
| AbsolutePathBuf::from_absolute_path_checked(raw) | |
| AbsolutePathBuf::relative_to_current_dir(raw) |
4c1665c to
ba9b37f
Compare
ba9b37f to
e49ceb1
Compare
…unix-socket # Conflicts: # codex-rs/core/src/seatbelt.rs
|
|
||
| fn parse_allow_unix_socket_path(raw: &str) -> Result<AbsolutePathBuf, String> { | ||
| AbsolutePathBuf::relative_to_current_dir(raw) | ||
| .map_err(|err| format!("invalid path {raw:?}: {err}")) |
There was a problem hiding this comment.
Since raw is &str, you don't need :? to print the Debug version of the value.
| .map_err(|err| format!("invalid path {raw:?}: {err}")) | |
| .map_err(|err| format!("invalid path {raw}: {err}")) |
| @@ -12,7 +12,9 @@ use crate::policy_transforms::should_require_platform_sandbox; | |||
| #[cfg(target_os = "macos")] | |||
There was a problem hiding this comment.
Instead of these top-level imports with #[cfg(target_os = "macos")], you might want to just do the imports inside the SandboxType::MacosSeatbelt case and then you can drop the cfg on each import.
| let has_unix_socket_access = matches!( | ||
| proxy.unix_domain_socket_policy, | ||
| UnixDomainSocketPolicy::AllowAll | ||
| ) || matches!( | ||
| &proxy.unix_domain_socket_policy, | ||
| UnixDomainSocketPolicy::Restricted { allowed } if !allowed.is_empty() | ||
| ); |
There was a problem hiding this comment.
This is better because if a new variant is added to the UnixDomainSocketPolicy enum, this forces the match to be updated:
| let has_unix_socket_access = matches!( | |
| proxy.unix_domain_socket_policy, | |
| UnixDomainSocketPolicy::AllowAll | |
| ) || matches!( | |
| &proxy.unix_domain_socket_policy, | |
| UnixDomainSocketPolicy::Restricted { allowed } if !allowed.is_empty() | |
| ); | |
| let has_unix_socket_access = match proxy.unix_domain_socket_policy { | |
| UnixDomainSocketPolicy::AllowAll => true, | |
| UnixDomainSocketPolicy::Restricted { allowed } => !allowed.is_empty() | |
| }; |
There was a problem hiding this comment.
Maybe has_some_unix_socket_access is more accurate since it is true when Restricted is non-empty, but that is not quite a strong a statement as AllowAll?
| "(allow network-outbound)\n(allow network-inbound)\n{MACOS_SEATBELT_NETWORK_POLICY}" | ||
| ) | ||
| let mut policy = String::from("(allow network-outbound)\n(allow network-inbound)\n"); | ||
| let unix_socket_policy = unix_socket_policy(proxy); |
There was a problem hiding this comment.
So was it an oversight that the result of unix_socket_policy() was not included previously?
There was a problem hiding this comment.
yeah, I think so. I think it mostly wasn’t an issue before because the unix socket policy was exercised through the proxy/restricted-network path.
| network: Option<&NetworkProxy>, | ||
| ) -> Vec<String> { | ||
| #[derive(Debug)] | ||
| pub struct SeatbeltCommandArgs<'a> { |
There was a problem hiding this comment.
nit: I would name this SeatbeltCommandParams since create_seatbelt_command_args() takes this struct as an argument, it doesn't create it.
There was a problem hiding this comment.
Or even CreateSeatbeltCommandArgsParams because it's the "params" for the create_seatbelt_command_args() function?
|
|
||
| struct TestConfigReloader; | ||
|
|
||
| #[async_trait::async_trait] |
There was a problem hiding this comment.
We got burned by async_trait recently: #16630. Prefer native async traits, which effectively makes #[async_trait::async_trait] unnecessary.
There was a problem hiding this comment.
Oh, I see, we already have:
#[async_trait]
pub trait ConfigReloader: Send + Sync {
/// Human-readable description of where config is loaded from, for logs.
fn source_label(&self) -> String;
/// Return a freshly loaded state if a reload is needed; otherwise, return `None`.
async fn maybe_reload(&self) -> Result<Option<ConfigState>>;
/// Force a reload, regardless of whether a change was detected.
async fn reload_now(&self) -> Result<ConfigState>;
}OK, we can let this be for now, but I'll go after it in a follow-up...
Changes
Allows sandboxes to restrict overall network access while granting access to specific unix sockets on mac.
Details
codex sandbox macos: adds a repeatable--allow-unix-socketoption.codex-sandboxing: threads explicit Unix socket roots into the macOS Seatbelt profile generation.Verification
cargo test -p codex-cli -p codex-sandboxingcargo build -p codex-cli --bin codexcodex sandbox macos --allow-unix-socket /tmp/test.sock -- test-clientgrants access as expected