Use PathUri in filesystem permission paths for exec-server (#28165)

## Why

Progress towards letting app-server and exec-server run on different
platforms, specifically for sandbox configuration.

## What

- Make the filesystem path containment hierarchy generic, defaulting to
`AbsolutePathBuf` for now.
- Have clients specify `AbsolutePathBuf` or `PathUri` directly where
needed.
- Use `PathUri` throughout exec-server filesystem protocol and trait
boundaries.
- Implement `From` for conversion to path URIs and `TryFrom` for
fallible conversion to absolute paths through the generic type
hierarchy.
This commit is contained in:
Adam Perry @ OpenAI
2026-06-15 16:55:23 -07:00
committed by GitHub
Unverified
parent 428cd44154
commit 46f17930b6
17 changed files with 320 additions and 97 deletions
+2
View File
@@ -2626,6 +2626,7 @@ dependencies = [
"codex-extension-api",
"codex-features",
"codex-feedback",
"codex-file-system",
"codex-git-utils",
"codex-home",
"codex-hooks",
@@ -3039,6 +3040,7 @@ name = "codex-file-system"
version = "0.0.0"
dependencies = [
"codex-protocol",
"codex-utils-absolute-path",
"codex-utils-path-uri",
"serde",
]
+1
View File
@@ -38,6 +38,7 @@ codex-exec-server = { workspace = true }
codex-extension-api = { workspace = true }
codex-features = { workspace = true }
codex-feedback = { workspace = true }
codex-file-system = { workspace = true }
codex-login = { workspace = true }
codex-memories-read = { workspace = true }
codex-mcp = { workspace = true }
-1
View File
@@ -54,7 +54,6 @@ use codex_config::types::AuthKeyringBackendKind;
use codex_config::types::OAuthCredentialsStoreMode;
use codex_exec_server::Environment;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::FileSystemSandboxContext;
use codex_extension_api::ExtensionDataInit;
use codex_extension_api::LoadedUserInstructions;
use codex_extension_api::PromptSlot;
+2 -1
View File
@@ -4,6 +4,7 @@ use crate::agents_md::LoadedAgentsMd;
use crate::config::GhostSnapshotConfig;
use crate::environment_selection::TurnEnvironmentSnapshot;
use codex_core_skills::HostLoadedSkills;
use codex_file_system::FileSystemSandboxContext;
use codex_model_provider::SharedModelProvider;
use codex_model_provider::create_model_provider;
use codex_protocol::SessionId;
@@ -341,7 +342,7 @@ impl TurnContext {
network_sandbox_policy,
);
FileSystemSandboxContext {
permissions,
permissions: permissions.into(),
cwd: Some(cwd.clone()),
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self
@@ -97,7 +97,7 @@ impl ApplyPatchRuntime {
let permissions =
effective_permission_profile(attempt.permissions, req.additional_permissions.as_ref());
Some(FileSystemSandboxContext {
permissions,
permissions: permissions.into(),
cwd: Some(attempt.sandbox_cwd.clone()),
windows_sandbox_level: attempt.windows_sandbox_level,
windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop,
@@ -233,7 +233,12 @@ async fn file_system_sandbox_context_uses_active_attempt() {
);
let expected_permissions =
PermissionProfile::from_runtime_permissions(&file_system_policy, network_policy);
assert_eq!(sandbox.permissions, expected_permissions);
let native_permissions: PermissionProfile = sandbox
.permissions
.clone()
.try_into()
.expect("native sandbox permissions");
assert_eq!(native_permissions, expected_permissions);
assert_eq!(
sandbox.cwd,
Some(codex_utils_path_uri::PathUri::from_abs_path(&path))
+8 -4
View File
@@ -66,7 +66,11 @@ impl FileSystemSandboxRunner {
request: FsHelperRequest,
) -> Result<FsHelperPayload, JSONRPCErrorError> {
let cwd = sandbox_cwd(sandbox)?;
let mut file_system_policy = sandbox.permissions.file_system_sandbox_policy();
let native_permissions: PermissionProfile =
sandbox.permissions.clone().try_into().map_err(|err| {
invalid_request(format!("invalid sandbox permission path URI: {err}"))
})?;
let mut file_system_policy = native_permissions.file_system_sandbox_policy();
let helper_read_roots = if sandbox.use_legacy_landlock {
Vec::new()
} else {
@@ -80,7 +84,7 @@ impl FileSystemSandboxRunner {
normalize_file_system_policy_root_aliases(&mut file_system_policy);
let network_policy = NetworkSandboxPolicy::Restricted;
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
sandbox.permissions.enforcement(),
native_permissions.enforcement(),
&file_system_policy,
network_policy,
);
@@ -578,7 +582,7 @@ mod tests {
},
access: FileSystemAccessMode::Write,
}]);
let sandbox_context = crate::FileSystemSandboxContext::from_permission_profile(
let sandbox_context = codex_file_system::FileSystemSandboxContext::from_permission_profile(
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
);
@@ -650,7 +654,7 @@ mod tests {
policy: &FileSystemSandboxPolicy,
cwd: PathUri,
) -> crate::FileSystemSandboxContext {
crate::FileSystemSandboxContext::from_permission_profile_with_cwd(
codex_file_system::FileSystemSandboxContext::from_permission_profile_with_cwd(
PermissionProfile::from_runtime_permissions(policy, NetworkSandboxPolicy::Restricted),
cwd,
)
+5 -4
View File
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use crate::FileSystemSandboxContext;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use codex_file_system::FileSystemSandboxContext;
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
use codex_utils_path_uri::PathUri;
use serde::Deserialize;
@@ -454,7 +454,7 @@ mod base64_bytes {
mod tests {
use super::FsReadFileParams;
use super::HttpRequestParams;
use crate::FileSystemSandboxContext;
use codex_file_system::FileSystemSandboxContext;
use codex_protocol::models::PermissionProfile;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
@@ -465,18 +465,19 @@ mod tests {
.expect("current directory")
.join("legacy-file.txt");
let legacy_cwd = std::env::current_dir().expect("current directory");
let expected_sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
let native_sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
PermissionProfile::default(),
PathUri::from_path(&legacy_cwd).expect("cwd URI"),
);
let mut legacy_sandbox =
serde_json::to_value(&expected_sandbox).expect("sandbox should serialize");
serde_json::to_value(&native_sandbox).expect("sandbox should serialize");
legacy_sandbox["cwd"] = serde_json::json!(legacy_cwd.to_string_lossy());
let params: FsReadFileParams = serde_json::from_value(serde_json::json!({
"path": legacy_path.to_string_lossy(),
"sandbox": legacy_sandbox,
}))
.expect("legacy absolute path should deserialize");
let expected_sandbox = native_sandbox;
let expected = FsReadFileParams {
path: PathUri::from_path(legacy_path).expect("path URI"),
sandbox: Some(expected_sandbox.clone()),
@@ -30,7 +30,8 @@ fn sandbox_context_from_profile_preserves_workspace_write_read_only_subpaths() -
std::fs::create_dir_all(&git_dir)?;
let sandbox = workspace_write_sandbox(writable_dir.clone());
let policy = sandbox.permissions.file_system_sandbox_policy();
let permissions: PermissionProfile = sandbox.permissions.try_into()?;
let policy = permissions.file_system_sandbox_policy();
let cwd = absolute_path(writable_dir.clone());
let writable_roots = policy.get_writable_roots_with_cwd(cwd.as_path());
let writable_dir = absolute_path(std::fs::canonicalize(writable_dir)?);
@@ -534,19 +535,21 @@ async fn file_system_sandboxed_write_allows_additional_write_root(
Some(vec![absolute_path(writable_dir)]),
)),
};
let native_permissions: PermissionProfile = sandbox.permissions.clone().try_into()?;
let file_system_policy = effective_file_system_sandbox_policy(
&sandbox.permissions.file_system_sandbox_policy(),
&native_permissions.file_system_sandbox_policy(),
Some(&additional_permissions),
);
let network_policy = effective_network_sandbox_policy(
sandbox.permissions.network_sandbox_policy(),
native_permissions.network_sandbox_policy(),
Some(&additional_permissions),
);
sandbox.permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
sandbox.permissions.enforcement(),
native_permissions.enforcement(),
&file_system_policy,
network_policy,
);
)
.into();
file_system
.write_file(
+2 -1
View File
@@ -342,9 +342,10 @@ async fn image_url(
environment: &ToolEnvironment,
) -> Result<ImageUrl, FunctionCallError> {
let path_uri = PathUri::from_abs_path(path);
let sandbox = environment.file_system_sandbox_context.clone();
let bytes = environment
.file_system
.read_file(&path_uri, Some(&environment.file_system_sandbox_context))
.read_file(&path_uri, Some(&sandbox))
.await
.map_err(|error| {
FunctionCallError::RespondToModel(format!(
+1
View File
@@ -9,6 +9,7 @@ workspace = true
[dependencies]
codex-protocol = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-path-uri = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+44 -28
View File
@@ -1,4 +1,5 @@
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::ManagedFileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement;
use codex_protocol::permissions::FileSystemPath;
@@ -7,6 +8,7 @@ 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 codex_utils_path_uri::PathUri;
use std::future::Future;
use std::io;
@@ -50,7 +52,7 @@ pub struct ReadDirectoryEntry {
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileSystemSandboxContext {
pub permissions: PermissionProfile,
pub permissions: PermissionProfile<PathUri>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathUri>,
pub windows_sandbox_level: WindowsSandboxLevel,
@@ -73,25 +75,32 @@ impl FileSystemSandboxContext {
&sandbox_policy,
&native_cwd,
);
let permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(&sandbox_policy),
);
let permissions =
PermissionProfile::<AbsolutePathBuf>::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(&sandbox_policy),
);
Ok(Self::from_permission_profile_with_cwd(permissions, cwd))
}
pub fn from_permission_profile(permissions: PermissionProfile) -> Self {
pub fn from_permission_profile(permissions: PermissionProfile<AbsolutePathBuf>) -> Self {
Self::from_permissions_and_cwd(permissions, /*cwd*/ None)
}
pub fn from_permission_profile_with_cwd(permissions: PermissionProfile, cwd: PathUri) -> Self {
pub fn from_permission_profile_with_cwd(
permissions: PermissionProfile<AbsolutePathBuf>,
cwd: PathUri,
) -> Self {
Self::from_permissions_and_cwd(permissions, Some(cwd))
}
fn from_permissions_and_cwd(permissions: PermissionProfile, cwd: Option<PathUri>) -> Self {
fn from_permissions_and_cwd(
permissions: PermissionProfile<AbsolutePathBuf>,
cwd: Option<PathUri>,
) -> Self {
Self {
permissions,
permissions: permissions.into(),
cwd,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
@@ -100,14 +109,36 @@ impl FileSystemSandboxContext {
}
pub fn should_run_in_sandbox(&self) -> bool {
let file_system_policy = self.permissions.file_system_sandbox_policy();
let Ok(permissions) =
PermissionProfile::<AbsolutePathBuf>::try_from(self.permissions.clone())
else {
// A sandbox context for another host must not select the unsandboxed filesystem.
return true;
};
let file_system_policy = permissions.file_system_sandbox_policy();
matches!(file_system_policy.kind, FileSystemSandboxKind::Restricted)
&& !file_system_policy.has_full_disk_write_access()
}
pub fn has_cwd_dependent_permissions(&self) -> bool {
let file_system_policy = self.permissions.file_system_sandbox_policy();
file_system_policy_has_cwd_dependent_entries(&file_system_policy)
match &self.permissions {
PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Restricted { entries, .. },
..
} => entries.iter().any(|entry| match &entry.path {
FileSystemPath::GlobPattern { pattern } => !Path::new(pattern).is_absolute(),
FileSystemPath::Special {
value: FileSystemSpecialPath::ProjectRoots { .. },
} => true,
FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => false,
}),
PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Unrestricted,
..
}
| PermissionProfile::Disabled
| PermissionProfile::External { .. } => false,
}
}
pub fn drop_cwd_if_unused(mut self) -> Self {
@@ -118,21 +149,6 @@ impl FileSystemSandboxContext {
}
}
fn file_system_policy_has_cwd_dependent_entries(
file_system_policy: &FileSystemSandboxPolicy,
) -> bool {
file_system_policy
.entries
.iter()
.any(|entry| match &entry.path {
FileSystemPath::GlobPattern { pattern } => !Path::new(pattern).is_absolute(),
FileSystemPath::Special {
value: FileSystemSpecialPath::ProjectRoots { .. },
} => true,
FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => false,
})
}
pub type FileSystemResult<T> = io::Result<T>;
/// Future returned by [`ExecutorFileSystem`] operations.
+183 -43
View File
@@ -23,6 +23,7 @@ use crate::protocol::SandboxPolicy;
use crate::user_input::UserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_image::ImageProcessingError;
use codex_utils_path_uri::PathUri;
use schemars::JsonSchema;
use crate::mcp::CallToolResult;
@@ -62,22 +63,59 @@ impl SandboxPermissions {
}
}
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, JsonSchema, TS)]
pub struct FileSystemPermissions {
pub entries: Vec<FileSystemSandboxEntry>,
#[derive(Debug, Clone, Eq, Hash, PartialEq, JsonSchema, TS)]
pub struct FileSystemPermissions<PathType = AbsolutePathBuf> {
pub entries: Vec<FileSystemSandboxEntry<PathType>>,
pub glob_scan_max_depth: Option<NonZeroUsize>,
}
pub type LegacyReadWriteRoots = (Option<Vec<AbsolutePathBuf>>, Option<Vec<AbsolutePathBuf>>);
impl From<FileSystemPermissions<AbsolutePathBuf>> for FileSystemPermissions<PathUri> {
fn from(value: FileSystemPermissions<AbsolutePathBuf>) -> Self {
FileSystemPermissions {
entries: value
.entries
.into_iter()
.map(FileSystemSandboxEntry::<PathUri>::from)
.collect(),
glob_scan_max_depth: value.glob_scan_max_depth,
}
}
}
impl FileSystemPermissions {
impl TryFrom<FileSystemPermissions<PathUri>> for FileSystemPermissions<AbsolutePathBuf> {
type Error = io::Error;
fn try_from(value: FileSystemPermissions<PathUri>) -> Result<Self, Self::Error> {
Ok(FileSystemPermissions {
entries: value
.entries
.into_iter()
.map(FileSystemSandboxEntry::<AbsolutePathBuf>::try_from)
.collect::<io::Result<_>>()?,
glob_scan_max_depth: value.glob_scan_max_depth,
})
}
}
impl<PathType> Default for FileSystemPermissions<PathType> {
fn default() -> Self {
Self {
entries: Vec::new(),
glob_scan_max_depth: None,
}
}
}
pub type LegacyReadWriteRoots<PathType = AbsolutePathBuf> =
(Option<Vec<PathType>>, Option<Vec<PathType>>);
impl<PathType> FileSystemPermissions<PathType> {
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn from_read_write_roots(
read: Option<Vec<AbsolutePathBuf>>,
write: Option<Vec<AbsolutePathBuf>>,
read: Option<Vec<PathType>>,
write: Option<Vec<PathType>>,
) -> Self {
let mut entries = Vec::new();
if let Some(read) = read {
@@ -98,21 +136,25 @@ impl FileSystemPermissions {
}
}
pub fn explicit_path_entries(
&self,
) -> impl Iterator<Item = (&AbsolutePathBuf, FileSystemAccessMode)> {
pub fn explicit_path_entries(&self) -> impl Iterator<Item = (&PathType, FileSystemAccessMode)> {
self.entries.iter().filter_map(|entry| match &entry.path {
FileSystemPath::Path { path } => Some((path, entry.access)),
FileSystemPath::GlobPattern { .. } | FileSystemPath::Special { .. } => None,
})
}
pub fn legacy_read_write_roots(&self) -> Option<LegacyReadWriteRoots> {
pub fn legacy_read_write_roots(&self) -> Option<LegacyReadWriteRoots<PathType>>
where
PathType: Clone,
{
self.as_legacy_permissions()
.map(|legacy| (legacy.read, legacy.write))
}
fn as_legacy_permissions(&self) -> Option<LegacyFileSystemPermissions> {
fn as_legacy_permissions(&self) -> Option<LegacyFileSystemPermissions<PathType>>
where
PathType: Clone,
{
if self.glob_scan_max_depth.is_some() {
return None;
}
@@ -140,30 +182,35 @@ impl FileSystemPermissions {
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct LegacyFileSystemPermissions {
#[serde(bound(deserialize = "PathType: Deserialize<'de>"))]
struct LegacyFileSystemPermissions<PathType = AbsolutePathBuf> {
#[serde(default, skip_serializing_if = "Option::is_none")]
read: Option<Vec<AbsolutePathBuf>>,
read: Option<Vec<PathType>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
write: Option<Vec<AbsolutePathBuf>>,
write: Option<Vec<PathType>>,
}
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct CanonicalFileSystemPermissions {
#[serde(bound(deserialize = "PathType: Deserialize<'de>"))]
struct CanonicalFileSystemPermissions<PathType = AbsolutePathBuf> {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
entries: Vec<FileSystemSandboxEntry>,
entries: Vec<FileSystemSandboxEntry<PathType>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
glob_scan_max_depth: Option<NonZeroUsize>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum FileSystemPermissionsDe {
Canonical(CanonicalFileSystemPermissions),
Legacy(LegacyFileSystemPermissions),
enum FileSystemPermissionsDe<PathType = AbsolutePathBuf> {
Canonical(CanonicalFileSystemPermissions<PathType>),
Legacy(LegacyFileSystemPermissions<PathType>),
}
impl Serialize for FileSystemPermissions {
impl<PathType> Serialize for FileSystemPermissions<PathType>
where
PathType: Clone + Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@@ -180,7 +227,10 @@ impl Serialize for FileSystemPermissions {
}
}
impl<'de> Deserialize<'de> for FileSystemPermissions {
impl<'de, PathType> Deserialize<'de> for FileSystemPermissions<PathType>
where
PathType: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
@@ -253,12 +303,12 @@ impl SandboxEnforcement {
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(tag = "type")]
pub enum ManagedFileSystemPermissions {
pub enum ManagedFileSystemPermissions<PathType = AbsolutePathBuf> {
/// Apply a managed filesystem sandbox from the listed entries.
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
Restricted {
entries: Vec<FileSystemSandboxEntry>,
entries: Vec<FileSystemSandboxEntry<PathType>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
glob_scan_max_depth: Option<NonZeroUsize>,
@@ -267,6 +317,50 @@ pub enum ManagedFileSystemPermissions {
Unrestricted,
}
impl From<ManagedFileSystemPermissions<AbsolutePathBuf>> for ManagedFileSystemPermissions<PathUri> {
fn from(value: ManagedFileSystemPermissions<AbsolutePathBuf>) -> Self {
match value {
ManagedFileSystemPermissions::Restricted {
entries,
glob_scan_max_depth,
} => ManagedFileSystemPermissions::Restricted {
entries: entries
.into_iter()
.map(FileSystemSandboxEntry::<PathUri>::from)
.collect(),
glob_scan_max_depth,
},
ManagedFileSystemPermissions::Unrestricted => {
ManagedFileSystemPermissions::Unrestricted
}
}
}
}
impl TryFrom<ManagedFileSystemPermissions<PathUri>>
for ManagedFileSystemPermissions<AbsolutePathBuf>
{
type Error = io::Error;
fn try_from(value: ManagedFileSystemPermissions<PathUri>) -> Result<Self, Self::Error> {
Ok(match value {
ManagedFileSystemPermissions::Restricted {
entries,
glob_scan_max_depth,
} => ManagedFileSystemPermissions::Restricted {
entries: entries
.into_iter()
.map(FileSystemSandboxEntry::<AbsolutePathBuf>::try_from)
.collect::<io::Result<_>>()?,
glob_scan_max_depth,
},
ManagedFileSystemPermissions::Unrestricted => {
ManagedFileSystemPermissions::Unrestricted
}
})
}
}
impl ManagedFileSystemPermissions {
fn from_sandbox_policy(file_system_sandbox_policy: &FileSystemSandboxPolicy) -> Self {
match file_system_sandbox_policy.kind {
@@ -311,12 +405,12 @@ pub const BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS: &str = ":danger-full-a
#[derive(Debug, Clone, Eq, PartialEq, Serialize, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(tag = "type")]
pub enum PermissionProfile {
pub enum PermissionProfile<PathType = AbsolutePathBuf> {
/// Codex owns sandbox construction for this profile.
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
Managed {
file_system: ManagedFileSystemPermissions,
file_system: ManagedFileSystemPermissions<PathType>,
network: NetworkSandboxPolicy,
},
/// Do not apply an outer sandbox.
@@ -327,6 +421,40 @@ pub enum PermissionProfile {
External { network: NetworkSandboxPolicy },
}
impl From<PermissionProfile<AbsolutePathBuf>> for PermissionProfile<PathUri> {
fn from(value: PermissionProfile<AbsolutePathBuf>) -> Self {
match value {
PermissionProfile::Managed {
file_system,
network,
} => PermissionProfile::Managed {
file_system: file_system.into(),
network,
},
PermissionProfile::Disabled => PermissionProfile::Disabled,
PermissionProfile::External { network } => PermissionProfile::External { network },
}
}
}
impl TryFrom<PermissionProfile<PathUri>> for PermissionProfile<AbsolutePathBuf> {
type Error = io::Error;
fn try_from(value: PermissionProfile<PathUri>) -> Result<Self, Self::Error> {
Ok(match value {
PermissionProfile::Managed {
file_system,
network,
} => PermissionProfile::Managed {
file_system: file_system.try_into()?,
network,
},
PermissionProfile::Disabled => PermissionProfile::Disabled,
PermissionProfile::External { network } => PermissionProfile::External { network },
})
}
}
/// Metadata for the named or implicit built-in permissions profile that
/// produced the active `PermissionProfile`.
///
@@ -360,7 +488,7 @@ impl ActivePermissionProfile {
}
}
impl Default for PermissionProfile {
impl<PathType> Default for PermissionProfile<PathType> {
fn default() -> Self {
Self::Managed {
file_system: ManagedFileSystemPermissions::Restricted {
@@ -548,10 +676,10 @@ impl PermissionProfile {
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum TaggedPermissionProfile {
enum TaggedPermissionProfile<PathType = AbsolutePathBuf> {
#[serde(rename_all = "snake_case")]
Managed {
file_system: ManagedFileSystemPermissions,
file_system: ManagedFileSystemPermissions<PathType>,
network: NetworkSandboxPolicy,
},
Disabled,
@@ -561,8 +689,8 @@ enum TaggedPermissionProfile {
},
}
impl From<TaggedPermissionProfile> for PermissionProfile {
fn from(value: TaggedPermissionProfile) -> Self {
impl<PathType> From<TaggedPermissionProfile<PathType>> for PermissionProfile<PathType> {
fn from(value: TaggedPermissionProfile<PathType>) -> Self {
match value {
TaggedPermissionProfile::Managed {
file_system,
@@ -581,16 +709,22 @@ impl From<TaggedPermissionProfile> for PermissionProfile {
/// represented enforcement explicitly.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct LegacyPermissionProfile {
struct LegacyPermissionProfile<PathType = AbsolutePathBuf> {
network: Option<NetworkPermissions>,
file_system: Option<FileSystemPermissions>,
file_system: Option<FileSystemPermissions<PathType>>,
}
impl From<LegacyPermissionProfile> for PermissionProfile {
fn from(value: LegacyPermissionProfile) -> Self {
let file_system_sandbox_policy = value.file_system.as_ref().map_or_else(
|| FileSystemSandboxPolicy::restricted(Vec::new()),
FileSystemSandboxPolicy::from,
impl<PathType> From<LegacyPermissionProfile<PathType>> for PermissionProfile<PathType> {
fn from(value: LegacyPermissionProfile<PathType>) -> Self {
let file_system = value.file_system.map_or_else(
|| ManagedFileSystemPermissions::Restricted {
entries: Vec::new(),
glob_scan_max_depth: None,
},
|permissions| ManagedFileSystemPermissions::Restricted {
entries: permissions.entries,
glob_scan_max_depth: permissions.glob_scan_max_depth,
},
);
let network_sandbox_policy = if value
.network
@@ -602,18 +736,24 @@ impl From<LegacyPermissionProfile> for PermissionProfile {
} else {
NetworkSandboxPolicy::Restricted
};
Self::from_runtime_permissions(&file_system_sandbox_policy, network_sandbox_policy)
Self::Managed {
file_system,
network: network_sandbox_policy,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum PermissionProfileDe {
Tagged(TaggedPermissionProfile),
Legacy(LegacyPermissionProfile),
enum PermissionProfileDe<PathType = AbsolutePathBuf> {
Tagged(TaggedPermissionProfile<PathType>),
Legacy(LegacyPermissionProfile<PathType>),
}
impl<'de> Deserialize<'de> for PermissionProfile {
impl<'de, PathType> Deserialize<'de> for PermissionProfile<PathType>
where
PathType: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
+51 -4
View File
@@ -6,6 +6,7 @@ use std::path::PathBuf;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::canonicalize_preserving_symlinks;
use codex_utils_path_uri::PathUri;
use globset::GlobBuilder;
use globset::GlobMatcher;
use schemars::JsonSchema;
@@ -176,11 +177,31 @@ impl FileSystemSpecialPath {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)]
pub struct FileSystemSandboxEntry {
pub path: FileSystemPath,
pub struct FileSystemSandboxEntry<PathType = AbsolutePathBuf> {
pub path: FileSystemPath<PathType>,
pub access: FileSystemAccessMode,
}
impl From<FileSystemSandboxEntry<AbsolutePathBuf>> for FileSystemSandboxEntry<PathUri> {
fn from(value: FileSystemSandboxEntry<AbsolutePathBuf>) -> Self {
FileSystemSandboxEntry {
path: value.path.into(),
access: value.access,
}
}
}
impl TryFrom<FileSystemSandboxEntry<PathUri>> for FileSystemSandboxEntry<AbsolutePathBuf> {
type Error = io::Error;
fn try_from(value: FileSystemSandboxEntry<PathUri>) -> Result<Self, Self::Error> {
Ok(FileSystemSandboxEntry {
path: value.path.try_into()?,
access: value.access,
})
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, Default, JsonSchema, TS,
)]
@@ -338,9 +359,9 @@ enum InvalidDenyReadGlobBehavior {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(tag = "type")]
pub enum FileSystemPath {
pub enum FileSystemPath<PathType = AbsolutePathBuf> {
Path {
path: AbsolutePathBuf,
path: PathType,
},
/// A git-style glob pattern. Pattern entries currently support
/// FileSystemAccessMode::Deny only.
@@ -352,6 +373,32 @@ pub enum FileSystemPath {
},
}
impl From<FileSystemPath<AbsolutePathBuf>> for FileSystemPath<PathUri> {
fn from(value: FileSystemPath<AbsolutePathBuf>) -> Self {
match value {
FileSystemPath::Path { path } => FileSystemPath::Path {
path: PathUri::from_abs_path(&path),
},
FileSystemPath::GlobPattern { pattern } => FileSystemPath::GlobPattern { pattern },
FileSystemPath::Special { value } => FileSystemPath::Special { value },
}
}
}
impl TryFrom<FileSystemPath<PathUri>> for FileSystemPath<AbsolutePathBuf> {
type Error = io::Error;
fn try_from(value: FileSystemPath<PathUri>) -> Result<Self, Self::Error> {
Ok(match value {
FileSystemPath::Path { path } => FileSystemPath::Path {
path: path.to_abs_path()?,
},
FileSystemPath::GlobPattern { pattern } => FileSystemPath::GlobPattern { pattern },
FileSystemPath::Special { value } => FileSystemPath::Special { value },
})
}
}
const PROJECT_ROOTS_GLOB_PATTERN_PREFIX: &str = "codex-project-roots://";
pub fn project_roots_glob_pattern(subpath: &Path) -> String {
+2 -2
View File
@@ -371,10 +371,10 @@ mod tests {
);
assert_eq!(metadata.cwd, PathBuf::from("/child/worktree"));
let permission_profile: PermissionProfile = PermissionProfile::Disabled;
assert_eq!(
metadata.sandbox_policy,
serde_json::to_string(&PermissionProfile::Disabled)
.expect("serialize permission profile")
serde_json::to_string(&permission_profile).expect("serialize permission profile")
);
assert_eq!(metadata.approval_mode, "never");
}
@@ -731,8 +731,9 @@ mod tests {
builder.model_provider = Some(config.default_model_provider_id.clone());
builder.cwd = home.path().to_path_buf();
let mut metadata = builder.build(config.default_model_provider_id.as_str());
let permission_profile: PermissionProfile = PermissionProfile::Disabled;
metadata.sandbox_policy =
serde_json::to_string(&PermissionProfile::Disabled).expect("serialize profile");
serde_json::to_string(&permission_profile).expect("serialize profile");
runtime
.upsert_thread(&metadata)
.await
@@ -886,9 +886,10 @@ mod tests {
.await
.expect("sqlite metadata read")
.expect("sqlite metadata");
let permission_profile: PermissionProfile = PermissionProfile::Disabled;
assert_eq!(
metadata.sandbox_policy,
serde_json::to_string(&PermissionProfile::Disabled).expect("serialize profile")
serde_json::to_string(&permission_profile).expect("serialize profile")
);
}