Separate exec permission paths from core models (#33200)

## Why

Core filesystem permission models operate on native absolute paths, while exec
sandbox contexts must serialize paths as portable URIs.

## What changed

- Make the core filesystem permission and profile types concrete over
  `AbsolutePathBuf` instead of generic over their path representation.
- Add exec-specific permission types that convert native permission paths to
  `PathUri` values at the sandbox protocol boundary.
- Replace the legacy read/write-roots tuple with a named
  `LegacyReadWriteRoots` struct.

## Testing

- Add coverage that exec sandbox permission paths serialize as URIs and round
  trip through the filesystem protocol.

GitOrigin-RevId: a0ef5f8aba9bbfdf00a00fcc4f199314836bc85d
This commit is contained in:
Adam Perry @ OpenAI
2026-07-15 01:31:40 +00:00
committed by copyberry
parent 70a0b1eef8
commit 72b41c55fb
7 changed files with 292 additions and 281 deletions
@@ -7,6 +7,7 @@ use codex_protocol::approvals::NetworkPolicyRuleAction as CoreNetworkPolicyRuleA
use codex_protocol::models::ActivePermissionProfile as CoreActivePermissionProfile;
use codex_protocol::models::AdditionalPermissionProfile as CoreAdditionalPermissionProfile;
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
use codex_protocol::models::LegacyReadWriteRoots;
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
use codex_protocol::permissions::FileSystemAccessMode as CoreFileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath as CoreFileSystemPath;
@@ -69,9 +70,9 @@ pub struct AdditionalFileSystemPermissions {
}
// TODO(anp): Remove this conversion once core permission paths use PathUri.
impl From<CoreFileSystemPermissions<AbsolutePathBuf>> for AdditionalFileSystemPermissions {
fn from(value: CoreFileSystemPermissions<AbsolutePathBuf>) -> Self {
if let Some((read, write)) = value.legacy_read_write_roots() {
impl From<CoreFileSystemPermissions> for AdditionalFileSystemPermissions {
fn from(value: CoreFileSystemPermissions) -> Self {
if let Some(LegacyReadWriteRoots { read, write }) = value.legacy_read_write_roots() {
let mut entries = Vec::with_capacity(
read.as_ref().map_or(0, Vec::len) + write.as_ref().map_or(0, Vec::len),
);
@@ -125,7 +126,7 @@ impl From<CoreFileSystemPermissions<AbsolutePathBuf>> for AdditionalFileSystemPe
}
// TODO(anp): Remove this conversion once core permission paths use PathUri.
impl TryFrom<AdditionalFileSystemPermissions> for CoreFileSystemPermissions<AbsolutePathBuf> {
impl TryFrom<AdditionalFileSystemPermissions> for CoreFileSystemPermissions {
type Error = io::Error;
fn try_from(value: AdditionalFileSystemPermissions) -> Result<Self, Self::Error> {
@@ -133,7 +134,7 @@ impl TryFrom<AdditionalFileSystemPermissions> for CoreFileSystemPermissions<Abso
Self {
entries: entries
.into_iter()
.map(CoreFileSystemSandboxEntry::<AbsolutePathBuf>::try_from)
.map(CoreFileSystemSandboxEntry::try_from)
.collect::<io::Result<_>>()?,
glob_scan_max_depth: None,
}
@@ -221,7 +222,7 @@ impl TryFrom<RequestPermissionProfile> for CoreRequestPermissionProfile {
network: value.network.map(CoreNetworkPermissions::from),
file_system: value
.file_system
.map(CoreFileSystemPermissions::<AbsolutePathBuf>::try_from)
.map(CoreFileSystemPermissions::try_from)
.transpose()?,
})
}
@@ -300,7 +301,7 @@ impl From<FileSystemSpecialPath> for CoreFileSystemSpecialPath {
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
// TODO(anp): Rename this type to distinguish it from the generic protocol FileSystemPath.
// TODO(anp): Rename this type to distinguish it from the protocol FileSystemPath.
pub enum FileSystemPath {
Path { path: LegacyAppPathString },
GlobPattern { pattern: String },
@@ -308,8 +309,8 @@ pub enum FileSystemPath {
}
// TODO(anp): Remove this conversion once core permission paths use PathUri.
impl From<CoreFileSystemPath<AbsolutePathBuf>> for FileSystemPath {
fn from(value: CoreFileSystemPath<AbsolutePathBuf>) -> Self {
impl From<CoreFileSystemPath> for FileSystemPath {
fn from(value: CoreFileSystemPath) -> Self {
match value {
CoreFileSystemPath::Path { path } => Self::Path {
path: LegacyAppPathString::from_abs_path(&path),
@@ -323,7 +324,7 @@ impl From<CoreFileSystemPath<AbsolutePathBuf>> for FileSystemPath {
}
// TODO(anp): Remove this conversion once core permission paths use PathUri.
impl TryFrom<FileSystemPath> for CoreFileSystemPath<AbsolutePathBuf> {
impl TryFrom<FileSystemPath> for CoreFileSystemPath {
type Error = io::Error;
fn try_from(value: FileSystemPath) -> Result<Self, Self::Error> {
@@ -351,8 +352,8 @@ pub struct FileSystemSandboxEntry {
}
// TODO(anp): Remove this conversion once core permission paths use PathUri.
impl From<CoreFileSystemSandboxEntry<AbsolutePathBuf>> for FileSystemSandboxEntry {
fn from(value: CoreFileSystemSandboxEntry<AbsolutePathBuf>) -> Self {
impl From<CoreFileSystemSandboxEntry> for FileSystemSandboxEntry {
fn from(value: CoreFileSystemSandboxEntry) -> Self {
Self {
path: value.path.into(),
access: value.access.into(),
@@ -360,7 +361,7 @@ impl From<CoreFileSystemSandboxEntry<AbsolutePathBuf>> for FileSystemSandboxEntr
}
}
impl TryFrom<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry<AbsolutePathBuf> {
impl TryFrom<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry {
type Error = io::Error;
fn try_from(value: FileSystemSandboxEntry) -> Result<Self, Self::Error> {
@@ -479,7 +480,7 @@ impl TryFrom<AdditionalPermissionProfile> for CoreAdditionalPermissionProfile {
network: value.network.map(CoreNetworkPermissions::from),
file_system: value
.file_system
.map(CoreFileSystemPermissions::<AbsolutePathBuf>::try_from)
.map(CoreFileSystemPermissions::try_from)
.transpose()?,
})
}
@@ -505,7 +506,7 @@ impl TryFrom<GrantedPermissionProfile> for CoreAdditionalPermissionProfile {
network: value.network.map(CoreNetworkPermissions::from),
file_system: value
.file_system
.map(CoreFileSystemPermissions::<AbsolutePathBuf>::try_from)
.map(CoreFileSystemPermissions::try_from)
.transpose()?,
})
}
@@ -290,7 +290,7 @@ fn write_permissions_for_paths_keep_dirs_outside_workspace_root() {
permissions
.and_then(|profile| profile.file_system)
.and_then(|fs| fs.legacy_read_write_roots())
.and_then(|(_read, write)| write),
.and_then(|roots| roots.write),
Some(vec![expected_outside])
);
}
@@ -1649,7 +1649,10 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions()
let approval_file_system = approval_permissions
.file_system
.expect("expected filesystem permissions");
let (approval_reads, approval_writes) = approval_file_system
let codex_protocol::models::LegacyReadWriteRoots {
read: approval_reads,
write: approval_writes,
} = approval_file_system
.legacy_read_write_roots()
.expect("expected legacy-compatible permissions");
assert!(approval_reads.as_ref().is_none_or(Vec::is_empty));
@@ -1657,7 +1660,10 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions()
let mut approval_writes = approval_writes.unwrap_or_default();
approval_writes.sort_by_key(|path| path.display().to_string());
let (_, expected_writes) = merged_permissions
let codex_protocol::models::LegacyReadWriteRoots {
write: expected_writes,
..
} = merged_permissions
.file_system
.expect("expected merged filesystem permissions")
.legacy_read_write_roots()
@@ -597,6 +597,11 @@ mod tests {
use codex_file_system::FileSystemSandboxContext;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
@@ -694,6 +699,38 @@ mod tests {
.expect_err("native absolute sandbox cwd should not deserialize as a URI");
}
#[test]
fn filesystem_protocol_round_trips_permission_paths_as_uris() {
let native_cwd = std::env::current_dir().expect("current directory");
let cwd = PathUri::from_host_native_path(&native_cwd).expect("cwd URI");
let mut file_system_policy =
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: native_cwd.try_into().expect("absolute cwd"),
},
access: FileSystemAccessMode::Read,
}]);
file_system_policy.glob_scan_max_depth = Some(2);
let permissions = PermissionProfile::from_runtime_permissions(
&file_system_policy,
NetworkSandboxPolicy::Restricted,
);
let sandbox =
FileSystemSandboxContext::from_permission_profile_with_cwd(permissions, cwd.clone());
let serialized = serde_json::to_value(&sandbox).expect("serialize sandbox");
assert_eq!(
serialized["permissions"]["file_system"]["entries"][0]["path"]["path"],
serde_json::json!(cwd.to_string())
);
assert_eq!(
serde_json::from_value::<FileSystemSandboxContext>(serialized)
.expect("deserialize sandbox"),
sandbox
);
}
#[test]
fn http_request_timeout_treats_omitted_and_null_as_no_timeout() {
let omitted: HttpRequestParams = serde_json::from_value(serde_json::json!({
+181 -34
View File
@@ -1,27 +1,30 @@
mod find_up;
pub use find_up::FindUpErrorPolicy;
pub use find_up::find_nearest_ancestor_with_markers;
pub use find_up::find_nearest_native_ancestor_with_markers;
use bytes::Bytes;
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::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxKind;
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;
pub use find_up::FindUpErrorPolicy;
pub use find_up::find_nearest_ancestor_with_markers;
pub use find_up::find_nearest_native_ancestor_with_markers;
use futures::Stream;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::future::Future;
use std::io;
use std::num::NonZeroUsize;
use std::path::Path;
use std::pin::Pin;
use std::task::Context;
@@ -119,10 +122,163 @@ pub struct WalkOutcome {
pub truncated: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ExecFileSystemPath {
Path { path: PathUri },
GlobPattern { pattern: String },
Special { value: FileSystemSpecialPath },
}
impl From<FileSystemPath> for ExecFileSystemPath {
fn from(value: FileSystemPath) -> Self {
match value {
FileSystemPath::Path { path } => Self::Path {
path: PathUri::from_abs_path(&path),
},
FileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
FileSystemPath::Special { value } => Self::Special { value },
}
}
}
impl TryFrom<ExecFileSystemPath> for FileSystemPath {
type Error = io::Error;
fn try_from(value: ExecFileSystemPath) -> Result<Self, Self::Error> {
Ok(match value {
ExecFileSystemPath::Path { path } => Self::Path {
path: path.to_abs_path()?,
},
ExecFileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
ExecFileSystemPath::Special { value } => Self::Special { value },
})
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ExecFileSystemSandboxEntry {
pub path: ExecFileSystemPath,
pub access: FileSystemAccessMode,
}
impl From<FileSystemSandboxEntry> for ExecFileSystemSandboxEntry {
fn from(value: FileSystemSandboxEntry) -> Self {
Self {
path: value.path.into(),
access: value.access,
}
}
}
impl TryFrom<ExecFileSystemSandboxEntry> for FileSystemSandboxEntry {
type Error = io::Error;
fn try_from(value: ExecFileSystemSandboxEntry) -> Result<Self, Self::Error> {
Ok(Self {
path: value.path.try_into()?,
access: value.access,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ExecManagedFileSystemPermissions {
Restricted {
entries: Vec<ExecFileSystemSandboxEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
glob_scan_max_depth: Option<NonZeroUsize>,
},
Unrestricted,
}
impl From<ManagedFileSystemPermissions> for ExecManagedFileSystemPermissions {
fn from(value: ManagedFileSystemPermissions) -> Self {
match value {
ManagedFileSystemPermissions::Restricted {
entries,
glob_scan_max_depth,
} => Self::Restricted {
entries: entries.into_iter().map(Into::into).collect(),
glob_scan_max_depth,
},
ManagedFileSystemPermissions::Unrestricted => Self::Unrestricted,
}
}
}
impl TryFrom<ExecManagedFileSystemPermissions> for ManagedFileSystemPermissions {
type Error = io::Error;
fn try_from(value: ExecManagedFileSystemPermissions) -> Result<Self, Self::Error> {
Ok(match value {
ExecManagedFileSystemPermissions::Restricted {
entries,
glob_scan_max_depth,
} => Self::Restricted {
entries: entries
.into_iter()
.map(TryInto::try_into)
.collect::<io::Result<_>>()?,
glob_scan_max_depth,
},
ExecManagedFileSystemPermissions::Unrestricted => Self::Unrestricted,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ExecPermissionProfile {
Managed {
file_system: ExecManagedFileSystemPermissions,
network: NetworkSandboxPolicy,
},
Disabled,
External {
network: NetworkSandboxPolicy,
},
}
impl From<PermissionProfile> for ExecPermissionProfile {
fn from(value: PermissionProfile) -> Self {
match value {
PermissionProfile::Managed {
file_system,
network,
} => Self::Managed {
file_system: file_system.into(),
network,
},
PermissionProfile::Disabled => Self::Disabled,
PermissionProfile::External { network } => Self::External { network },
}
}
}
impl TryFrom<ExecPermissionProfile> for PermissionProfile {
type Error = io::Error;
fn try_from(value: ExecPermissionProfile) -> Result<Self, Self::Error> {
Ok(match value {
ExecPermissionProfile::Managed {
file_system,
network,
} => Self::Managed {
file_system: file_system.try_into()?,
network,
},
ExecPermissionProfile::Disabled => Self::Disabled,
ExecPermissionProfile::External { network } => Self::External { network },
})
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileSystemSandboxContext {
pub permissions: PermissionProfile<PathUri>,
pub permissions: ExecPermissionProfile,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathUri>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
@@ -147,30 +303,23 @@ impl FileSystemSandboxContext {
&sandbox_policy,
&native_cwd,
);
let permissions =
PermissionProfile::<AbsolutePathBuf>::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(&sandbox_policy),
);
let permissions = PermissionProfile::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<AbsolutePathBuf>) -> Self {
pub fn from_permission_profile(permissions: PermissionProfile) -> Self {
Self::from_permissions_and_cwd(permissions, /*cwd*/ None)
}
pub fn from_permission_profile_with_cwd(
permissions: PermissionProfile<AbsolutePathBuf>,
cwd: PathUri,
) -> Self {
pub fn from_permission_profile_with_cwd(permissions: PermissionProfile, cwd: PathUri) -> Self {
Self::from_permissions_and_cwd(permissions, Some(cwd))
}
fn from_permissions_and_cwd(
permissions: PermissionProfile<AbsolutePathBuf>,
cwd: Option<PathUri>,
) -> Self {
fn from_permissions_and_cwd(permissions: PermissionProfile, cwd: Option<PathUri>) -> Self {
let workspace_roots = cwd.iter().cloned().collect();
Self {
permissions: permissions.into(),
@@ -183,9 +332,7 @@ impl FileSystemSandboxContext {
}
pub fn should_run_in_sandbox(&self) -> bool {
let Ok(permissions) =
PermissionProfile::<AbsolutePathBuf>::try_from(self.permissions.clone())
else {
let Ok(permissions) = PermissionProfile::try_from(self.permissions.clone()) else {
// A sandbox context for another host must not select the unsandboxed filesystem.
return true;
};
@@ -196,22 +343,22 @@ impl FileSystemSandboxContext {
pub fn has_cwd_dependent_permissions(&self) -> bool {
match &self.permissions {
PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Restricted { entries, .. },
ExecPermissionProfile::Managed {
file_system: ExecManagedFileSystemPermissions::Restricted { entries, .. },
..
} => entries.iter().any(|entry| match &entry.path {
FileSystemPath::GlobPattern { pattern } => !Path::new(pattern).is_absolute(),
FileSystemPath::Special {
ExecFileSystemPath::GlobPattern { pattern } => !Path::new(pattern).is_absolute(),
ExecFileSystemPath::Special {
value: FileSystemSpecialPath::ProjectRoots { .. },
} => true,
FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => false,
ExecFileSystemPath::Path { .. } | ExecFileSystemPath::Special { .. } => false,
}),
PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Unrestricted,
ExecPermissionProfile::Managed {
file_system: ExecManagedFileSystemPermissions::Unrestricted,
..
}
| PermissionProfile::Disabled
| PermissionProfile::External { .. } => false,
| ExecPermissionProfile::Disabled
| ExecPermissionProfile::External { .. } => false,
}
}
+44 -178
View File
@@ -23,7 +23,6 @@ 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::ResponseItemId;
@@ -64,59 +63,29 @@ impl SandboxPermissions {
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq, JsonSchema, TS)]
pub struct FileSystemPermissions<PathType = AbsolutePathBuf> {
pub entries: Vec<FileSystemSandboxEntry<PathType>>,
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, JsonSchema, TS)]
pub struct FileSystemPermissions {
pub entries: Vec<FileSystemSandboxEntry>,
pub glob_scan_max_depth: Option<NonZeroUsize>,
}
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,
}
}
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LegacyReadWriteRoots {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub read: Option<Vec<AbsolutePathBuf>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub write: Option<Vec<AbsolutePathBuf>>,
}
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> {
impl FileSystemPermissions {
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn from_read_write_roots(
read: Option<Vec<PathType>>,
write: Option<Vec<PathType>>,
read: Option<Vec<AbsolutePathBuf>>,
write: Option<Vec<AbsolutePathBuf>>,
) -> Self {
let mut entries = Vec::new();
if let Some(read) = read {
@@ -137,25 +106,20 @@ impl<PathType> FileSystemPermissions<PathType> {
}
}
pub fn explicit_path_entries(&self) -> impl Iterator<Item = (&PathType, FileSystemAccessMode)> {
pub fn explicit_path_entries(
&self,
) -> impl Iterator<Item = (&AbsolutePathBuf, 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<PathType>>
where
PathType: Clone,
{
pub fn legacy_read_write_roots(&self) -> Option<LegacyReadWriteRoots> {
self.as_legacy_permissions()
.map(|legacy| (legacy.read, legacy.write))
}
fn as_legacy_permissions(&self) -> Option<LegacyFileSystemPermissions<PathType>>
where
PathType: Clone,
{
fn as_legacy_permissions(&self) -> Option<LegacyReadWriteRoots> {
if self.glob_scan_max_depth.is_some() {
return None;
}
@@ -174,7 +138,7 @@ impl<PathType> FileSystemPermissions<PathType> {
}
}
Some(LegacyFileSystemPermissions {
Some(LegacyReadWriteRoots {
read: (!read.is_empty()).then_some(read),
write: (!write.is_empty()).then_some(write),
})
@@ -183,35 +147,21 @@ impl<PathType> FileSystemPermissions<PathType> {
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "PathType: Deserialize<'de>"))]
struct LegacyFileSystemPermissions<PathType = AbsolutePathBuf> {
#[serde(default, skip_serializing_if = "Option::is_none")]
read: Option<Vec<PathType>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
write: Option<Vec<PathType>>,
}
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(bound(deserialize = "PathType: Deserialize<'de>"))]
struct CanonicalFileSystemPermissions<PathType = AbsolutePathBuf> {
struct CanonicalFileSystemPermissions {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
entries: Vec<FileSystemSandboxEntry<PathType>>,
entries: Vec<FileSystemSandboxEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
glob_scan_max_depth: Option<NonZeroUsize>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum FileSystemPermissionsDe<PathType = AbsolutePathBuf> {
Canonical(CanonicalFileSystemPermissions<PathType>),
Legacy(LegacyFileSystemPermissions<PathType>),
enum FileSystemPermissionsDe {
Canonical(CanonicalFileSystemPermissions),
Legacy(LegacyReadWriteRoots),
}
impl<PathType> Serialize for FileSystemPermissions<PathType>
where
PathType: Clone + Serialize,
{
impl Serialize for FileSystemPermissions {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@@ -228,10 +178,7 @@ where
}
}
impl<'de, PathType> Deserialize<'de> for FileSystemPermissions<PathType>
where
PathType: Deserialize<'de>,
{
impl<'de> Deserialize<'de> for FileSystemPermissions {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
@@ -244,7 +191,7 @@ where
entries,
glob_scan_max_depth,
}),
FileSystemPermissionsDe::Legacy(LegacyFileSystemPermissions { read, write }) => {
FileSystemPermissionsDe::Legacy(LegacyReadWriteRoots { read, write }) => {
Ok(Self::from_read_write_roots(read, write))
}
}
@@ -304,12 +251,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<PathType = AbsolutePathBuf> {
pub enum ManagedFileSystemPermissions {
/// Apply a managed filesystem sandbox from the listed entries.
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
Restricted {
entries: Vec<FileSystemSandboxEntry<PathType>>,
entries: Vec<FileSystemSandboxEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
glob_scan_max_depth: Option<NonZeroUsize>,
@@ -318,50 +265,6 @@ pub enum ManagedFileSystemPermissions<PathType = AbsolutePathBuf> {
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 {
@@ -406,12 +309,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<PathType = AbsolutePathBuf> {
pub enum PermissionProfile {
/// Codex owns sandbox construction for this profile.
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
Managed {
file_system: ManagedFileSystemPermissions<PathType>,
file_system: ManagedFileSystemPermissions,
network: NetworkSandboxPolicy,
},
/// Do not apply an outer sandbox.
@@ -422,40 +325,6 @@ pub enum PermissionProfile<PathType = AbsolutePathBuf> {
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`.
///
@@ -489,7 +358,7 @@ impl ActivePermissionProfile {
}
}
impl<PathType> Default for PermissionProfile<PathType> {
impl Default for PermissionProfile {
fn default() -> Self {
Self::Managed {
file_system: ManagedFileSystemPermissions::Restricted {
@@ -677,10 +546,10 @@ impl PermissionProfile {
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum TaggedPermissionProfile<PathType = AbsolutePathBuf> {
enum TaggedPermissionProfile {
#[serde(rename_all = "snake_case")]
Managed {
file_system: ManagedFileSystemPermissions<PathType>,
file_system: ManagedFileSystemPermissions,
network: NetworkSandboxPolicy,
},
Disabled,
@@ -690,8 +559,8 @@ enum TaggedPermissionProfile<PathType = AbsolutePathBuf> {
},
}
impl<PathType> From<TaggedPermissionProfile<PathType>> for PermissionProfile<PathType> {
fn from(value: TaggedPermissionProfile<PathType>) -> Self {
impl From<TaggedPermissionProfile> for PermissionProfile {
fn from(value: TaggedPermissionProfile) -> Self {
match value {
TaggedPermissionProfile::Managed {
file_system,
@@ -710,13 +579,13 @@ impl<PathType> From<TaggedPermissionProfile<PathType>> for PermissionProfile<Pat
/// represented enforcement explicitly.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct LegacyPermissionProfile<PathType = AbsolutePathBuf> {
struct LegacyPermissionProfile {
network: Option<NetworkPermissions>,
file_system: Option<FileSystemPermissions<PathType>>,
file_system: Option<FileSystemPermissions>,
}
impl<PathType> From<LegacyPermissionProfile<PathType>> for PermissionProfile<PathType> {
fn from(value: LegacyPermissionProfile<PathType>) -> Self {
impl From<LegacyPermissionProfile> for PermissionProfile {
fn from(value: LegacyPermissionProfile) -> Self {
let file_system = value.file_system.map_or_else(
|| ManagedFileSystemPermissions::Restricted {
entries: Vec::new(),
@@ -746,15 +615,12 @@ impl<PathType> From<LegacyPermissionProfile<PathType>> for PermissionProfile<Pat
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum PermissionProfileDe<PathType = AbsolutePathBuf> {
Tagged(TaggedPermissionProfile<PathType>),
Legacy(LegacyPermissionProfile<PathType>),
enum PermissionProfileDe {
Tagged(TaggedPermissionProfile),
Legacy(LegacyPermissionProfile),
}
impl<'de, PathType> Deserialize<'de> for PermissionProfile<PathType>
where
PathType: Deserialize<'de>,
{
impl<'de> Deserialize<'de> for PermissionProfile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
+5 -51
View File
@@ -6,7 +6,6 @@ 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;
@@ -177,31 +176,11 @@ impl FileSystemSpecialPath {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)]
pub struct FileSystemSandboxEntry<PathType = AbsolutePathBuf> {
pub path: FileSystemPath<PathType>,
pub struct FileSystemSandboxEntry {
pub path: FileSystemPath,
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,
)]
@@ -359,9 +338,10 @@ 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<PathType = AbsolutePathBuf> {
pub enum FileSystemPath {
Path {
path: PathType,
// TODO(anp): Use PathUri once permission paths no longer require native-path rollout serialization.
path: AbsolutePathBuf,
},
/// A git-style glob pattern. Pattern entries currently support
/// FileSystemAccessMode::Deny only.
@@ -373,32 +353,6 @@ pub enum FileSystemPath<PathType = AbsolutePathBuf> {
},
}
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 {