mirror of
https://github.com/openai/codex.git
synced 2026-11-03 00:21:31 +00:00
[feat] add seatbelt permission files (#11639)
Add seatbelt permission extension abstraction as permission files for seatbelt profiles. This should complement our current sandbox policy
This commit is contained in:
1 parent
76256a8cec
commit
dfd1e199a0
5 files changed
+393
-6
No files matched your search
@@ -14,6 +14,30 @@ When using the workspace-write sandbox policy, the Seatbelt profile allows
|
|||||||
writes under the configured writable roots while keeping `.git` (directory or
|
writes under the configured writable roots while keeping `.git` (directory or
|
||||||
pointer file), the resolved `gitdir:` target, and `.codex` read-only.
|
pointer file), the resolved `gitdir:` target, and `.codex` read-only.
|
||||||
|
|
||||||
|
Network access and filesystem read/write roots are controlled by
|
||||||
|
`SandboxPolicy`. Seatbelt consumes the resolved policy and enforces it.
|
||||||
|
|
||||||
|
Seatbelt also supports macOS permission-profile extensions layered on top of
|
||||||
|
`SandboxPolicy`:
|
||||||
|
|
||||||
|
- no extension profile provided:
|
||||||
|
keeps legacy default preferences read access (`user-preference-read`).
|
||||||
|
- extension profile provided with no `macos_preferences` grant:
|
||||||
|
does not add preferences access clauses.
|
||||||
|
- `macos_preferences = "readonly"`:
|
||||||
|
enables cfprefs read clauses and `user-preference-read`.
|
||||||
|
- `macos_preferences = "readwrite"`:
|
||||||
|
includes readonly clauses plus `user-preference-write` and cfprefs shm write
|
||||||
|
clauses.
|
||||||
|
- `macos_automation = true`:
|
||||||
|
enables broad Apple Events send permissions.
|
||||||
|
- `macos_automation = ["com.apple.Notes", ...]`:
|
||||||
|
enables Apple Events send only to listed bundle IDs.
|
||||||
|
- `macos_accessibility = true`:
|
||||||
|
enables `com.apple.axserver` mach lookup.
|
||||||
|
- `macos_calendar = true`:
|
||||||
|
enables `com.apple.CalendarAgent` mach lookup.
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
Expects the binary containing `codex-core` to run the equivalent of `codex sandbox linux` (legacy alias: `codex debug landlock`) when `arg0` is `codex-linux-sandbox`. See the `codex-arg0` crate for details.
|
Expects the binary containing `codex-core` to run the equivalent of `codex sandbox linux` (legacy alias: `codex debug landlock`) when `arg0` is `codex-linux-sandbox`. See the `codex-arg0` crate for details.
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ pub use model_provider_info::create_oss_provider_with_base_url;
|
|||||||
mod event_mapping;
|
mod event_mapping;
|
||||||
pub mod review_format;
|
pub mod review_format;
|
||||||
pub mod review_prompts;
|
pub mod review_prompts;
|
||||||
|
mod seatbelt_permissions;
|
||||||
mod thread_manager;
|
mod thread_manager;
|
||||||
pub mod web_search;
|
pub mod web_search;
|
||||||
pub mod windows_sandbox_read_grants;
|
pub mod windows_sandbox_read_grants;
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ use tokio::process::Child;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::protocol::SandboxPolicy;
|
use crate::protocol::SandboxPolicy;
|
||||||
|
use crate::seatbelt_permissions::MacOsPreferencesPermission;
|
||||||
|
use crate::seatbelt_permissions::MacOsSeatbeltProfileExtensions;
|
||||||
|
use crate::seatbelt_permissions::build_seatbelt_extensions;
|
||||||
use crate::spawn::CODEX_SANDBOX_ENV_VAR;
|
use crate::spawn::CODEX_SANDBOX_ENV_VAR;
|
||||||
use crate::spawn::SpawnChildRequest;
|
use crate::spawn::SpawnChildRequest;
|
||||||
use crate::spawn::StdioPolicy;
|
use crate::spawn::StdioPolicy;
|
||||||
@@ -179,6 +182,24 @@ pub(crate) fn create_seatbelt_command_args(
|
|||||||
sandbox_policy_cwd: &Path,
|
sandbox_policy_cwd: &Path,
|
||||||
enforce_managed_network: bool,
|
enforce_managed_network: bool,
|
||||||
network: Option<&NetworkProxy>,
|
network: Option<&NetworkProxy>,
|
||||||
|
) -> Vec<String> {
|
||||||
|
create_seatbelt_command_args_with_extensions(
|
||||||
|
command,
|
||||||
|
sandbox_policy,
|
||||||
|
sandbox_policy_cwd,
|
||||||
|
enforce_managed_network,
|
||||||
|
network,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_seatbelt_command_args_with_extensions(
|
||||||
|
command: Vec<String>,
|
||||||
|
sandbox_policy: &SandboxPolicy,
|
||||||
|
sandbox_policy_cwd: &Path,
|
||||||
|
enforce_managed_network: bool,
|
||||||
|
network: Option<&NetworkProxy>,
|
||||||
|
extensions: Option<&MacOsSeatbeltProfileExtensions>,
|
||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
let (file_write_policy, file_write_dir_params) = {
|
let (file_write_policy, file_write_dir_params) = {
|
||||||
if sandbox_policy.has_full_disk_write_access() {
|
if sandbox_policy.has_full_disk_write_access() {
|
||||||
@@ -275,15 +296,33 @@ pub(crate) fn create_seatbelt_command_args(
|
|||||||
|
|
||||||
let proxy = proxy_policy_inputs(network);
|
let proxy = proxy_policy_inputs(network);
|
||||||
let network_policy = dynamic_network_policy(sandbox_policy, enforce_managed_network, &proxy);
|
let network_policy = dynamic_network_policy(sandbox_policy, enforce_managed_network, &proxy);
|
||||||
|
let seatbelt_extensions = extensions.map_or_else(
|
||||||
let full_policy = format!(
|
|| {
|
||||||
"{MACOS_SEATBELT_BASE_POLICY}\n{file_read_policy}\n{file_write_policy}\n{network_policy}"
|
// Backward-compatibility default when no extension profile is provided.
|
||||||
|
build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions {
|
||||||
|
macos_preferences: MacOsPreferencesPermission::ReadOnly,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
build_seatbelt_extensions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let full_policy = if seatbelt_extensions.policy.is_empty() {
|
||||||
|
format!(
|
||||||
|
"{MACOS_SEATBELT_BASE_POLICY}\n{file_read_policy}\n{file_write_policy}\n{network_policy}"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{MACOS_SEATBELT_BASE_POLICY}\n{file_read_policy}\n{file_write_policy}\n{network_policy}\n{}",
|
||||||
|
seatbelt_extensions.policy
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let dir_params = [
|
let dir_params = [
|
||||||
file_read_dir_params,
|
file_read_dir_params,
|
||||||
file_write_dir_params,
|
file_write_dir_params,
|
||||||
macos_dir_params(),
|
macos_dir_params(),
|
||||||
|
seatbelt_extensions.dir_params,
|
||||||
]
|
]
|
||||||
.concat();
|
.concat();
|
||||||
|
|
||||||
@@ -328,10 +367,14 @@ mod tests {
|
|||||||
use super::MACOS_SEATBELT_BASE_POLICY;
|
use super::MACOS_SEATBELT_BASE_POLICY;
|
||||||
use super::ProxyPolicyInputs;
|
use super::ProxyPolicyInputs;
|
||||||
use super::create_seatbelt_command_args;
|
use super::create_seatbelt_command_args;
|
||||||
|
use super::create_seatbelt_command_args_with_extensions;
|
||||||
use super::dynamic_network_policy;
|
use super::dynamic_network_policy;
|
||||||
use super::macos_dir_params;
|
use super::macos_dir_params;
|
||||||
use crate::protocol::SandboxPolicy;
|
use crate::protocol::SandboxPolicy;
|
||||||
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
|
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
|
||||||
|
use crate::seatbelt_permissions::MacOsAutomationPermission;
|
||||||
|
use crate::seatbelt_permissions::MacOsPreferencesPermission;
|
||||||
|
use crate::seatbelt_permissions::MacOsSeatbeltProfileExtensions;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -395,6 +438,66 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seatbelt_args_include_macos_permission_extensions() {
|
||||||
|
let cwd = std::env::temp_dir();
|
||||||
|
let args = create_seatbelt_command_args_with_extensions(
|
||||||
|
vec!["echo".to_string(), "ok".to_string()],
|
||||||
|
&SandboxPolicy::new_read_only_policy(),
|
||||||
|
cwd.as_path(),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
Some(&MacOsSeatbeltProfileExtensions {
|
||||||
|
macos_preferences: MacOsPreferencesPermission::ReadWrite,
|
||||||
|
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||||
|
"com.apple.Notes".to_string(),
|
||||||
|
]),
|
||||||
|
macos_accessibility: true,
|
||||||
|
macos_calendar: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let policy = &args[1];
|
||||||
|
|
||||||
|
assert!(policy.contains("(allow user-preference-write)"));
|
||||||
|
assert!(policy.contains("(appleevent-destination \"com.apple.Notes\")"));
|
||||||
|
assert!(policy.contains("com.apple.axserver"));
|
||||||
|
assert!(policy.contains("com.apple.CalendarAgent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seatbelt_args_without_extension_profile_keep_legacy_preferences_read_access() {
|
||||||
|
let cwd = std::env::temp_dir();
|
||||||
|
let args = create_seatbelt_command_args(
|
||||||
|
vec!["echo".to_string(), "ok".to_string()],
|
||||||
|
&SandboxPolicy::new_read_only_policy(),
|
||||||
|
cwd.as_path(),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let policy = &args[1];
|
||||||
|
assert!(policy.contains("(allow user-preference-read)"));
|
||||||
|
assert!(!policy.contains("(allow user-preference-write)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seatbelt_args_omit_macos_extensions_when_profile_is_empty() {
|
||||||
|
let cwd = std::env::temp_dir();
|
||||||
|
let args = create_seatbelt_command_args_with_extensions(
|
||||||
|
vec!["echo".to_string(), "ok".to_string()],
|
||||||
|
&SandboxPolicy::new_read_only_policy(),
|
||||||
|
cwd.as_path(),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
Some(&MacOsSeatbeltProfileExtensions::default()),
|
||||||
|
);
|
||||||
|
let policy = &args[1];
|
||||||
|
assert!(!policy.contains("appleevent-send"));
|
||||||
|
assert!(!policy.contains("com.apple.axserver"));
|
||||||
|
assert!(!policy.contains("com.apple.CalendarAgent"));
|
||||||
|
assert!(!policy.contains("user-preference-read"));
|
||||||
|
assert!(!policy.contains("user-preference-write"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_seatbelt_args_allows_local_binding_when_explicitly_enabled() {
|
fn create_seatbelt_args_allows_local_binding_when_explicitly_enabled() {
|
||||||
let policy = dynamic_network_policy(
|
let policy = dynamic_network_policy(
|
||||||
@@ -564,6 +667,14 @@ mod tests {
|
|||||||
(allow file-write*
|
(allow file-write*
|
||||||
(require-all (subpath (param "WRITABLE_ROOT_0")) (require-not (subpath (param "WRITABLE_ROOT_0_RO_0"))) (require-not (subpath (param "WRITABLE_ROOT_0_RO_1"))) ) (subpath (param "WRITABLE_ROOT_1")) (subpath (param "WRITABLE_ROOT_2"))
|
(require-all (subpath (param "WRITABLE_ROOT_0")) (require-not (subpath (param "WRITABLE_ROOT_0_RO_0"))) (require-not (subpath (param "WRITABLE_ROOT_0_RO_1"))) ) (subpath (param "WRITABLE_ROOT_1")) (subpath (param "WRITABLE_ROOT_2"))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
; macOS permission profile extensions
|
||||||
|
(allow ipc-posix-shm-read* (ipc-posix-name-prefix "apple.cfprefs."))
|
||||||
|
(allow mach-lookup
|
||||||
|
(global-name "com.apple.cfprefsd.daemon")
|
||||||
|
(global-name "com.apple.cfprefsd.agent")
|
||||||
|
(local-name "com.apple.cfprefsd.agent"))
|
||||||
|
(allow user-preference-read)
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -851,6 +962,14 @@ mod tests {
|
|||||||
(allow file-write*
|
(allow file-write*
|
||||||
(require-all (subpath (param "WRITABLE_ROOT_0")) (require-not (subpath (param "WRITABLE_ROOT_0_RO_0"))) (require-not (subpath (param "WRITABLE_ROOT_0_RO_1"))) ) (subpath (param "WRITABLE_ROOT_1")){tempdir_policy_entry}
|
(require-all (subpath (param "WRITABLE_ROOT_0")) (require-not (subpath (param "WRITABLE_ROOT_0_RO_0"))) (require-not (subpath (param "WRITABLE_ROOT_0_RO_1"))) ) (subpath (param "WRITABLE_ROOT_1")){tempdir_policy_entry}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
; macOS permission profile extensions
|
||||||
|
(allow ipc-posix-shm-read* (ipc-posix-name-prefix "apple.cfprefs."))
|
||||||
|
(allow mach-lookup
|
||||||
|
(global-name "com.apple.cfprefsd.daemon")
|
||||||
|
(global-name "com.apple.cfprefsd.agent")
|
||||||
|
(local-name "com.apple.cfprefsd.agent"))
|
||||||
|
(allow user-preference-read)
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,6 @@
|
|||||||
(allow process-fork)
|
(allow process-fork)
|
||||||
(allow signal (target same-sandbox))
|
(allow signal (target same-sandbox))
|
||||||
|
|
||||||
; Allow cf prefs to work.
|
|
||||||
(allow user-preference-read)
|
|
||||||
|
|
||||||
; process-info
|
; process-info
|
||||||
(allow process-info* (target same-sandbox))
|
(allow process-info* (target same-sandbox))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
#![cfg(target_os = "macos")]
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub enum MacOsPreferencesPermission {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
ReadOnly,
|
||||||
|
ReadWrite,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub enum MacOsAutomationPermission {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
All,
|
||||||
|
BundleIds(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub struct MacOsSeatbeltProfileExtensions {
|
||||||
|
pub macos_preferences: MacOsPreferencesPermission,
|
||||||
|
pub macos_automation: MacOsAutomationPermission,
|
||||||
|
pub macos_accessibility: bool,
|
||||||
|
pub macos_calendar: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub(crate) struct SeatbeltExtensionPolicy {
|
||||||
|
pub(crate) policy: String,
|
||||||
|
pub(crate) dir_params: Vec<(String, PathBuf)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MacOsSeatbeltProfileExtensions {
|
||||||
|
pub fn normalized(&self) -> Self {
|
||||||
|
let mut normalized = self.clone();
|
||||||
|
if let MacOsAutomationPermission::BundleIds(bundle_ids) = &self.macos_automation {
|
||||||
|
let bundle_ids = normalize_bundle_ids(bundle_ids);
|
||||||
|
normalized.macos_automation = if bundle_ids.is_empty() {
|
||||||
|
MacOsAutomationPermission::None
|
||||||
|
} else {
|
||||||
|
MacOsAutomationPermission::BundleIds(bundle_ids)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
normalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_seatbelt_extensions(
|
||||||
|
extensions: &MacOsSeatbeltProfileExtensions,
|
||||||
|
) -> SeatbeltExtensionPolicy {
|
||||||
|
let extensions = extensions.normalized();
|
||||||
|
let mut clauses = Vec::new();
|
||||||
|
|
||||||
|
match extensions.macos_preferences {
|
||||||
|
MacOsPreferencesPermission::None => {}
|
||||||
|
MacOsPreferencesPermission::ReadOnly => {
|
||||||
|
clauses.push(
|
||||||
|
"(allow ipc-posix-shm-read* (ipc-posix-name-prefix \"apple.cfprefs.\"))"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
clauses.push(
|
||||||
|
"(allow mach-lookup\n (global-name \"com.apple.cfprefsd.daemon\")\n (global-name \"com.apple.cfprefsd.agent\")\n (local-name \"com.apple.cfprefsd.agent\"))"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
clauses.push("(allow user-preference-read)".to_string());
|
||||||
|
}
|
||||||
|
MacOsPreferencesPermission::ReadWrite => {
|
||||||
|
clauses.push(
|
||||||
|
"(allow ipc-posix-shm-read* (ipc-posix-name-prefix \"apple.cfprefs.\"))"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
clauses.push(
|
||||||
|
"(allow mach-lookup\n (global-name \"com.apple.cfprefsd.daemon\")\n (global-name \"com.apple.cfprefsd.agent\")\n (local-name \"com.apple.cfprefsd.agent\"))"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
clauses.push("(allow user-preference-read)".to_string());
|
||||||
|
clauses.push("(allow user-preference-write)".to_string());
|
||||||
|
clauses.push(
|
||||||
|
"(allow ipc-posix-shm-write-data (ipc-posix-name-prefix \"apple.cfprefs.\"))"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
clauses.push(
|
||||||
|
"(allow ipc-posix-shm-write-create (ipc-posix-name-prefix \"apple.cfprefs.\"))"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match extensions.macos_automation {
|
||||||
|
MacOsAutomationPermission::None => {}
|
||||||
|
MacOsAutomationPermission::All => {
|
||||||
|
clauses.push(
|
||||||
|
"(allow mach-lookup\n (global-name \"com.apple.coreservices.launchservicesd\")\n (global-name \"com.apple.coreservices.appleevents\"))"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
clauses.push("(allow appleevent-send)".to_string());
|
||||||
|
}
|
||||||
|
MacOsAutomationPermission::BundleIds(bundle_ids) => {
|
||||||
|
if !bundle_ids.is_empty() {
|
||||||
|
clauses.push(
|
||||||
|
"(allow mach-lookup (global-name \"com.apple.coreservices.appleevents\"))"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
let destinations = bundle_ids
|
||||||
|
.iter()
|
||||||
|
.map(|bundle_id| format!(" (appleevent-destination \"{bundle_id}\")"))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
clauses.push(format!("(allow appleevent-send\n{destinations}\n)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if extensions.macos_accessibility {
|
||||||
|
clauses.push("(allow mach-lookup (local-name \"com.apple.axserver\"))".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if extensions.macos_calendar {
|
||||||
|
clauses.push("(allow mach-lookup (global-name \"com.apple.CalendarAgent\"))".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if clauses.is_empty() {
|
||||||
|
SeatbeltExtensionPolicy::default()
|
||||||
|
} else {
|
||||||
|
SeatbeltExtensionPolicy {
|
||||||
|
policy: format!(
|
||||||
|
"; macOS permission profile extensions\n{}\n",
|
||||||
|
clauses.join("\n")
|
||||||
|
),
|
||||||
|
dir_params: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_bundle_ids(bundle_ids: &[String]) -> Vec<String> {
|
||||||
|
let mut unique = BTreeSet::new();
|
||||||
|
for bundle_id in bundle_ids {
|
||||||
|
let candidate = bundle_id.trim();
|
||||||
|
if is_valid_bundle_id(candidate) {
|
||||||
|
unique.insert(candidate.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unique.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_bundle_id(bundle_id: &str) -> bool {
|
||||||
|
if bundle_id.len() < 3 || !bundle_id.contains('.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bundle_id
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::MacOsAutomationPermission;
|
||||||
|
use super::MacOsPreferencesPermission;
|
||||||
|
use super::MacOsSeatbeltProfileExtensions;
|
||||||
|
use super::build_seatbelt_extensions;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preferences_read_only_emits_read_clauses_only() {
|
||||||
|
let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions {
|
||||||
|
macos_preferences: MacOsPreferencesPermission::ReadOnly,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
assert!(policy.policy.contains("(allow user-preference-read)"));
|
||||||
|
assert!(!policy.policy.contains("(allow user-preference-write)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preferences_read_write_emits_write_clauses() {
|
||||||
|
let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions {
|
||||||
|
macos_preferences: MacOsPreferencesPermission::ReadWrite,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
assert!(policy.policy.contains("(allow user-preference-read)"));
|
||||||
|
assert!(policy.policy.contains("(allow user-preference-write)"));
|
||||||
|
assert!(policy.policy.contains(
|
||||||
|
"(allow ipc-posix-shm-write-create (ipc-posix-name-prefix \"apple.cfprefs.\"))"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn automation_all_emits_unscoped_appleevents() {
|
||||||
|
let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions {
|
||||||
|
macos_automation: MacOsAutomationPermission::All,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
assert!(policy.policy.contains("(allow appleevent-send)"));
|
||||||
|
assert!(
|
||||||
|
policy
|
||||||
|
.policy
|
||||||
|
.contains("com.apple.coreservices.launchservicesd")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn automation_bundle_ids_are_normalized_and_scoped() {
|
||||||
|
let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions {
|
||||||
|
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||||
|
" com.apple.Notes ".to_string(),
|
||||||
|
"com.apple.Calendar".to_string(),
|
||||||
|
"bad bundle".to_string(),
|
||||||
|
"com.apple.Notes".to_string(),
|
||||||
|
]),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
assert!(
|
||||||
|
policy
|
||||||
|
.policy
|
||||||
|
.contains("(appleevent-destination \"com.apple.Calendar\")")
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
policy
|
||||||
|
.policy
|
||||||
|
.contains("(appleevent-destination \"com.apple.Notes\")")
|
||||||
|
);
|
||||||
|
assert!(!policy.policy.contains("bad bundle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accessibility_and_calendar_emit_mach_lookups() {
|
||||||
|
let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions {
|
||||||
|
macos_accessibility: true,
|
||||||
|
macos_calendar: true,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
assert!(policy.policy.contains("com.apple.axserver"));
|
||||||
|
assert!(policy.policy.contains("com.apple.CalendarAgent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_extensions_emit_empty_policy() {
|
||||||
|
let policy = build_seatbelt_extensions(&MacOsSeatbeltProfileExtensions::default());
|
||||||
|
assert_eq!(policy.policy, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user