mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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:
committed by
GitHub
Unverified
parent
428cd44154
commit
46f17930b6
Generated
+2
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
|
||||
@@ -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
@@ -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>,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user